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);
Определение:
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 циклов ядра
UPD: время
real 0m0,217s
user 0m0,093s
sys 0m0,124s
avx_nt_memcpy_forward_llss
медиана : 24053086 циклов ядра
UPD: время
real 0m0,184s
user 0m0,056s
sys 0m0,128s
UPD: результат был получен при выполнении теста с taskset -c 1 ./bin
Таким образом, я получил почти почти в 2 раза разницу в основных циклах между реализация подпрограммы копирования памяти. Я интерпретирую это так, как в случае обычных хранилищ в памяти WB у нас есть запросы RFO, конкурирующие по пропускной способности шины, как это указано в IOM / 3.6.12 (подчеркните мой):
Хотя данные Пропускная способность полной записи 64-байтовой шины из-за невременных хранилищ в два раза превышает пропускную способность записи шины в WB-память , при передаче 8-байтовых кусков происходит потеря пропускной способности запроса шины и обеспечивается значительно меньшая пропускная способность данных.
ВОПРОС 1: Как провести анализ производительности в случае одиночного выстрела? Счетчики производительности не кажутся полезными из-за накладных расходов при запуске перфорирования и итераций при прогреве.
ВОПРОС 2: Является ли такой тест верным. Сначала я учел cpuid
, чтобы начать измерения с чистыми ресурсами ЦП, чтобы избежать сбоев из-за предыдущих инструкций в полете. Я добавил клобберы памяти как барьер компиляции и lfence
, чтобы избежать выполнения rdpmc
OoO.