You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python生成器yield/send传输大体积数据的开销疑问

关于生成器send/yield的性能问题解答

核心底层逻辑说明

Python 中generator.send()和yield的交互仅传递对象的引用,不会对传输的结构做任何深拷贝操作,本身的执行耗时是纳秒级,和传输对象的大小完全无关,你的初始认知是正确的。

你观测到的接近1秒的间隔,本质是send调用完成到生成器被调度执行到logger.info("got data")之间的所有前置操作的总耗时,和send/yield本身无关。

耗时的常见原因

你遇到的高延迟大概率来自两个核心方向:

  • 大对象的批量GC开销:你传输的百万键值对字典树,所有节点的引用计数初始为1,当发送端的作用域销毁该字典时,会触发整个对象树的引用计数批量减1,大量节点引用计数归零后会被即时回收,整个遍历销毁的过程会阻塞事件循环,刚好落在你两个日志的时间区间内。
  • 协程调度延迟:如果你的事件循环队列中存在大量待执行的前置任务,send将生成器标记为就绪后,需要等待所有前置任务执行完成才会执行到接收端的日志打印逻辑。加上30MiB Protobuf数据的反序列化本身就有数百毫秒的开销,累加后就会出现接近1秒的延迟。

验证方法

你可以新增埋点确认send本身的耗时:

# 发送端埋点
import time
data = huge_big_dictionary_of_dictionaries
logger.info("sending to generator")
start = time.perf_counter()
generator.send(data)
end = time.perf_counter()
logger.info(f"send operation itself cost: {end - start:.6f}s")

# 接收端埋点
data = yield(...)
logger.info("got data right after yield")

实测后你会发现send本身的耗时稳定在微秒级,和字典大小没有关联。

优化方案(实现常数时间传输)

  • 做对象池复用:如果这类大字典是高频传输的固定结构,用完后放回对象池复用,不要直接交给GC回收,避免批量销毁的开销。
  • 调整引用持有逻辑:发送端完成传输后不要立即释放大对象的引用,等接收端处理完成后再统一销毁,把回收开销转移到非关键路径。
  • 优化大对象的存储结构:不要一次性将30MiB的Protobuf全量反序列化为Python字典,Python字典的内存开销是序列化后数据的5~10倍,操作本身就很慢。可以按需反序列化需要访问的节点,或者用更紧凑的结构体(比如dataclass+slots、第三方的array类结构)存储字典树,大幅降低内存和操作开销。
  • 拆分长耗时任务:把大对象的处理逻辑拆分为多个小任务分步执行,避免单个任务阻塞事件循环超过10ms,降低调度延迟。

内容的提问来源于stack exchange,提问作者Goswin von Brederlow

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 13:12:02