威士忌湖 i7-8565U
我正在尝试学习如何通过手动编写基准测试(而不使用任何基准测试框架)以内存复制例程为例进行常规和非临时写入到WB内存,并希望能够获得一些评论。
声明:
void *avx_memcpy_forward_llss(void *restrict, const void *restrict, size_t);
void *avx_nt_memcpy_forward_llss(void *restrict, const void *restrict, size_t);
定义:
avx_memcpy_forward_llss:
shr rdx, 0x3
xor rcx, rcx
avx_memcpy_forward_loop_llss:
vmovdqa ymm0, [rsi + 8*rcx]
vmovdqa ymm1, [rsi + 8*rcx + 0x20]
vmovdqa [rdi + rcx*8], ymm0
vmovdqa [rdi + rcx*8 + 0x20], ymm1
add rcx, 0x08
cmp rdx, rcx
ja avx_memcpy_forward_loop_llss
ret
avx_nt_memcpy_forward_llss:
shr rdx, 0x3
xor rcx, rcx
avx_nt_memcpy_forward_loop_llss:
vmovdqa ymm0, [rsi + 8*rcx]
vmovdqa ymm1, [rsi + 8*rcx + 0x20]
vmovntdq [rdi + rcx*8], ymm0
vmovntdq [rdi + rcx*8 + 0x20], ymm1
add rcx, 0x08
cmp rdx, rcx
ja avx_nt_memcpy_forward_loop_llss
ret
基准测试代码:
#include <stdio.h>
#include <inttypes.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <immintrin.h>
#include <x86intrin.h>
#include "memcopy.h"
#define BUF_SIZE 128 * 1024 * 1024
_Alignas(64) char src[BUF_SIZE];
_Alignas(64) char dest[BUF_SIZE];
static inline void warmup(unsigned wa_iterations, void *(*copy_fn)(void *, const void *, size_t));
static inline void cache_flush(char *buf, size_t size);
static inline void generate_data(char *buf, size_t size);
uint64_t run_benchmark(unsigned wa_iteration, void *(*copy_fn)(void *, const void *, size_t)){
generate_data(src, sizeof src);
warmup(4, copy_fn);
cache_flush(src, sizeof src);
cache_flush(dest, sizeof dest);
__asm__ __volatile__("mov $0, %%rax\n cpuid":::"rax", "rbx", "rcx", "rdx", "memory");
uint64_t cycles_start = __rdpmc((1 << 30) + 1);
copy_fn(dest, src, sizeof src);
__asm__ __volatile__("lfence" ::: "memory");
uint64_t cycles_end = __rdpmc((1 << 30) + 1);
return cycles_end - cycles_start;
}
int main(void){
uint64_t single_shot_result = run_benchmark(1024, avx_memcpy_forward_llss);
printf("Core clock cycles = %" PRIu64 "\n", single_shot_result);
}
static inline void warmup(unsigned wa_iterations, void *(*copy_fn)(void *, const void *, size_t)){
while(wa_iterations --> 0){
copy_fn(dest, src, sizeof src);
copy_fn(dest, src, sizeof src);
copy_fn(dest, src, sizeof src);
copy_fn(dest, src, sizeof src);
copy_fn(dest, src, sizeof src);
copy_fn(dest, src, sizeof src);
copy_fn(dest, src, sizeof src);
copy_fn(dest, src, sizeof src);
}
}
static inline void generate_data(char *buf, size_t sz){
int fd = open("/dev/urandom", O_RDONLY);
read(fd, buf, sz);
}
static inline void cache_flush(char *buf, size_t sz){
for(size_t i = 0; i < sz; i+=_SC_LEVEL1_DCACHE_LINESIZE){
_mm_clflush(buf + i);
}
}
结果:
avx_memcpy_forward_llss
中位数:44479368核心周期
更新:时间
real 0m0,217s
user 0m0,093s
sys 0m0,124s
avx_nt_memcpy_forward_llss
中位数:24053086个核心周期
更新:时间
real 0m0,184s
user 0m0,056s
sys 0m0,128s
更新: 使用taskset -c 1 ./bin
运行基准测试后得到了结果。
因此,在内存复制例程实现中,我在核心周期方面获得了近乎两倍的差异。 我将其解释为在常规存储器写入WB内存的情况下,我们有RFO请求竞争总线带宽,如IOM / 3.6.12中所指定的那样(强调属于我):
尽管由于非暂态存储器的64字节总线完整写入的数据带宽是WB内存的两倍,但传输8字节块会浪费总线请求带宽并提供显着较低的数据带宽。
问题1:如何进行单次拍摄的基准分析?由于启动性能和预热迭代开销,perf计数器似乎不太有用。
问题2:这样的基准测试是否正确?我在开始时考虑了cpuid
,以便使用干净的CPU资源开始测量,以避免由于先前指令在执行中而造成停顿。 我添加了内存清除作为编译屏障,并添加了lfence
以避免rdpmc
乱序执行。
copy_fn
的调用?该函数使您能够消除写入“dest”时将发生的页面故障的开销,但我不确定为什么需要多次调用copy_fn
。另外,lfence
不能确保所有先前的存储已成为全局可观察到的。您可以使用mfence
代替。尽管在您的处理器上,稍后从WC内存加载可能会通过mfence
(与lfence
相反)。此外,我认为您需要在第一个__rdpmc
之后加上编译器屏障。 - Hadi Braislfence
,以便在执行 rdpmc 后进行序列化,但是 软件必须在 RDPMC 指令之前和/或之后插入一个序列化指令(例如 CPUID 指令) 因此lfence
看起来不足够。此外,我认为您需要在第一个__rdpmc
之后立即放置编译器屏障。 好主意。为什么在 warmup 中有这么多 copy_fn 的调用? 我认为这是为了预热操作码高速缓存而需要的。但是调用量是随机选择的。 - St.Antario