使用BinaryFormatter序列化不同类时性能差异悬殊的原因排查
BinaryFormatter传输大字符串性能极差的根本原因
我通过BinaryFormatter结合TCP传输不同消息类型,遇到了显著的性能差异:
- 包含字节数组、体积约5MB的
SharedPreview类,传输耗时仅约500ms - 包含大字符串字段、体积仅200KB的
SharedClientLogs类,传输耗时却高达20秒
已知将字符串转为字节数组可解决该问题,以下是大字符串导致性能骤降的根本原因:
1. BinaryFormatter字符串序列化的元数据冗余
BinaryFormatter处理字符串时,并非直接写入原始字节,会额外生成大量元数据:
- 存储字符串长度、编码类型(默认UTF-16)等标识信息
- 对长字符串拆分存储,添加结构标记
- 日志类字符串通常低重复,无法复用
BinaryFormatter的字符串引用优化,反而因元数据累加,大幅增加序列化/反序列化的计算开销。
而字节数组的序列化逻辑极简:仅写入数组长度+原始二进制流,几乎无额外元数据,处理效率极高。
2. UTF-16编码的额外开销
C#字符串默认采用UTF-16编码,每个字符占2字节(扩展字符占4字节):
- 200KB的UTF-8文本转为UTF-16后体积翻倍至400KB,直接增大传输数据量
- 序列化时需逐字符处理编码转换,反序列化时还要重新解析为字符串,字符级别的操作远比对字节数组的直接读写耗时。
而SharedPreview中的字节数组是原始二进制数据,无需编码转换,直接写入流即可。
3. 内存操作与GC压力差异
- 序列化大字符串时,
BinaryFormatter会生成大量临时对象(如字符数组、序列化标记),频繁触发GC回收,导致线程阻塞,拖慢整体流程 - 字节数组序列化的内存操作更连续,临时对象极少,GC压力可忽略不计。
4. TCP传输的碎片化开销
BinaryFormatter序列化字符串生成的字节流因元数据插入而碎片化,TCP发送时需拆分更多小数据包,增加了握手、确认的交互次数,放大了传输开销。而字节数组的序列化流是连续二进制块,TCP可高效批量发送。
内容的提问来源于stack exchange,提问作者LarryB
相关产品推荐
相关产品推荐

