提问人:Frontier_Setter 提问时间:10/20/2023 最后编辑:Frontier_Setter 更新时间:10/24/2023 访问量:106
为什么使用非临时存储指令不能减少内存带宽使用?(写入似乎正在生成额外的读取)
Why using non-temporal store instructions cannot reduce memory bandwidth usage? (Writes seem to be generating extra reads)
问:
我想使用非临时指令来减少 memcpy 过程中写入分配产生的读取带宽。优化后的预期读写带宽应相同,均等于实际数据处理带宽。
但我在实验中发现,内存读取带宽仍然是写入带宽的 1.7 倍。
我的代码是使用内联汇编编写的,核心逻辑如下:
asm volatile(
"mov %[memarea], %%rax \n" // rax = dst arr
"mov %[srcarea], %%rcx \n" // rcx = src arr
"1: \n" // start of write loop
"movdqa 0*16(%%rcx), %%xmm0 \n"
"movdqa 1*16(%%rcx), %%xmm1 \n"
"movdqa 2*16(%%rcx), %%xmm2 \n"
"movdqa 3*16(%%rcx), %%xmm3 \n"
"movdqa 4*16(%%rcx), %%xmm4 \n"
"movdqa 5*16(%%rcx), %%xmm5 \n"
"movdqa 6*16(%%rcx), %%xmm6 \n"
"movdqa 7*16(%%rcx), %%xmm7 \n"
"PREFETCHNTA 8*16(%%rcx) \n"
"PREFETCHNTA 12*16(%%rcx) \n"
"movntdq %%xmm0, 0*16(%%rax) \n"
"movntdq %%xmm1, 1*16(%%rax) \n"
"movntdq %%xmm2, 2*16(%%rax) \n"
"movntdq %%xmm3, 3*16(%%rax) \n"
"movntdq %%xmm4, 4*16(%%rax) \n"
"movntdq %%xmm5, 5*16(%%rax) \n"
"movntdq %%xmm6, 6*16(%%rax) \n"
"movntdq %%xmm7, 7*16(%%rax) \n"
"add $8*16, %%rax \n"
"add $8*16, %%rcx \n"
// test write loop condition
"cmp %[end], %%rax \n" // compare to end iterator
"jb 1b \n"
:
: [memarea] "r" (dst), [srcarea] "r" (src), [end] "r" (dst+effect_size)
: "rax", "rcx", "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", "cc", "memory");
_mm_sfence();
在 memset 测试中,基于非临时指令的版本不会产生读取带宽,因此指令的功能确实有效。
我的使用有什么问题吗?
更新:
我用 Peter Cordes 提供的代码进行了实验
处理器: Intel(R) Xeon(R) Gold 5218 CPU with 22MiB LLC
操作系统: CentOS7 with kernel 5.14.0
编译器:GCC 4.8.5 和 GCC 11.2.1
编译选项:
gcc nt_memcpy.c -o nt_memcpy.exe -O2 -msse2 -mavx -std=gnu99
用于带宽监控的工具:
pcm-memory
结果,数组大小为 32MB 和 1GB,在循环中调用复制函数。
- 复制 32MB 数据时,读取速度为 4284.13 MB/s,写入速度为 3778.02 MB/s。(CPU 有 22 MiB 的 L3 缓存,所以这个测试大小太小,不能在这个 CPU 上做一个很好的测试,不像 Peter 的 i7-6700k 有 8 MiB,它几乎足够大。
- 复制 1GB 数据时,读取速度为 6336.48 MB/s,写入速度为 3803.00 MB/s
- 基线近乎空闲的读取速度为 100 MB/s,写入速度为 75 MB/s
此结果不符合非临时存储指令的预期行为(预期的读写带宽应接近)。
它也与彼得·科德斯(Peter Cordes)获得的结果不同 (详见注释)
我无法在配备 DDR4-2666 的 i7-6700k 上重现您的结果。读取 ~= intel_gpu_top监控的写入带宽,以从集成内存控制器获取统计信息。(读取速度约为 13400 MiB/s,写入速度约为 13900 MiB/s,而基线近乎空闲的读取速度为 1200 MiB/s,写入速度为 8 到 16 MiB/s。
将数组大小提高到 1GiB,我的 IMC 读取带宽只是空闲 + 写入带宽,因此没有多余的读取。
实验中的额外内存读取带宽可能来自哪里?
以下是 perf 收集的 pmc,未观察到重大的 rfo 事件。看来nt-store已经生效了。
# taskset -c 1 ./perf5 stat --all-user -e task-clock,context-switches,cpu-migrations,page-faults,cycles,instructions,uops_issued.any,idq.mite_uops,offcore_requests.demand_rfo,l2_rqsts.all_rfo -- ./nt_memcpy.exe
Performance counter stats for './nt_memcpy.exe':
88,537.13 msec task-clock # 1.000 CPUs utilized
0 context-switches # 0.000 /sec
0 cpu-migrations # 0.000 /sec
278,571 page-faults # 3.146 K/sec
239,435,668,714 cycles # 2.704 GHz (93.56%)
55,340,543,407 instructions # 0.23 insn per cycle (94.51%)
53,026,607,051 uops_issued.any # 598.919 M/sec (93.92%)
15,151,551,517 idq.mite_uops # 171.132 M/sec (93.56%)
168 offcore_requests.demand_rfo # 1.898 /sec (93.75%)
994 l2_rqsts.all_rfo # 11.227 /sec (92.61%)
88.541352219 seconds time elapsed
85.776548000 seconds user
2.557275000 seconds sys
更新:
我使用客户端 CPU 进行了一个实验:
- 处理器: Intel(R) Core(TM) i7-10700 CPU with 16MiB LLC
- 操作系统: Ubunt with kernel 5.15.0
- 编译器: gcc 9.4.0
- 编译选项:
gcc nt_memcpy.c -o nt_memcpy.exe -O2 -msse2 -mavx -std=gnu99
- 用于带宽监控的工具:
intel-gpu-top
结果,数组大小为 1GB,在循环中调用复制函数。
- 复制 1GB 数据时,读取速度为 14260 MiB/s,写入速度为 14133 MiB/s
- 基线近乎空闲的读取速度为 48 MB/s,写入速度为 4 MB/s
这一结果符合预期,也符合Peter获得的数据。
是服务器和客户端 CPU 的区别,还是 pcm-memory
和 intel-gpu-top
工具的区别?我无法在服务器上使用 intel-gpu-top
(因为我没有显卡),也无法在客户端 CPU 上使用 pcm-memory
(不支持架构),因此我无法相互验证。
答: 暂无答案
评论
intel_gpu_top
alignas(128) char srcbuf[1024*1024*32] = {1};
alignas(128) char dstbuf[1024*1024*32];
.data
Memory bw(GB/s) = (UNC_M_CAS_COUNT.RD +UNC_M_CAS_COUNT.WR) * 64 / 1000000000