在L1caching上获取Haswell的峰值带宽:只有62%
我试图在L1caching中获得全部带宽,以便在Intel处理器上使用以下function
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的三合一function。
使用此function的SandyBridge / IvyBridge处理器(使用与NASM的汇编)可获得95%的峰值。 但是,除非我展开循环,否则使用Haswell I只能达到峰值的62%。 如果我展开16次,我得到92%。 我不明白这一点。
我决定使用NASM在汇编中编写我的function。 assembly中的主循环看起来像这样。
.L2: vmovaps ymm1, [rdi+rax] vfmadd231ps ymm1, ymm2, [rsi+rax] vmovaps [rdx+rax], ymm1 add rax, 32 jne .L2
在例子12.7-12.11的Agner Fog's Optimizing Assembly手册中,他对Pentium M,Core 2,Sandy Bridge做了几乎相同的事情(但对于y[i] = y[i] +k*x[i]
) ,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的原因是它们融合到一个macros操作中(不要与仍然使用多个端口的μop融合混淆),只需要端口6和一个μop。 。 为了和Agner Fog的表格保持一致,并且我认为说一个指令可以在不同的端口上平等地分配到每个1/2的时间是更有意义的,所以我为端口vfmadd231ps
指令可以使用端口0或端口1.我select端口0.负载vmovaps
可以使用端口2或3.我select了2, vfmadd231ps
使用端口3vmovaps
和vmadd231ps
分配了1/2可以去。
根据这张表以及所有Core2处理器在每个时钟周期内都可以做4个μops的事实,看起来这个循环在每个时钟周期都是可能的,但是我还没有设法获得它。 有人可以向我解释为什么我不能在不展开的情况下接近Haswell的这个函数的峰值带宽? 这可能没有展开,如果是这样,怎么办? 让我清楚,我真的想要最大化这个函数的ILP(我不仅想要最大的带宽),所以这是我不想展开的原因。
编辑:这是一个更新,因为Iwillnotexist Idonotexist显示使用IACA,商店从来没有使用端口7.我设法打破了66%的障碍,没有展开,并在一个时钟周期内每次迭代,而无需展开(理论上)。 我们先来解决商店问题。
Stephen Canon在评论中提到,端口7中的地址生成单元(AGU)只能处理简单的操作,如[base + offset]
而不是[base + index]
。 在“ 英特尔优化参考手册”中 ,我发现的唯一一件事就是对port7的评论,它说“Simple_AGU”没有定义什么简单的方法。 但是Iwillnotexist Idonotexist在IACA的评论中发现,这个问题在六个月前就已经提到,Intel的一名员工在03/11/2014写道:
Port7 AGU只能在具有简单内存地址(无索引寄存器)的存储器上工作。
Stephen Canonbuild议“使用商店地址作为加载操作数的偏移量”。 我已经尝试过这样
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
这确实使商店使用port7。 但是,还有一个问题,那就是vmadd231ps
不能和IACA看到的负载相融合。 另外还需要我原来的function没有的cmp
指令。 所以商店使用一个更less的微操作,但是cmp
(或者更确切地说,因为cmp
macros与jne
融合)需要多一个。 IACA报告1.5的块吞吐量。 实际上这只能达到峰值的57%左右。
但是我发现了一种让vmadd231ps
指令与负载融合的方法。 这只能使用静态数组与地址[绝对32位地址+索引]像这样。 Evgeny Kluev原来build议这个 。
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%的障碍,但从我展开16次的92%还有很长的路要走。 所以在Haswell,接近峰值的唯一select是展开。 在Core2上通过Ivy Bridge不是必需的,但它在Haswell上。
End_edit:
这里是C / C ++ Linux代码来testing这个。 NASM代码在C / C ++代码之后发布。 唯一需要改变的是频率数字。 在行double frequency = 1.3;
用你的处理器的工作频率(不是标称频率)replace1.3(在BIOS中,禁用turbo的i5-4250U是1.3 GHz)。
编译
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)); } }
使用System V AMD64 ABI的NASM代码。
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
IACA分析
使用IACA(英特尔架构代码分析器)显示macros观操作融合确实正在发生,而这不是问题。 这是Mysticial是正确的: 问题是商店根本没有使用Port 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 - 23:20
回复中遇到了在Tue, 03/11/2014 - 12:39
在IACA网站上发布的关于这种现象的post。
Port7 AGU只能在具有简单内存地址(无索引寄存器)的存储器上工作。 这就是为什么上面的分析没有显示port7的利用率。
这坚定了为什么港口7没有被使用。
现在,对比上面的一个32倍的展开循环(事实上unroll16
shoudl实际上被称为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(只加载和存储),并且所有的加载/存储指令都没有依赖关系,因此不应该有任何危害,无论它们是否阻止它们进入任何执行端口。
然后我testing了第一个和第二个加载( 1L
和2L
),存储( S1
)和加法( A
)的每一个排列,同时在结束时留下条件跳转( J
),并且对于每一个我testing了每个可能的将x
, y
和z
的偏移量组合为0或-32字节(以纠正在r+r
索引之一之前重新sortingadd rax, 32
的事实add rax, 32
会导致加载或存储以错误的地址为目标)。 循环alignment到32个字节。 testing在2.4GHz的i7-4700MQ上运行,在Linux下使用echo'0 echo '0' > /sys/devices/system/cpu/cpufreq/boost
禁用TurboBoost,频率常数为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字节偏移访问可以改变效率。
- 我们感兴趣的模式(加载1,加载2,存储1和跳转,加上它们周围的任何地方以及适当的-32偏移量)都是一样的,都在更高的高度:
-
12SAJ
情况0(无偏移),效率65.34%(最高) -
12ASJ
案例1(S-32
),效率64.48% -
1A2SJ
案例3(2L-32
,S-32
),效率64.06% -
A12SJ
案例7(1L-32
A12SJ
2L-32
,S-32
),效率63.99%
-
- 对于每个排列,至less存在一个“情况”,允许在更高的效率高度执行。 特别是案例1(其中
S-32
)似乎可以保证这一点。 - 案例2,4和6保证在较低的高原执行。 他们有一个共同点,那就是一方或双方的货物都被-32偏移了,而商店则没有。
- 对于0,3,5和7的情况,取决于排列。
从哪里我们可以得出至less几个结论:
- 执行端口2和3真的不关心它们生成和加载哪个加载地址。
- macros观操作融合的
add
和jmp
似乎没有任何排列的说明(特别是在案例1抵消),导致我相信@Evgeny Kluev的结论是不正确的:add
距离jne
似乎并没有影响他们的融合。 现在我确信Haswell ROB正确地处理了这个问题。- Evgeny看到的情况(从案例0的效率65%到效率49%的情况下,效率达到49%)仅仅是由于核心对macros的加载和存储的地址的价值的影响,而不是由于核心无法对macros-fuse添加和分支。
- 此外,由于平均循环时间是1.5CC,所以至less在一些时间内必须发生macros观操作融合。 如果macros观操作融合没有发生,这将是最低2CC。
- 在未展开的循环中testing了所有有效和无效的指令排列,我们没有看到任何高于65.34%的指令。 这在经验上回答“否”是否可以在不展开的情况下使用全带宽的问题。
我会假设几个可能的解释:
- 由于地址相对于彼此的价值,我们看到一些奇怪的颠倒。
- 如果是的话,那么将会存在一组
x
,y
和z
的偏移量,这将允许最大的吞吐量。 我的部分快速随机testing似乎不支持这一点。
- 如果是的话,那么将会存在一组
-
我们看到循环运行在一个两步模式; 循环迭代在一个时钟周期内交替运行,然后是两个循环。
-
这可能是解码器影响的macros观操作融合。 从Agner雾:
在Sandy Bridge和Ivy Bridge处理器的最后四个解码器中,不能解码熔丝算术/逻辑指令。 我还没有testing这是否也适用于Haswell。
- 或者,每隔一个时钟周期一个指令发送到“错误”端口,阻塞下一次迭代一个额外的时钟周期。 这样的情况在下一个时钟周期会自我修正,但会保持振荡。
- 如果有人访问英特尔性能计数器,他应该查看事件
UOPS_EXECUTED_PORT.PORT_[0-7]
。 如果没有发生振荡,所有使用的端口将在相关时间段内被平等地固定; 否则,如果发生振荡,将会有50%的分裂。 尤其重要的是看神秘的港口(0,1,6和7)。
- 如果有人访问英特尔性能计数器,他应该查看事件
-
以下是我认为没有发生的事情:
- 我不相信融合的算术+分支uop是通过到端口0阻塞执行的,因为预测分支专门发送到端口6(参见Haswell雾
Haswell -> Control transfer instructions
指令表Haswell -> Control transfer instructions
)。 经过上述循环的几次迭代之后,分支预测器将会知道这个分支是一个循环,并且总是预测为被采用。
我相信这是英特尔性能计数器将要解决的问题。