St.*_*rio 4 c performance benchmarking assembly x86-64
Whiskey Lake 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);
Run Code Online (Sandbox Code Playgroud)
定义:
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
Run Code Online (Sandbox Code Playgroud)
基准代码:
#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);
}
}
Run Code Online (Sandbox Code Playgroud)
结果:
avx_memcpy_forward_llss中位数:44479368 个核心周期
UPD:时间
real 0m0,217s
user 0m0,093s
sys 0m0,124s
Run Code Online (Sandbox Code Playgroud)
avx_nt_memcpy_forward_llss中位数:24053086 个核心周期
UPD:时间
real 0m0,184s
user 0m0,056s
sys 0m0,128s
Run Code Online (Sandbox Code Playgroud)
UPD:结果是在运行基准测试时得到的 taskset -c 1 ./bin
所以我在内存复制例程实现之间的核心周期上几乎有 2 倍的差异。我将其解释为在常规存储到 WB 内存的情况下,我们有 RFO 请求在总线带宽上竞争,因为它在 IOM/3.6.12 中指定(强调我的):
尽管由于 非临时存储导致的完整 64 字节总线写入的数据带宽是总线写入到 WB 内存的数据带宽的两倍,但传输 8 字节块会浪费总线请求带宽并提供显着较低的数据带宽。
QUESTION 1:单发情况下如何做基准分析?由于性能启动开销和预热迭代开销,性能计数器似乎没有用。
问题 2:这样的基准测试是否正确。我cpuid一开始就考虑了,以便开始使用干净的 CPU 资源进行测量,以避免由于先前的指令在运行中造成的停顿。我添加了内存破坏作为编译屏障并lfence避免rdpmc被执行 OoO。
只要有可能,基准测试应该以允许尽可能多的“健全性检查”的方式报告结果。在这种情况下,启用此类检查的几种方法包括:
其他重要的“最佳实践”原则:
如果我假设您的处理器在测试期间以 4.6 GHz 的最大 Turbo 频率运行,那么报告的周期计数分别对应于 9.67 毫秒和 5.23 毫秒。将这些插入“健全性检查”显示:
这些“健全性检查”的失败告诉我们频率不可能高达 4.6 GHz(并且可能不高于 3.0 GHz),但主要只是指出需要明确地测量经过的时间......
您在优化手册中关于流存储效率低下的引用仅适用于无法合并为完整缓存行传输的情况。您的代码按照“最佳实践”建议存储到输出缓存行的每个元素(写入同一行的所有存储指令都连续执行,并且每个循环仅生成一个存储流)。不可能完全阻止硬件破坏流媒体商店,但在您的情况下,这种情况应该非常罕见——也许是百万分之几。检测部分流存储是一个非常高级的主题,需要在“非核心”和/或通过查找升高的 DRAM CAS 计数(这可能是由于其他原因)间接检测部分流存储中使用记录不佳的性能计数器。http://sites.utexas.edu/jdm4372/2018/01/01/notes-on-non-temporal-aka-streaming-stores/