DRAM读写带宽差异远超预期,测量方法是否合理?差异正常吗?
我希望测量DRAM的读写带宽,采用的测量方法为:反复扫描一个128MB数组,仅访问每个缓存行的前4字节;读取操作使用add eax/ebx, [mem]指令,写入操作使用mov [mem], eax指令。
基于缓存写回策略,我预期写入带宽应为读取带宽的1/2,但实际测量结果显示写入带宽仅为读取带宽的1/20(读取耗时113.529ms,写入耗时2254.57ms)。
代码实现
asm (R"( .bss .align 64 A: .zero 128 * 1024 * 1024 .text )"); void read() { asm volatile (R"( .intel_syntax noprefix lea rdi, A[rip] lea rsi, [rdi + 128 * 1024 * 1024] .align 64 1: add eax, [rdi + 64 * 0] add ebx, [rdi + 64 * 1] add ecx, [rdi + 64 * 2] add edx, [rdi + 64 * 3] add eax, [rdi + 64 * 4] add ebx, [rdi + 64 * 5] add ecx, [rdi + 64 * 6] add edx, [rdi + 64 * 7] add rdi, 64 * 8 cmp rdi, rsi jne 1b .att_syntax )"::: "rdi", "rsi", "eax", "ebx", "ecx", "edx" ); } void write() { asm volatile (R"( .intel_syntax noprefix lea rdi, A[rip] lea rsi, [rdi + 128 * 1024 * 1024] .align 64 1: mov [rdi + 64 * 0], al mov [rdi + 64 * 1], eax mov [rdi + 64 * 2], eax mov [rdi + 64 * 3], eax mov [rdi + 64 * 4], eax mov [rdi + 64 * 5], eax mov [rdi + 64 * 6], eax mov [rdi + 64 * 7], eax add rdi, 64 * 8 cmp rdi, rsi jne 1b .att_syntax )"::: "rdi", "rsi" ); } #include <chrono> #include <iostream> using namespace std; void measure(const char* name, void (*f)()) { for (int i = 0; i < 20; i++) f(); auto start = chrono::steady_clock::now(); for (int i = 0; i < 200; i++) f(); auto end = chrono::steady_clock::now(); cout << name << ": " << (end - start).count() * 1e-6 << " ms" << endl; } int main() { measure("reading", read); measure("writing", write); }
实际输出
reading: 113.529 ms writing: 2254.57 ms
现咨询:我的测量方法是否存在问题?DRAM的读写带宽差异真的会达到如此大的程度吗?
测量方法的核心问题
你的测量代码存在两个关键问题,直接导致了读写带宽的巨大差异:
1. 写入操作的指令效率极低
在write()函数的汇编代码中,第一条写入指令是mov [rdi + 64 * 0], al——这是一个单字节写入。现代CPU对窄宽度内存写入有额外开销:
- 单字节写入需要CPU先读取整个缓存行(缓存行是最小内存操作单位),修改其中1字节后标记为脏行等待写回。
- 更关键的是,窄写入会破坏CPU的**写合并(Write Combining)**机制。写合并允许CPU将多个小写入合并成一个总线事务,大幅提升写入效率,但单字节写入通常无法触发该机制,迫使每个写入单独发起总线请求,极大增加延迟。
而读取操作使用32位读取(add eax, [mem]),能高效利用缓存和总线带宽,指令吞吐量更高。
2. 读写循环的指令吞吐量不匹配
读取循环中,8条add指令可被CPU乱序执行,充分利用流水线和内存并行访问能力。而写入循环中,第一条单字节写入的延迟会拖慢整个循环节奏,加上写合并失效,每条写入指令的实际执行周期远高于读取指令,导致整体耗时剧增。
DRAM本身的读写带宽差异
正常情况下,DRAM写入带宽确实会比读取带宽低,但不会达到1/20的程度。写回缓存策略下,读取缓存不命中直接加载缓存行;写入缓存不命中会先加载缓存行(写分配),修改后标记为脏,后续批量写回DRAM。通常写入带宽是读取带宽的70%-90%,极端情况也不会低于50%。
修复建议
要得到准确的DRAM读写带宽测量结果,需修改写入代码:
- 把所有写入操作改成相同宽度的32位或64位写入,比如全部用
mov [rdi + 64 * n], eax,或更高效的64位写入mov [rdi + 64 * n], rax。 - 确保写入操作能触发写合并,避免窄宽度写入。
- 保持读写循环的指令结构一致,让两者的指令吞吐量和内存访问模式尽量匹配,对比结果才具备参考性。
修改后的写入循环示例:
1: mov [rdi + 64 * 0], eax mov [rdi + 64 * 1], eax mov [rdi + 64 * 2], eax mov [rdi + 64 * 3], eax mov [rdi + 64 * 4], eax mov [rdi + 64 * 5], eax mov [rdi + 64 * 6], eax mov [rdi + 64 * 7], eax add rdi, 64 * 8 cmp rdi, rsi jne 1b
修改后,写入带宽应该会接近读取带宽的50%-90%,符合缓存写回策略下的预期表现。
内容的提问来源于stack exchange,提问作者platelet

