bytes不可变导致每次拼接都触发完整内存拷贝,引发频繁分配、复制与GC;bytearray应按典型包长预估容量初始化,避免空构造或盲目设过大值。

为什么直接用 bytes 做网络缓冲会拖慢吞吐
因为 bytes 不可变,每次追加数据(比如 b1 + b2)都会触发完整内存拷贝,新对象要分配、旧对象要复制、老对象还要等 GC。在高频收包场景下(如每秒数万小包),这会迅速吃光 CPU 并堆积内存碎片。
bytearray 作为可变缓冲的正确初始化方式
别用 bytearray() 空构造——它初始容量是 0,第一次 .extend() 就得 realloc;也别盲目设极大值(如 bytearray(65536))浪费内存。按典型包长预估更稳妥:
- HTTP/1.1 头部通常 bytearray(4096)
- UDP 单包 ≤ 65507 字节 → 用
bytearray(65536)足够 - 若包长波动大,先
bytearray(8192),再配合.resize()动态扩(注意:.resize()可能触发拷贝,但比频繁拼接少得多)
recv() 到底该用 memoryview 还是直接 .extend()
直接 .extend(recv_data) 看似简单,但 recv_data 是 bytes,仍会触发一次拷贝;更高效的是用 memoryview 零拷贝写入:
# 推荐:避免中间 bytes 拷贝 buf = bytearray(8192) mv = memoryview(buf) n = sock.recv_into(mv) # 直接写入 buf 底层内存 buf.resize(n) # 截断到实际接收长度
注意:sock.recv_into() 要求 socket 处于阻塞或设置了 SO_RCVTIMEO,非阻塞模式下可能返回 0 或抛 BlockingIOError,需自行处理。
边界处理:如何安全切分粘包而不破坏 bytearray 性能
切片本身不拷贝(buf[0:pos] 返回 bytes),但你往往需要保留剩余部分继续读。别用 buf = buf[pos:]——这是深拷贝。改用 .remove 或就地移动:
- 如果协议有固定头长(如 4 字节长度字段),用
del buf[:header_len]删除已解析部分(O(n) 但只删头部,可控) - 如果要提取中间一段并保留上下文,用
buf[pos:pos+length]获取bytes副本,再用del buf[:pos+length] - 极端吞吐场景可维护读写偏移(
read_offset),避免任何del,仅逻辑上“跳过”已处理字节(需自己管理生命周期,易出错)
真正难的不是怎么快,而是怎么在零拷贝、内存复用和代码可维护性之间卡准那个点——多数服务用 del buf[:n] 已足够,只有百万级 QPS 才值得上偏移游标方案。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











