Почему memcpy использует prefetcht0 из 4 последовательных строк кэша? - PullRequest
3 голосов
/ 14 января 2020

Я анализирую реализацию memcpy на моей машине. Kaby Lake i7 снижается до __memcpy_avx_unaligned_erms. Значение __x86_shared_non_temporal_threshold, которое используется для определения необходимости использования невременных хранилищ AVX, составляет 6 МБ на моей машине. Фрагмент кода для не временных значений таков:

0x000000000018ee21 <+849>: prefetcht0 -0x100(%rcx)
prefetcht0 -0x140(%rcx)
prefetcht0 -0x180(%rcx)
prefetcht0 -0x1c0(%rcx)
vmovdqu (%rcx),%ymm0
vmovdqu -0x20(%rcx),%ymm1
vmovdqu -0x40(%rcx),%ymm2
vmovdqu -0x60(%rcx),%ymm3
sub    $0x80,%rcx
sub    $0x80,%rdx
vmovntdq %ymm0,(%r9)
vmovntdq %ymm1,-0x20(%r9)
vmovntdq %ymm2,-0x40(%r9)
vmovntdq %ymm3,-0x60(%r9)
sub    r9,0x80
cmp    rdx,0x80
ja     0x18ee21 <__memmove_avx_unaligned_erms+849>

ВОПРОС: Действительно ли prefetcht0 действительно полезен? У нас уже есть предварительная выборка HW, поэтому отсутствие предварительной выборки SW не должно вызывать большой разницы.


Я написал свою собственную функцию копирования для измерения разницы производительности с prefetcht0 и без него. Для простоты я предположил, что источник и назначение выровнены по страницам, а их размер кратен размеру страницы (это, скорее всего, будет справедливо для ввода-вывода).

Существует 2 реализации:

I. avx_copy_unr15 без предварительной выборки ПО

Декларация:

void *avx_copy_unr15(void *restrict dest, const void *restrict src, size_t sz);

Реализация:

//rdi - dest
//rsi - src
//rdx - size
avx_copy_unr15:
cmp $0, %rdx
jz avx_copy_unr15_ret
avx_copy_unr15_loop:
vmovapd (%rsi), %ymm0
vmovapd 0x20(%rsi), %ymm1
vmovapd 0x40(%rsi), %ymm2
vmovapd 0x60(%rsi), %ymm3
vmovapd 0x80(%rsi), %ymm4
vmovapd 0xA0(%rsi), %ymm5
vmovapd 0xC0(%rsi), %ymm6
vmovapd 0xE0(%rsi), %ymm7
vmovapd 0x100(%rsi), %ymm8
vmovapd 0x120(%rsi), %ymm9
vmovapd 0x140(%rsi), %ymm10
vmovapd 0x160(%rsi), %ymm11
vmovapd 0x180(%rsi), %ymm12
vmovapd 0x1A0(%rsi), %ymm13
vmovapd 0x1C0(%rsi), %ymm14
vmovapd 0x1E0(%rsi), %ymm15
vmovntpd %ymm0, (%rdi)
vmovntpd %ymm1, 0x20(%rdi)
vmovntpd %ymm2, 0x40(%rdi)
vmovntpd %ymm3, 0x60(%rdi)
vmovntpd %ymm4, 0x80(%rdi)
vmovntpd %ymm5, 0xA0(%rdi)
vmovntpd %ymm6, 0xC0(%rdi)
vmovntpd %ymm7, 0xE0(%rdi)
vmovntpd %ymm8, 0x100(%rdi)
vmovntpd %ymm9, 0x120(%rdi)
vmovntpd %ymm10, 0x140(%rdi)
vmovntpd %ymm11, 0x160(%rdi)
vmovntpd %ymm12, 0x180(%rdi)
vmovntpd %ymm13, 0x1A0(%rdi)
vmovntpd %ymm14, 0x1C0(%rdi)
vmovntpd %ymm15, 0x1E0(%rdi)
add $0x200, %rsi
add $0x200, %rdi
sub $0x200, %rdx
jnz avx_copy_unr15_loop
avx_copy_unr15_ret:
vzeroupper
ret 

II. avx_copy_unr15_sw_prefetch с предварительной выборкой ПО

Объявление:

void *avx_copy_unr15_sw_prefetch(void *restrict dest, const void *restrict src, size_t sz);

Реализация (согласно комментарию @Jester я изменил prefetcht0 для следующей итерации):

//rdi - dest
//rsi - src
//rdx - size
avx_copy_unr15_sw_prefetch:
cmp $0, %rdx
jz avx_copy_unr15_ret
avx_copy_unr15_loop:
prefetcht0 0x200(%rsi)
prefetcht0 0x240(%rsi)
prefetcht0 0x280(%rsi)
prefetcht0 0x2C0(%rsi)
prefetcht0 0x300(%rsi)
prefetcht0 0x340(%rsi)
prefetcht0 0x380(%rsi)
prefetcht0 0x3C0(%rsi)
vmovapd (%rsi), %ymm0
vmovapd 0x20(%rsi), %ymm1
vmovapd 0x40(%rsi), %ymm2
vmovapd 0x60(%rsi), %ymm3
vmovapd 0x80(%rsi), %ymm4
vmovapd 0xA0(%rsi), %ymm5
vmovapd 0xC0(%rsi), %ymm6
vmovapd 0xE0(%rsi), %ymm7
vmovapd 0x100(%rsi), %ymm8
vmovapd 0x120(%rsi), %ymm9
vmovapd 0x140(%rsi), %ymm10
vmovapd 0x160(%rsi), %ymm11
vmovapd 0x180(%rsi), %ymm12
vmovapd 0x1A0(%rsi), %ymm13
vmovapd 0x1C0(%rsi), %ymm14
vmovapd 0x1E0(%rsi), %ymm15
vmovntpd %ymm0, (%rdi)
vmovntpd %ymm1, 0x20(%rdi)
vmovntpd %ymm2, 0x40(%rdi)
vmovntpd %ymm3, 0x60(%rdi)
vmovntpd %ymm4, 0x80(%rdi)
vmovntpd %ymm5, 0xA0(%rdi)
vmovntpd %ymm6, 0xC0(%rdi)
vmovntpd %ymm7, 0xE0(%rdi)
vmovntpd %ymm8, 0x100(%rdi)
vmovntpd %ymm9, 0x120(%rdi)
vmovntpd %ymm10, 0x140(%rdi)
vmovntpd %ymm11, 0x160(%rdi)
vmovntpd %ymm12, 0x180(%rdi)
vmovntpd %ymm13, 0x1A0(%rdi)
vmovntpd %ymm14, 0x1C0(%rdi)
vmovntpd %ymm15, 0x1E0(%rdi)
add $0x200, %rsi
add $0x200, %rdi
sub $0x200, %rdx
jnz avx_copy_unr15_loop
avx_copy_unr15_ret:
vzeroupper
ret 

Я измерил это, используя __rdtsc() intrinsi c, доступный в x86intrin.h, и примерно за 10 прогонов измерений я сделал около 20000 итераций и заметил, что предварительная выборка SW происходит примерно на 1 - 1.5% быстрее.

Ошибка измерения или предварительная выборка ПО действительно помогает? Вот как выглядит одна итерация моего измерения:

#define DO_MEASURE(fn, iterations_total, ...) \
    __extension__({\
        uint64_t total_tsc = 0; \
        unsigned total = iterations_total; \
        while(total --> 0) { \
            uint64_t start = __rdtsc(); \
            fn(__VA_ARGS__); \
            fn(__VA_ARGS__); \
            fn(__VA_ARGS__); \
            fn(__VA_ARGS__); \
            fn(__VA_ARGS__); \
            fn(__VA_ARGS__); \
            fn(__VA_ARGS__); \
            fn(__VA_ARGS__); \
            uint64_t finish = __rdtsc(); \
            uint64_t iteration_result = (finish - start) / 8; \
            total_tsc += iteration_result; \
        }; \
        total_tsc / iterations_total; \
    }) 

Вот как выглядит весь исходный файл для измерения:

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

#include "memcp.h" //header with asm("") implementation
#include "benchmark.h" //header with DO_MEASURE macro shown above

#define BUF_SIZE 4096 * 1024
#define ITERATIONS 2000

_Alignas(4096) char src[BUF_SIZE];
_Alignas(4096) char dest[BUF_SIZE];

int main(void){
    int fd = open("/dev/urandom", O_RDONLY);
    read(fd, src, BUF_SIZE);

    unsigned avx_unr15_iterations_total = 10;
    unsigned avx_unr15_iteration = 0;
    while(avx_unr15_iteration++ < avx_unr15_iterations_total){
        unsigned long result = DO_MEASURE(avx_copy_unr15, ITERATIONS, dest, src, BUF_SIZE);
        printf("avx_copy_unr15: iteration %u = %lu\n", avx_unr15_iteration, result);
    }

    unsigned avx_unr15_sw_prefetch_iterations_total = 10;
    unsigned avx_unr15_sw_prefetch_iteration = 0;
    while(avx_unr15_sw_prefetch_iteration++ < avx_unr15_sw_prefetch_iterations_total){
        unsigned long result = DO_MEASURE(avx_copy_unr15_sw_prefetch, ITERATIONS, dest, src, BUF_SIZE);
        printf("avx_copy_sw_prefetch_unr15: iteration %u = %lu\n", avx_unr15_sw_prefetch_iteration, result);
    }
}

Компиляция с помощью -Werror -Wextra -pedantic -Wall -g3 -O3 -Wno-unused-result Результат выполнения таких тестов таков:

avx_copy_unr15: iteration 1 = 296576
avx_copy_unr15: iteration 2 = 289416
avx_copy_unr15: iteration 3 = 287765
avx_copy_unr15: iteration 4 = 287584
avx_copy_unr15: iteration 5 = 287525
avx_copy_unr15: iteration 6 = 290479
avx_copy_unr15: iteration 7 = 286566
avx_copy_unr15: iteration 8 = 287082
avx_copy_unr15: iteration 9 = 287584
avx_copy_unr15: iteration 10 = 288825

avx_copy_sw_prefetch_unr15: iteration 1 = 288732
avx_copy_sw_prefetch_unr15: iteration 2 = 287374
avx_copy_sw_prefetch_unr15: iteration 3 = 286325
avx_copy_sw_prefetch_unr15: iteration 4 = 287374
avx_copy_sw_prefetch_unr15: iteration 5 = 297077
avx_copy_sw_prefetch_unr15: iteration 6 = 323929
avx_copy_sw_prefetch_unr15: iteration 7 = 321866
avx_copy_sw_prefetch_unr15: iteration 8 = 321503
avx_copy_sw_prefetch_unr15: iteration 9 = 321280
avx_copy_sw_prefetch_unr15: iteration 10 = 329869

Взятие perf record на версию предварительной выборки SW показывает следующие результаты:

 18,19 │  0:   prefetcht0 0x200(%rsi)                                                                                                                                                                              
  9,89 │       prefetcht0 0x240(%rsi)                                                                                                                                                                              
  8,85 │       prefetcht0 0x280(%rsi)                                                                                                                                                                              
  7,95 │       prefetcht0 0x2c0(%rsi)                                                                                                                                                                              
  7,02 │       prefetcht0 0x300(%rsi)                                                                                                                                                                              
  6,20 │       prefetcht0 0x340(%rsi)                                                                                                                                                                              
  5,58 │       prefetcht0 0x380(%rsi)                                                                                                                                                                              
  4,99 │       prefetcht0 0x3c0(%rsi)                                                                                                                                                                              
  1,42 │       vmovapd (%rsi),%ymm0                                                                                                                                                                                
  0,42 │       vmovapd 0x20(%rsi),%ymm1                                                                                                                                                                            
  1,44 │       vmovapd 0x40(%rsi),%ymm2                                                                                                                                                                            
  0,60 │       vmovapd 0x60(%rsi),%ymm3                                                                                                                                                                            
  1,22 │       vmovapd 0x80(%rsi),%ymm4                                                                                                                                                                            
  0,42 │       vmovapd 0xa0(%rsi),%ymm5                                                                                                                                                                            
  1,40 │       vmovapd 0xc0(%rsi),%ymm6                                                                                                                                                                            
  0,63 │       vmovapd 0xe0(%rsi),%ymm7                                                                                                                                                                            
  1,42 │       vmovapd 0x100(%rsi),%ymm8                                                                                                                                                                           
  0,47 │       vmovapd 0x120(%rsi),%ymm9                                                                                                                                                                           
  1,43 │       vmovapd 0x140(%rsi),%ymm10                                                                                                                                                                          
  0,61 │       vmovapd 0x160(%rsi),%ymm11                                                                                                                                                                          
  1,25 │       vmovapd 0x180(%rsi),%ymm12                                                                                                                                                                          
  0,47 │       vmovapd 0x1a0(%rsi),%ymm13                                                                                                                                                                          
  1,42 │       vmovapd 0x1c0(%rsi),%ymm14                                                                                                                                                                          
  0,67 │       vmovapd 0x1e0(%rsi),%ymm15                                                                                                                                                                          
  2,40 │       vmovntpd %ymm0,(%rdi)                                                                                                                                                                               
  0,95 │       vmovntpd %ymm1,0x20(%rdi)                                                                                                                                                                           
  0,79 │       vmovntpd %ymm2,0x40(%rdi)                                                                                                                                                                           
  0,90 │       vmovntpd %ymm3,0x60(%rdi)                                                                                                                                                                           
  0,77 │       vmovntpd %ymm4,0x80(%rdi)                                                                                                                                                                           
  0,85 │       vmovntpd %ymm5,0xa0(%rdi)                                                                                                                                                                           
  0,92 │       vmovntpd %ymm6,0xc0(%rdi)                                                                                                                                                                           
  1,00 │       vmovntpd %ymm7,0xe0(%rdi)                                                                                                                                                                           
  0,78 │       vmovntpd %ymm8,0x100(%rdi)                                                                                                                                                                          
  0,86 │       vmovntpd %ymm9,0x120(%rdi)                                                                                                                                                                          
  0,90 │       vmovntpd %ymm10,0x140(%rdi)                                                                                                                                                                         
  1,09 │       vmovntpd %ymm11,0x160(%rdi)                                                                                                                                                                         
  0,84 │       vmovntpd %ymm12,0x180(%rdi)                                                                                                                                                                         
  0,74 │       vmovntpd %ymm13,0x1a0(%rdi)                                                                                                                                                                         
  0,64 │       vmovntpd %ymm14,0x1c0(%rdi)                                                                                                                                                                         
  0,70 │       vmovntpd %ymm15,0x1e0(%rdi)      
...