多线程反序列化(BinaryFormatter/Protobuf)未达性能预期的排查
结合你描述的场景(数据已预读入内存、分块数与核心数一致、多线程耗时为单块5倍),核心原因集中在内存竞争、GC开销、序列化库隐性锁、CPU缓存效率这几个维度,具体拆解如下:
内存分配与GC全局锁竞争
反序列化Dictionary<string,string>、Hashtable这类集合时,会大量分配小对象(字符串、键值对条目等)。单线程下GC压力可控,多线程并发分配时,.NET的GC会触发全局停顿(尤其是在.NET Framework或未启用分段GC的.NET Core环境中)——多个线程同时等待GC完成,直接拉低并行效率。你看到的5倍耗时,很大概率是GC停顿的累计开销远超过反序列化本身的计算耗时。序列化库的内部隐性锁
即使每个线程使用独立的序列化器实例,部分库的内部元数据缓存(比如Protobuf的类型Descriptor缓存、MessagePack的序列化器缓存)是全局共享的,多线程并发访问时会触发隐性锁竞争。BinaryFormatter本身线程不安全,若不小心复用实例会直接导致线程冲突;而Protobuf虽然线程安全,但缓存的锁竞争仍会在高并发下产生等待开销,这也是它比BinaryFormatter快但仍未达预期的原因之一。CPU缓存失效(伪共享/缓存颠簸)
如果反序列化后的对象存储在连续内存块(比如数组)中,多线程并发操作相邻元素时会触发伪共享——多个线程同时修改同一条缓存线的数据,导致缓存频繁失效,CPU不得不反复从主存读取数据,大幅降低执行效率。另外,若分块数据本身的内存分布跨NUMA节点,线程调度到非数据所在NUMA节点时,内存访问延迟会显著增加。Parallel.ForEach/Tasks的调度开销
你提到Threads方式性能最优,本质是Parallel.ForEach依赖线程池调度,存在额外的任务排队、上下文切换开销;而线程池的线程初始化、负载均衡逻辑在计算密集型场景下反而不如直接创建固定数量(等于核心数)的线程高效。尤其是当反序列化任务的执行时间较短时,调度开销占比会被放大。CPU睿频降频影响
单线程执行时CPU通常能维持睿频高频,而多核心满载时,CPU会自动降频以控制功耗和温度。比如单线程睿频到4.5GHz,8核心满载时可能降到3.2GHz,实际计算能力的下降会导致总耗时无法达到单线程的N分之一。
内容的提问来源于stack exchange,提问作者jw9832hds

