处理32GB连续内存,和拆成两个16GB块,性能真的一样吗?一位工程师用AMD旗舰U跑了几百组实验,发现"线性访问最优"这句老话,藏着个没人说清的边界条件。
他的测试结论很具体:块大小低于256KB时,跨块跳转的开销会吃掉你10%-47%的吞吐量。这个数据来自Ryzen 9 7950X3D,但背后的逻辑适用于几乎所有现代CPU。
为什么"越小越好"会翻车
现代CPU的内存层级像俄罗斯套娃。L1缓存32KB,L2 1MB,L3 128MB,然后才是DDR5。每次跨块访问,你都在赌预取器(prefetcher)能不能猜对你的下一步。
工程师的测试设计很精巧:内核接收> const>,也就是"块的向量"的非拥有版本。这模拟了真实场景——数据库分页、图像分块处理、流式计算,都是这个结构。
他跑了几种典型负载:标量统计(scalar_stats)、SIMD求和、内存拷贝。每种负载对内存子系统的压力不同,但曲线形状惊人地相似。
标量统计是最轻量的,大概3个周期处理一个float,0.75周期/字节。即便如此,当块大小从1MB砍到64KB,吞吐量从7GB/s跌到3.7GB/s。不是算法变了,是CPU花了将近一半时间在等内存。
预取器的"惯性"有多长
问题出在跨块跳转。线性访问时,硬件预取器会顺着地址流批量拉数据,像传送带一样。但当你跳到另一个不连续的块,预取器要重新"加速"。
工程师把这种现象量化成了"有效块大小"——让跨块惩罚低于5%的最小块尺寸。他的7950X3D上,这个阈值因负载而异:内存密集型任务需要512KB-1MB,计算密集型可以容忍64KB-256KB。
有个反直觉的发现:SIMD向量化(single instruction multiple data,单指令多数据)反而放大了块大小的敏感度。AVX-512把内存压力提上去后,小块的惩罚从10%飙升到35%。向量宽度越宽,对连续性的依赖越强。
这解释了为什么有些"优化"会适得其反——你把数据拆得更细想提高并行度,结果内存子系统成了瓶颈。
实际系统的甜蜜点
工程师的GitHub仓库里有个细节:他测试了从4KB到256MB的完整区间,步长按2的幂次分布。曲线不是单调的,在L2边界(1MB附近)和L3边界(128MB附近)都有明显拐点。
这意味着"越大越好"也有天花板。当块超过L3容量,你又开始和主存打交道,性能曲线再次下滑。
他的个人建议写在最前面:原始数据处理时,块大小至少256KB;如果有每块固定开销(比如系统调用、锁竞争),这个阈值还要往上调。
这个结论对数据库引擎、游戏资源流送、AI推理框架都有参考价值。Unity的AssetBundle、PyTorch的DataLoader、RocksDB的SSTable,底层都是同一个问题——怎么在内存局部性和调度灵活性之间找平衡。
工程师最后放了一句话在README里:"如果你的块小于64KB,别怪预取器没提醒你。"你的系统里,最小的内存块是多大?
热门跟贴