Получение максимальной пропускной способности на Haswell в кеше L1: только 62%
Я пытаюсь получить полную пропускную способность в кэше L1 для следующей функции на процессорах Intel
float triad(float *x, float *y, float *z, const int n) {
float k = 3.14159f;
for(int i=0; i<n; i++) {
z[i] = x[i] + k*y[i];
}
}
Это функция триады от STREAM.
Я получаю около 95% пика с процессорами SandyBridge/IvyBridge с этой функцией (используя сборку с NASM). Однако, используя Haswell, я достигаю только 62% пика, если не разверну петлю. Если я развернусь 16 раз, я получу 92%. Я не понимаю этого.
Я решил написать свою функцию в сборке, используя NASM. Основной цикл в сборке выглядит следующим образом.
.L2:
vmovaps ymm1, [rdi+rax]
vfmadd231ps ymm1, ymm2, [rsi+rax]
vmovaps [rdx+rax], ymm1
add rax, 32
jne .L2
Оказывается, в руководстве по оптимизации сборки Agner Fog в примерах 12.7-12.11 он делает почти то же самое (но для y[i] = y[i] +k*x[i]
) для Pentium M, Core 2, Sandy Bridge, FMA4 и FMA3. Мне удалось воспроизвести его код более или менее самостоятельно (на самом деле у него есть небольшая ошибка в примере FMA3, когда он вещает). Он дает счетчик размеров команд, объединенные операции, порты выполнения в таблицах для каждого процессора, кроме FMA4 и FMA3. Я попытался сделать эту таблицу самостоятельно для FMA3.
ports
size μops-fused 0 1 2 3 4 5 6 7
vmovaps 5 1 ½ ½
vfmadd231ps 6 1 ½ ½ ½ ½
vmovaps 5 1 1 1
add 4 ½ ½
jne 2 ½ ½
--------------------------------------------------------------
total 22 4 ½ ½ 1 1 1 0 1 1
Размер относится к длине инструкции в байтах. Причина add
а также jne
инструкции имеют половину µop, если они объединены в одну макрооперацию (не путать с µop fusion, которая все еще использует несколько портов) и им нужен только порт 6 и один µop. vfmadd231ps
Инструкция может использовать порт 0 или порт 1. Я выбрал порт 0. Загрузка vmovaps
можно использовать порт 2 или 3. Я выбрал 2 и имел vfmadd231ps
используйте порт 3.. Чтобы соответствовать таблицам Агнера Фога, и, поскольку я думаю, что имеет больше смысла говорить, что инструкция, которая может идти на разные порты, одинаково идет в каждую половину времени, я назначил 1/2 для портов vmovaps
а также vmadd231ps
можно пойти в.
Исходя из этой таблицы и того факта, что все процессоры Core2 могут делать четыре микропроцессора в каждом тактовом цикле, кажется, что этот цикл должен быть возможен в каждом тактовом цикле, но мне не удалось его получить. Может кто-нибудь объяснить мне, почему я не могу приблизиться к пиковой пропускной способности для этой функции на Haswell без развертывания? Возможно ли это без развертывания и если да, то как это можно сделать? Позвольте мне прояснить, что я действительно пытаюсь максимизировать ILP для этой функции (мне нужна не только максимальная пропускная способность), поэтому я не хочу развертываться.
Изменить: Вот обновление, так как Iwillnotexist Idonotexist показал с помощью IACA, что магазины никогда не используют порт 7. Мне удалось преодолеть 66% барьер без развертывания и сделать это за один такт за каждую итерацию без развертывания (теоретически). Давайте сначала решим проблему магазина.
В комментарии Стивена Кэнона упоминается, что блок генерации адресов (AGU) в порту 7 может выполнять только простые операции, такие как [base + offset]
и не [base + index]
, В справочном руководстве по оптимизации Intel единственное, что я нашел, - это комментарий к порту7, в котором написано "Simple_AGU" без определения того, что означает простой. Но затем Iwillnotexist Idonotexist обнаружил в комментариях IACA, что эта проблема уже упоминалась шесть месяцев назад, в которой сотрудник Intel написал 03/11/2014:
Port7 AGU может работать только в хранилищах с простым адресом памяти (без индексного регистра).
Стивен Канон предлагает "использовать адрес магазина в качестве смещения для операндов загрузки". Я пробовал это так
vmovaps ymm1, [rdi + r9 + 32*i]
vfmadd231ps ymm1, ymm2, [rsi + r9 + 32*i]
vmovaps [r9 + 32*i], ymm1
add r9, 32*unroll
cmp r9, rcx
jne .L2
Это действительно заставляет магазин использовать port 7. Тем не менее, у него есть еще одна проблема, которая заключается в том, что vmadd231ps
не сливается с нагрузкой, которую вы можете видеть из IACA. Это также необходимо дополнительно cmp
инструкция, которую не выполняла моя первоначальная функция. Таким образом, магазин использует одну микрооперацию меньше, но cmp
(точнее add
так как cmp
макрос сливается с jne
) нужен еще один. IACA сообщает о пропускной способности блока 1,5. На практике это только около 57% от пика.
Но я нашел способ получить vmadd231ps
Инструкция по слиянию с нагрузкой. Это может быть сделано только с использованием статических массивов с адресацией [абсолютный 32-битный адрес + индекс], как это. Евгений Клюев оригинал предложил это.
vmovaps ymm1, [src1_end + rax]
vfmadd231ps ymm1, ymm2, [src2_end + rax]
vmovaps [dst_end + rax], ymm1
add rax, 32
jl .L2
куда src1_end
, src2_end
, а также dst_end
являются конечными адресами статических массивов.
Это воспроизводит таблицу в моем вопросе с четырьмя слитными микрооперациями, которые я ожидал. Если вы поместите это в IACA, он сообщит, что пропускная способность блока равна 1,0. Теоретически это должно происходить так же, как версии SSE и AVX. На практике это составляет около 72% от пика. Это преодолевает 66% барьер, но это все еще далеко от 92%, которые я разворачиваю 16 раз. Так что на Haswell единственная возможность приблизиться к вершине - развернуться. Это не обязательно на Core2 через Ivy Bridge, но на Haswell.
End_edit:
Вот код C/C++ Linux, чтобы проверить это. Код NASM размещается после кода C / C++. Единственное, что вам нужно изменить, это номер частоты. В соответствии double frequency = 1.3;
замените 1.3 на какую-либо рабочую (не номинальную) частоту ваших процессоров (которая в случае i5-4250U с отключенным турбонаддувом в BIOS составляет 1,3 ГГц).
Компилировать с
nasm -f elf64 triad_sse_asm.asm
nasm -f elf64 triad_avx_asm.asm
nasm -f elf64 triad_fma_asm.asm
g++ -m64 -lrt -O3 -mfma tests.cpp triad_fma_asm.o -o tests_fma
g++ -m64 -lrt -O3 -mavx tests.cpp triad_avx_asm.o -o tests_avx
g++ -m64 -lrt -O3 -msse2 tests.cpp triad_sse_asm.o -o tests_sse
Код C / C++
#include <x86intrin.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#define TIMER_TYPE CLOCK_REALTIME
extern "C" float triad_sse_asm_repeat(float *x, float *y, float *z, const int n, int repeat);
extern "C" float triad_sse_asm_repeat_unroll16(float *x, float *y, float *z, const int n, int repeat);
extern "C" float triad_avx_asm_repeat(float *x, float *y, float *z, const int n, int repeat);
extern "C" float triad_avx_asm_repeat_unroll16(float *x, float *y, float *z, const int n, int repeat);
extern "C" float triad_fma_asm_repeat(float *x, float *y, float *z, const int n, int repeat);
extern "C" float triad_fma_asm_repeat_unroll16(float *x, float *y, float *z, const int n, int repeat);
#if (defined(__FMA__))
float triad_fma_repeat(float *x, float *y, float *z, const int n, int repeat) {
float k = 3.14159f;
int r;
for(r=0; r<repeat; r++) {
int i;
__m256 k4 = _mm256_set1_ps(k);
for(i=0; i<n; i+=8) {
_mm256_store_ps(&z[i], _mm256_fmadd_ps(k4, _mm256_load_ps(&y[i]), _mm256_load_ps(&x[i])));
}
}
}
#elif (defined(__AVX__))
float triad_avx_repeat(float *x, float *y, float *z, const int n, int repeat) {
float k = 3.14159f;
int r;
for(r=0; r<repeat; r++) {
int i;
__m256 k4 = _mm256_set1_ps(k);
for(i=0; i<n; i+=8) {
_mm256_store_ps(&z[i], _mm256_add_ps(_mm256_load_ps(&x[i]), _mm256_mul_ps(k4, _mm256_load_ps(&y[i]))));
}
}
}
#else
float triad_sse_repeat(float *x, float *y, float *z, const int n, int repeat) {
float k = 3.14159f;
int r;
for(r=0; r<repeat; r++) {
int i;
__m128 k4 = _mm_set1_ps(k);
for(i=0; i<n; i+=4) {
_mm_store_ps(&z[i], _mm_add_ps(_mm_load_ps(&x[i]), _mm_mul_ps(k4, _mm_load_ps(&y[i]))));
}
}
}
#endif
double time_diff(timespec start, timespec end)
{
timespec temp;
if ((end.tv_nsec-start.tv_nsec)<0) {
temp.tv_sec = end.tv_sec-start.tv_sec-1;
temp.tv_nsec = 1000000000+end.tv_nsec-start.tv_nsec;
} else {
temp.tv_sec = end.tv_sec-start.tv_sec;
temp.tv_nsec = end.tv_nsec-start.tv_nsec;
}
return (double)temp.tv_sec + (double)temp.tv_nsec*1E-9;
}
int main () {
int bytes_per_cycle = 0;
double frequency = 1.3; //Haswell
//double frequency = 3.6; //IB
//double frequency = 2.66; //Core2
#if (defined(__FMA__))
bytes_per_cycle = 96;
#elif (defined(__AVX__))
bytes_per_cycle = 48;
#else
bytes_per_cycle = 24;
#endif
double peak = frequency*bytes_per_cycle;
const int n =2048;
float* z2 = (float*)_mm_malloc(sizeof(float)*n, 64);
char *mem = (char*)_mm_malloc(1<<18,4096);
char *a = mem;
char *b = a+n*sizeof(float);
char *c = b+n*sizeof(float);
float *x = (float*)a;
float *y = (float*)b;
float *z = (float*)c;
for(int i=0; i<n; i++) {
x[i] = 1.0f*i;
y[i] = 1.0f*i;
z[i] = 0;
}
int repeat = 1000000;
timespec time1, time2;
#if (defined(__FMA__))
triad_fma_repeat(x,y,z2,n,repeat);
#elif (defined(__AVX__))
triad_avx_repeat(x,y,z2,n,repeat);
#else
triad_sse_repeat(x,y,z2,n,repeat);
#endif
while(1) {
double dtime, rate;
clock_gettime(TIMER_TYPE, &time1);
#if (defined(__FMA__))
triad_fma_asm_repeat(x,y,z,n,repeat);
#elif (defined(__AVX__))
triad_avx_asm_repeat(x,y,z,n,repeat);
#else
triad_sse_asm_repeat(x,y,z,n,repeat);
#endif
clock_gettime(TIMER_TYPE, &time2);
dtime = time_diff(time1,time2);
rate = 3.0*1E-9*sizeof(float)*n*repeat/dtime;
printf("unroll1 rate %6.2f GB/s, efficency %6.2f%%, error %d\n", rate, 100*rate/peak, memcmp(z,z2, sizeof(float)*n));
clock_gettime(TIMER_TYPE, &time1);
#if (defined(__FMA__))
triad_fma_repeat(x,y,z,n,repeat);
#elif (defined(__AVX__))
triad_avx_repeat(x,y,z,n,repeat);
#else
triad_sse_repeat(x,y,z,n,repeat);
#endif
clock_gettime(TIMER_TYPE, &time2);
dtime = time_diff(time1,time2);
rate = 3.0*1E-9*sizeof(float)*n*repeat/dtime;
printf("intrinsic rate %6.2f GB/s, efficency %6.2f%%, error %d\n", rate, 100*rate/peak, memcmp(z,z2, sizeof(float)*n));
clock_gettime(TIMER_TYPE, &time1);
#if (defined(__FMA__))
triad_fma_asm_repeat_unroll16(x,y,z,n,repeat);
#elif (defined(__AVX__))
triad_avx_asm_repeat_unroll16(x,y,z,n,repeat);
#else
triad_sse_asm_repeat_unroll16(x,y,z,n,repeat);
#endif
clock_gettime(TIMER_TYPE, &time2);
dtime = time_diff(time1,time2);
rate = 3.0*1E-9*sizeof(float)*n*repeat/dtime;
printf("unroll16 rate %6.2f GB/s, efficency %6.2f%%, error %d\n", rate, 100*rate/peak, memcmp(z,z2, sizeof(float)*n));
}
}
Код NASM с использованием System V AMD64 ABI.
triad_fma_asm.asm:
global triad_fma_asm_repeat
;RDI x, RSI y, RDX z, RCX n, R8 repeat
;z[i] = y[i] + 3.14159*x[i]
pi: dd 3.14159
;align 16
section .text
triad_fma_asm_repeat:
shl rcx, 2
add rdi, rcx
add rsi, rcx
add rdx, rcx
vbroadcastss ymm2, [rel pi]
;neg rcx
align 16
.L1:
mov rax, rcx
neg rax
align 16
.L2:
vmovaps ymm1, [rdi+rax]
vfmadd231ps ymm1, ymm2, [rsi+rax]
vmovaps [rdx+rax], ymm1
add rax, 32
jne .L2
sub r8d, 1
jnz .L1
vzeroupper
ret
global triad_fma_asm_repeat_unroll16
section .text
triad_fma_asm_repeat_unroll16:
shl rcx, 2
add rcx, rdi
vbroadcastss ymm2, [rel pi]
.L1:
xor rax, rax
mov r9, rdi
mov r10, rsi
mov r11, rdx
.L2:
%assign unroll 32
%assign i 0
%rep unroll
vmovaps ymm1, [r9 + 32*i]
vfmadd231ps ymm1, ymm2, [r10 + 32*i]
vmovaps [r11 + 32*i], ymm1
%assign i i+1
%endrep
add r9, 32*unroll
add r10, 32*unroll
add r11, 32*unroll
cmp r9, rcx
jne .L2
sub r8d, 1
jnz .L1
vzeroupper
ret
triad_ava_asm.asm:
global triad_avx_asm_repeat
;RDI x, RSI y, RDX z, RCX n, R8 repeat
pi: dd 3.14159
align 16
section .text
triad_avx_asm_repeat:
shl rcx, 2
add rdi, rcx
add rsi, rcx
add rdx, rcx
vbroadcastss ymm2, [rel pi]
;neg rcx
align 16
.L1:
mov rax, rcx
neg rax
align 16
.L2:
vmulps ymm1, ymm2, [rdi+rax]
vaddps ymm1, ymm1, [rsi+rax]
vmovaps [rdx+rax], ymm1
add rax, 32
jne .L2
sub r8d, 1
jnz .L1
vzeroupper
ret
global triad_avx_asm_repeat2
;RDI x, RSI y, RDX z, RCX n, R8 repeat
;pi: dd 3.14159
align 16
section .text
triad_avx_asm_repeat2:
shl rcx, 2
vbroadcastss ymm2, [rel pi]
align 16
.L1:
xor rax, rax
align 16
.L2:
vmulps ymm1, ymm2, [rdi+rax]
vaddps ymm1, ymm1, [rsi+rax]
vmovaps [rdx+rax], ymm1
add eax, 32
cmp eax, ecx
jne .L2
sub r8d, 1
jnz .L1
vzeroupper
ret
global triad_avx_asm_repeat_unroll16
align 16
section .text
triad_avx_asm_repeat_unroll16:
shl rcx, 2
add rcx, rdi
vbroadcastss ymm2, [rel pi]
align 16
.L1:
xor rax, rax
mov r9, rdi
mov r10, rsi
mov r11, rdx
align 16
.L2:
%assign unroll 16
%assign i 0
%rep unroll
vmulps ymm1, ymm2, [r9 + 32*i]
vaddps ymm1, ymm1, [r10 + 32*i]
vmovaps [r11 + 32*i], ymm1
%assign i i+1
%endrep
add r9, 32*unroll
add r10, 32*unroll
add r11, 32*unroll
cmp r9, rcx
jne .L2
sub r8d, 1
jnz .L1
vzeroupper
ret
triad_sse_asm.asm:
global triad_sse_asm_repeat
;RDI x, RSI y, RDX z, RCX n, R8 repeat
pi: dd 3.14159
;align 16
section .text
triad_sse_asm_repeat:
shl rcx, 2
add rdi, rcx
add rsi, rcx
add rdx, rcx
movss xmm2, [rel pi]
shufps xmm2, xmm2, 0
;neg rcx
align 16
.L1:
mov rax, rcx
neg rax
align 16
.L2:
movaps xmm1, [rdi+rax]
mulps xmm1, xmm2
addps xmm1, [rsi+rax]
movaps [rdx+rax], xmm1
add rax, 16
jne .L2
sub r8d, 1
jnz .L1
ret
global triad_sse_asm_repeat2
;RDI x, RSI y, RDX z, RCX n, R8 repeat
;pi: dd 3.14159
;align 16
section .text
triad_sse_asm_repeat2:
shl rcx, 2
movss xmm2, [rel pi]
shufps xmm2, xmm2, 0
align 16
.L1:
xor rax, rax
align 16
.L2:
movaps xmm1, [rdi+rax]
mulps xmm1, xmm2
addps xmm1, [rsi+rax]
movaps [rdx+rax], xmm1
add eax, 16
cmp eax, ecx
jne .L2
sub r8d, 1
jnz .L1
ret
global triad_sse_asm_repeat_unroll16
section .text
triad_sse_asm_repeat_unroll16:
shl rcx, 2
add rcx, rdi
movss xmm2, [rel pi]
shufps xmm2, xmm2, 0
.L1:
xor rax, rax
mov r9, rdi
mov r10, rsi
mov r11, rdx
.L2:
%assign unroll 8
%assign i 0
%rep unroll
movaps xmm1, [r9 + 16*i]
mulps xmm1, xmm2,
addps xmm1, [r10 + 16*i]
movaps [r11 + 16*i], xmm1
%assign i i+1
%endrep
add r9, 16*unroll
add r10, 16*unroll
add r11, 16*unroll
cmp r9, rcx
jne .L2
sub r8d, 1
jnz .L1
ret
1 ответ
IACA Анализ
Использование IACA (анализатор архитектуры Intel) показывает, что слияние макросов действительно происходит, и что это не проблема. Это мистик, который прав: проблема в том, что магазин вообще не использует порт 7.
IACA сообщает следующее:
Intel(R) Architecture Code Analyzer Version - 2.1
Analyzed File - ../../../tests_fma
Binary Format - 64Bit
Architecture - HSW
Analysis Type - Throughput
Throughput Analysis Report
--------------------------
Block Throughput: 1.55 Cycles Throughput Bottleneck: FrontEnd, PORT2_AGU, PORT3_AGU
Port Binding In Cycles Per Iteration:
---------------------------------------------------------------------------------------
| Port | 0 - DV | 1 | 2 - D | 3 - D | 4 | 5 | 6 | 7 |
---------------------------------------------------------------------------------------
| Cycles | 0.5 0.0 | 0.5 | 1.5 1.0 | 1.5 1.0 | 1.0 | 0.0 | 1.0 | 0.0 |
---------------------------------------------------------------------------------------
N - port number or number of cycles resource conflict caused delay, DV - Divider pipe (on port 0)
D - Data fetch pipe (on ports 2 and 3), CP - on a critical path
F - Macro Fusion with the previous instruction occurred
* - instruction micro-ops not bound to a port
^ - Micro Fusion happened
# - ESP Tracking sync uop was issued
@ - SSE instruction followed an AVX256 instruction, dozens of cycles penalty is expected
! - instruction not supported, was not accounted in Analysis
| Num Of | Ports pressure in cycles | |
| Uops | 0 - DV | 1 | 2 - D | 3 - D | 4 | 5 | 6 | 7 | |
---------------------------------------------------------------------------------
| 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [rdi+rax*1]
| 2 | 0.5 | 0.5 | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [rsi+rax*1]
| 2 | | | 0.5 | 0.5 | 1.0 | | | | CP | vmovaps ymmword ptr [rdx+rax*1], ymm1
| 1 | | | | | | | 1.0 | | | add rax, 0x20
| 0F | | | | | | | | | | jnz 0xffffffffffffffec
Total Num Of Uops: 6
В частности, заявленная пропускная способность блоков в циклах (1.5) очень хорошая с эффективностью 66%.
Пост на собственном сайте МАКА об этом явлении на Tue, 03/11/2014 - 12:39
Этот ответ был встречен сотрудником Intel наTue, 03/11/2014 - 23:20
:
Port7 AGU может работать только в хранилищах с простым адресом памяти (без индексного регистра). Вот почему приведенный выше анализ не показывает использование port7.
Это твердо устанавливает, почему порт 7 не использовался.
Теперь сопоставьте вышесказанное с 32-кратным развернутым циклом (получается,unroll16
Шоудл на самом деле можно назватьunroll32
):
Intel(R) Architecture Code Analyzer Version - 2.1
Analyzed File - ../../../tests_fma
Binary Format - 64Bit
Architecture - HSW
Analysis Type - Throughput
Throughput Analysis Report
--------------------------
Block Throughput: 32.00 Cycles Throughput Bottleneck: PORT2_AGU, Port2_DATA, PORT3_AGU, Port3_DATA, Port4, Port7
Port Binding In Cycles Per Iteration:
---------------------------------------------------------------------------------------
| Port | 0 - DV | 1 | 2 - D | 3 - D | 4 | 5 | 6 | 7 |
---------------------------------------------------------------------------------------
| Cycles | 16.0 0.0 | 16.0 | 32.0 32.0 | 32.0 32.0 | 32.0 | 2.0 | 2.0 | 32.0 |
---------------------------------------------------------------------------------------
N - port number or number of cycles resource conflict caused delay, DV - Divider pipe (on port 0)
D - Data fetch pipe (on ports 2 and 3), CP - on a critical path
F - Macro Fusion with the previous instruction occurred
* - instruction micro-ops not bound to a port
^ - Micro Fusion happened
# - ESP Tracking sync uop was issued
@ - SSE instruction followed an AVX256 instruction, dozens of cycles penalty is expected
! - instruction not supported, was not accounted in Analysis
| Num Of | Ports pressure in cycles | |
| Uops | 0 - DV | 1 | 2 - D | 3 - D | 4 | 5 | 6 | 7 | |
---------------------------------------------------------------------------------
| 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9]
| 2^ | 1.0 | | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10]
| 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11], ymm1
| 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x20]
| 2^ | | 1.0 | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x20]
| 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x20], ymm1
| 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x40]
| 2^ | 1.0 | | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x40]
| 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x40], ymm1
| 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x60]
| 2^ | | 1.0 | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x60]
| 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x60], ymm1
| 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x80]
| 2^ | 1.0 | | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x80]
| 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x80], ymm1
| 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0xa0]
| 2^ | | 1.0 | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0xa0]
| 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0xa0], ymm1
| 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0xc0]
| 2^ | 1.0 | | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0xc0]
| 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0xc0], ymm1
| 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0xe0]
| 2^ | | 1.0 | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0xe0]
| 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0xe0], ymm1
| 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x100]
| 2^ | 1.0 | | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x100]
| 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x100], ymm1
| 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x120]
| 2^ | | 1.0 | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x120]
| 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x120], ymm1
| 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x140]
| 2^ | 1.0 | | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x140]
| 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x140], ymm1
| 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x160]
| 2^ | | 1.0 | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x160]
| 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x160], ymm1
| 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x180]
| 2^ | 1.0 | | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x180]
| 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x180], ymm1
| 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x1a0]
| 2^ | | 1.0 | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x1a0]
| 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x1a0], ymm1
| 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x1c0]
| 2^ | 1.0 | | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x1c0]
| 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x1c0], ymm1
| 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x1e0]
| 2^ | | 1.0 | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x1e0]
| 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x1e0], ymm1
| 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x200]
| 2^ | 1.0 | | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x200]
| 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x200], ymm1
| 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x220]
| 2^ | | 1.0 | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x220]
| 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x220], ymm1
| 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x240]
| 2^ | 1.0 | | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x240]
| 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x240], ymm1
| 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x260]
| 2^ | | 1.0 | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x260]
| 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x260], ymm1
| 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x280]
| 2^ | 1.0 | | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x280]
| 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x280], ymm1
| 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x2a0]
| 2^ | | 1.0 | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x2a0]
| 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x2a0], ymm1
| 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x2c0]
| 2^ | 1.0 | | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x2c0]
| 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x2c0], ymm1
| 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x2e0]
| 2^ | | 1.0 | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x2e0]
| 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x2e0], ymm1
| 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x300]
| 2^ | 1.0 | | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x300]
| 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x300], ymm1
| 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x320]
| 2^ | | 1.0 | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x320]
| 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x320], ymm1
| 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x340]
| 2^ | 1.0 | | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x340]
| 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x340], ymm1
| 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x360]
| 2^ | | 1.0 | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x360]
| 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x360], ymm1
| 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x380]
| 2^ | 1.0 | | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x380]
| 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x380], ymm1
| 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x3a0]
| 2^ | | 1.0 | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x3a0]
| 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x3a0], ymm1
| 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x3c0]
| 2^ | 1.0 | | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x3c0]
| 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x3c0], ymm1
| 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x3e0]
| 2^ | | 1.0 | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x3e0]
| 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x3e0], ymm1
| 1 | | | | | | 1.0 | | | | add r9, 0x400
| 1 | | | | | | | 1.0 | | | add r10, 0x400
| 1 | | | | | | 1.0 | | | | add r11, 0x400
| 1 | | | | | | | 1.0 | | | cmp r9, rcx
| 0F | | | | | | | | | | jnz 0xfffffffffffffcaf
Total Num Of Uops: 164
Мы видим здесь микро-слияние и правильное планирование магазина до порта 7.
Ручной анализ (см. Выше)
Теперь я могу ответить на второй ваш вопрос:возможно ли это без развертывания, и если да, то как это можно сделать?, Ответ - нет.
Я дополнил массивыx
,y
а также z
влево и вправо с большим количеством буфера для эксперимента, приведенного ниже, и изменил внутренний цикл следующим образом:
.L2:
vmovaps ymm1, [rdi+rax] ; 1L
vmovaps ymm0, [rsi+rax] ; 2L
vmovaps [rdx+rax], ymm2 ; S1
add rax, 32 ; ADD
jne .L2 ; JMP
Это преднамеренно не использует FMA (только загрузка и сохранение), и все инструкции по загрузке / сохранению не имеют никаких зависимостей, поскольку, следовательно, не должно быть никаких опасностей, препятствующих их передаче в любые порты выполнения.
Затем я проверил каждую перестановку первой и второй нагрузок (1L
а также2L
), магазин (S1
) и добавить (A
) при выходе из условного прыжка (J
) в конце, и для каждого из них я тестировал каждую возможную комбинацию смещенийx
,y
а также z
на 0 или -32 байта (чтобы исправить тот факт, что переупорядочение add rax, 32
перед одним из r+r
индексы могут привести к тому, что загрузка или хранилище будут направлены не по адресу). Цикл был выровнен до 32 байтов. Тесты проводились на 2, 4 ГГц i7-4700MQ с отключенным TurboBoost с помощью echo '0' > /sys/devices/system/cpu/cpufreq/boost
под Linux и используя 2.4 для постоянной частоты. Вот результаты эффективности (максимум 24):
Cases: 0 1 2 3 4 5 6 7
L1 L2 S L1 L2 S L1 L2 S L1 L2 S L1 L2 S L1 L2 S L1 L2 S L1 L2 S
-0 -0 -0 -0 -0 -32 -0 -32 -0 -0 -32 -32 -32 -0 -0 -32 -0 -32 -32 -32 -0 -32 -32 -32
________________________________________________________________________________________________
12SAJ: 65.34% 65.34% 49.63% 65.07% 49.70% 65.05% 49.22% 65.07%
12ASJ: 48.59% 64.48% 48.74% 49.69% 48.75% 49.69% 48.99% 48.60%
1A2SJ: 49.69% 64.77% 48.67% 64.06% 49.69% 49.69% 48.94% 49.69%
1AS2J: 48.61% 64.66% 48.73% 49.71% 48.77% 49.69% 49.05% 48.74%
1S2AJ: 49.66% 65.13% 49.49% 49.66% 48.96% 64.82% 49.02% 49.66%
1SA2J: 64.44% 64.69% 49.69% 64.34% 49.69% 64.41% 48.75% 64.14%
21SAJ: 65.33%* 65.34% 49.70% 65.06% 49.62% 65.07% 49.22% 65.04%
21ASJ: Hypothetically =12ASJ
2A1SJ: Hypothetically =1A2SJ
2AS1J: Hypothetically =1AS2J
2S1AJ: Hypothetically =1S2AJ
2SA1J: Hypothetically =1SA2J
S21AJ: 48.91% 65.19% 49.04% 49.72% 49.12% 49.63% 49.21% 48.95%
S2A1J: Hypothetically =S1A2J
SA21J: Hypothetically =SA12J
SA12J: 64.69% 64.93% 49.70% 64.66% 49.69% 64.27% 48.71% 64.56%
S12AJ: 48.90% 65.20% 49.12% 49.63% 49.03% 49.70% 49.21%* 48.94%
S1A2J: 49.69% 64.74% 48.65% 64.48% 49.43% 49.69% 48.66% 49.69%
A2S1J: Hypothetically =A1S2J
A21SJ: Hypothetically =A12SJ
A12SJ: 64.62% 64.45% 49.69% 64.57% 49.69% 64.45% 48.58% 63.99%
A1S2J: 49.72% 64.69% 49.72% 49.72% 48.67% 64.46% 48.95% 49.72%
AS21J: Hypothetically =AS21J
AS12J: 48.71% 64.53% 48.76% 49.69% 48.76% 49.74% 48.93% 48.69%
Мы можем заметить несколько вещей из таблицы:
- Несколько плато результатов, но только два основных: чуть менее 50% и около 65%.
- L1 и L2 могут свободно переставлять друг друга, не влияя на результат.
- Смещение доступа на -32 байтаможет изменить эффективность.
- Интересующие нас паттерны (Load 1, Load 2, Store 1 и Jump с надписью Add в любом месте вокруг них и корректным применением смещений -32) одинаковы и находятся на более высоком плато:
12SAJ
Случай 0 (без смещений), с эффективностью 65,34% (самый высокий)12ASJ
Случай 1 (S-32
), с эффективностью 64,48%1A2SJ
Дело 3 (2L-32
,S-32
), с эффективностью 64,06%A12SJ
Дело 7 (1L-32
,2L-32
,S-32
), с эффективностью 63,99%
- Всегда существует хотя бы один "случай" для каждой перестановки, который позволяет выполнить на более высоком плато эффективности. В частности, Случай 1 (где
S-32
Кажется, это гарантирует. - Корпуса 2, 4 и 6 гарантируют исполнение на нижнем плато. Общим для них является то, что одна или обе нагрузки смещены на -32, а магазин - нет.
- Для случаев 0, 3, 5 и 7 это зависит от перестановки.
Отсюда можно сделать хотя бы несколько выводов:
- Порты исполнения 2 и 3 действительно не заботятся о том, с какого адреса загрузки они генерируют и загружают.
- Макрооперация слияния
add
а такжеjmp
кажется, не затронутым какой-либо перестановкой инструкций (в частности, в случае смещения в случае 1), что наводит меня на мысль, что вывод @Evgeny Kluev неверен: расстояние отadd
отjne
не влияет на их слияние. Я вполне уверен, что теперь Haswell ROB справляется с этим правильно.- Что видел Евгений
12SAJ
с эффективностью 65% по сравнению с остальными (с эффективностью 49% в случае 0) эффект был обусловлен исключительно значением адресов, загружаемых и сохраняемых с адресов, а не неспособностью ядра слиться с макрокомандой при добавлении и переходе. - Кроме того, макрооперация должна происходить,по крайней мере, некоторое время, так как среднее время цикла составляет 1,5 куб. Если макрооперация не произойдет, это будет минимум 2CC.
- Что видел Евгений
- Протестировав все действительные и недействительные перестановки инструкций в неразвернутом цикле, мы не увидели ничего выше 65,34%. Это дает эмпирический ответ "нет" на вопрос о том, возможно ли использовать всю полосу пропускания без развертывания.
Я предположу несколько возможных объяснений:
- Мы видим странное извращение из-за значения адресов относительно друг друга.
- Если это так, то существует множество смещений
x
,y
а такжеz
это позволило бы максимальную пропускную способность. Быстрые случайные тесты с моей стороны, кажется, не подтверждают это.
- Если это так, то существует множество смещений
Мы видим, как цикл запускается в одно-двухступенчатом режиме; Итерации цикла чередуются в одном такте, а затем в двух.
Это может быть макрооперация, на которую влияют декодеры. От Агнер Туман:
Плавкие арифметические / логические инструкции не могут быть декодированы в последнем из четырех декодеров на процессорах Sandy Bridge и Ivy Bridge.Я не проверял, относится ли это также к Haswell.
- Альтернативно, каждый второй тактовый цикл выдает команду "неправильному" порту, блокируя следующую итерацию на один дополнительный тактовый цикл. Такая ситуация будет самокорректирующейся в следующем тактовом цикле, но останется колебательной.
- Если у кого-то есть доступ к счетчикам производительности Intel, он должен посмотреть на события
UOPS_EXECUTED_PORT.PORT_[0-7]
, Если колебания не происходит, все используемые порты будут одинаково привязаны в течение соответствующего отрезка времени; В противном случае, если произойдет колебание, будет 50% -ое разделение. Особенно важно посмотреть на порты, отмеченные Мистическим (0, 1, 6 и 7).
- Если у кого-то есть доступ к счетчикам производительности Intel, он должен посмотреть на события
И вот что я думаю не происходит:
- Я не верю, что объединенная арифметика + ветвь uop блокирует выполнение, переходя на порт 0, поскольку предсказанные ветки отправляются исключительно на порт 6 (см. Таблицу инструкций Agner Fog в разделе
Haswell -> Control transfer instructions
). После нескольких итераций вышеуказанного цикла предиктор ветвления узнает, что эта ветвь является циклом, и всегда предсказывает, как принято.
Я считаю, что это проблема, которая будет решена с помощью счетчиков производительности Intel.