Lor*_*ran 5 c++ assembly x86-64 auto-vectorization avx512
我尝试编写一些函数来使用单个矩阵和源向量数组来执行矩阵向量乘法。我曾经用 C++ 编写过这些函数,并在 x86 AVX512 汇编中编写过一次,以将性能与英特尔 VTune Profiler 进行比较。当使用源向量数组作为目标数组时,汇编变体的执行速度比 C++ 对应版本快 3.5 倍到 10x\xc2\xa0,但是当使用不同的源和目标数组时,汇编变体的性能几乎不比 C++ 对应版本更好,实现几乎相同的性能...有时甚至更糟。
\n我无法理解的另一件事是,为什么在使用不同的源和目标数组时,C++ 对应项甚至可以达到与汇编变体接近相同或更好的性能水平,即使汇编代码要短得多并且也根据静态分析工具 uica 和 llvm-mca 速度提高数倍。uica.uops.info
\n我不想让这篇文章变得太长,所以我只发布执行 mat4-vec4 乘法的函数的代码。
\n这是汇编变体的代码,它假设矩阵要转置:
\nalignas(64) uint32_t mat4_mul_vec4_avx512_vpermps_index[64]{ 0, 0, 0, 0, 4, 4, 4, 4, 8, 8, 8, 8, 12, 12, 12, 12,\n 1, 1, 1, 1, 5, 5, 5, 5, 9, 9, 9, 9, 13, 13, 13, 13,\n 2, 2, 2, 2, 6, 6, 6, 6, 10, 10, 10, 10, 14, 14, 14, 14,\n 3, 3, 3, 3, 7, 7, 7, 7, 11, 11, 11, 11, 15, 15, 15, 15 };\n\nvoid __declspec(naked, align(64)) mat4_mul_vec4_avx512(vec4f_t* destination, const mat4f_t& src1, const vec4f_t* src2, uint32_t vector_count) {\n__asm {\n vbroadcastf32x4 zmm16, xmmword ptr[rdx]\n vbroadcastf32x4 zmm17, xmmword ptr[rdx + 16]\n\n vbroadcastf32x4 zmm18, xmmword ptr[rdx + 32]\n vbroadcastf32x4 zmm19, xmmword ptr[rdx + 48]\n\n vmovups zmm20, zmmword ptr[mat4_mul_vec4_avx512_vpermps_index]\n vmovups zmm21, zmmword ptr[mat4_mul_vec4_avx512_vpermps_index + 64]\n\n vmovups zmm22, zmmword ptr[mat4_mul_vec4_avx512_vpermps_index + 128]\n vmovups zmm23, zmmword ptr[mat4_mul_vec4_avx512_vpermps_index + 192]\n\n vmovups zmm24, zmmword ptr[r8]\n\n vpermps zmm25, zmm20, zmm24\n vpermps zmm26, zmm21, zmm24\n vpermps zmm27, zmm22, zmm24\n vpermps zmm28, zmm23, zmm24\n\n xor eax, eax\n\n align 32\n mat4_mul_vec4_avx512_loop:\n\n vmovups zmm24, zmmword ptr[r8+rax+64]\n\n vmulps zmm29, zmm16, zmm25\n vpermps zmm25, zmm20, zmm24\n\n vfmadd231ps zmm29, zmm17, zmm26\n vpermps zmm26, zmm21, zmm24\n\n vfmadd231ps zmm29, zmm18, zmm27\n vpermps zmm27, zmm22, zmm24\n\n vfmadd231ps zmm29, zmm19, zmm28\n vpermps zmm28, zmm23, zmm24\n\n vmovups zmmword ptr[rcx+rax], zmm29\n\n add rax, 64\n\n sub r9, 4\n jnz mat4_mul_vec4_avx512_loop\n\n ret\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n这是 C++ 变体,它假设矩阵不被转置:
\nvoid mat4_mul_vec4_cpp(vec4f_t* destination, const mat4f_t& src1, const vec4f_t* src2, uint32_t vector_count) {\nfor (uint32_t i0{}; i0 < vector_count; ++i0) {\n destination[i0].element_[0] = src1.element_[0][0] * src2[i0].element_[0] + src1.element_[0][1] * src2[i0].element_[1] + src1.element_[0][2] * src2[i0].element_[2] + src1.element_[0][3] * src2[i0].element_[3];\n destination[i0].element_[1] = src1.element_[1][0] * src2[i0].element_[0] + src1.element_[1][1] * src2[i0].element_[1] + src1.element_[1][2] * src2[i0].element_[2] + src1.element_[1][3] * src2[i0].element_[3];\n destination[i0].element_[2] = src1.element_[2][0] * src2[i0].element_[0] + src1.element_[2][1] * src2[i0].element_[1] + src1.element_[2][2] * src2[i0].element_[2] + src1.element_[2][3] * src2[i0].element_[3];\n destination[i0].element_[3] = src1.element_[3][0] * src2[i0].element_[0] + src1.element_[3][1] * src2[i0].element_[1] + src1.element_[3][2] * src2[i0].element_[2] + src1.element_[3][3] * src2[i0].element_[3];\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n英特尔 C++ 编译器生成以下汇编代码:
\n00007FF69F123D50 sub rsp,38h \n00007FF69F123D54 vmovaps xmmword ptr [rsp+20h],xmm8 \n00007FF69F123D5A vmovaps xmmword ptr [rsp+10h],xmm7 \n00007FF69F123D60 vmovaps xmmword ptr [rsp],xmm6 \n destination[i0].element_[0] = src1.element_[0][0] * src2[i0].element_[0] + src1.element_[0][1] * src2[i0].element_[1] + src1.element_[0][2] * src2[i0].element_[2] + src1.element_[0][3] * src2[i0].element_[3];\n00007FF69F123D65 vmovss xmm0,dword ptr [rdx] \n00007FF69F123D69 vmovss xmm1,dword ptr [rdx+4] \n00007FF69F123D6E vmovss xmm2,dword ptr [rdx+8] \n00007FF69F123D73 vmovss xmm3,dword ptr [rdx+0Ch] \n destination[i0].element_[1] = src1.element_[1][0] * src2[i0].element_[0] + src1.element_[1][1] * src2[i0].element_[1] + src1.element_[1][2] * src2[i0].element_[2] + src1.element_[1][3] * src2[i0].element_[3];\n00007FF69F123D78 vmovss xmm4,dword ptr [rdx+10h] \n00007FF69F123D7D vmovss xmm5,dword ptr [rdx+14h] \n00007FF69F123D82 vmovss xmm16,dword ptr [rdx+18h] \n00007FF69F123D89 vmovss xmm17,dword ptr [rdx+1Ch] \n destination[i0].element_[2] = src1.element_[2][0] * src2[i0].element_[0] + src1.element_[2][1] * src2[i0].element_[1] + src1.element_[2][2] * src2[i0].element_[2] + src1.element_[2][3] * src2[i0].element_[3];\n00007FF69F123D90 vmovss xmm18,dword ptr [rdx+20h] \n00007FF69F123D97 vmovss xmm19,dword ptr [rdx+24h] \n00007FF69F123D9E vmovss xmm20,dword ptr [rdx+28h] \n00007FF69F123DA5 vmovss xmm21,dword ptr [rdx+2Ch] \n destination[i0].element_[3] = src1.element_[3][0] * src2[i0].element_[0] + src1.element_[3][1] * src2[i0].element_[1] + +src1.element_[3][2] * src2[i0].element_[2] + src1.element_[3][3] * src2[i0].element_[3];\n00007FF69F123DAC vmovss xmm22,dword ptr [rdx+30h] \n00007FF69F123DB3 vmovss xmm23,dword ptr [rdx+34h] \n00007FF69F123DBA vmovss xmm24,dword ptr [rdx+38h] \n00007FF69F123DC1 vmovss xmm25,dword ptr [rdx+3Ch] \n for (uint32_t i0{}; i0 < vector_count; ++i0) {\n00007FF69F123DC8 lea rax,[r8+3A9800h] \n00007FF69F123DCF cmp rax,rcx \n00007FF69F123DD2 jbe mat4_mul_vec4_cpp+150h (07FF69F123EA0h) \n00007FF69F123DD8 lea rax,[rcx+3A9800h] \n00007FF69F123DDF cmp rax,r8 \n00007FF69F123DE2 jbe mat4_mul_vec4_cpp+150h (07FF69F123EA0h) \n00007FF69F123DE8 mov eax,0Ch \n00007FF69F123DED nop dword ptr [rax] \n destination[i0].element_[0] = src1.element_[0][0] * src2[i0].element_[0] + src1.element_[0][1] * src2[i0].element_[1] + src1.element_[0][2] * src2[i0].element_[2] + src1.element_[0][3] * src2[i0].element_[3];\n00007FF69F123DF0 vmulss xmm26,xmm0,dword ptr [r8+rax-0Ch] \n00007FF69F123DF8 vfmadd231ss xmm26,xmm1,dword ptr [r8+rax-8] \n00007FF69F123E00 vfmadd231ss xmm26,xmm2,dword ptr [r8+rax-4] \n00007FF69F123E08 vfmadd231ss xmm26,xmm3,dword ptr [r8+rax] \n00007FF69F123E0F vmovss dword ptr [rcx+rax-0Ch],xmm26 \n destination[i0].element_[1] = src1.element_[1][0] * src2[i0].element_[0] + src1.element_[1][1] * src2[i0].element_[1] + src1.element_[1][2] * src2[i0].element_[2] + src1.element_[1][3] * src2[i0].element_[3];\n00007FF69F123E17 vmulss xmm26,xmm4,dword ptr [r8+rax-0Ch] \n00007FF69F123E1F vfmadd231ss xmm26,xmm5,dword ptr [r8+rax-8] \n00007FF69F123E27 vfmadd231ss xmm26,xmm16,dword ptr [r8+rax-4] \n00007FF69F123E2F vfmadd231ss xmm26,xmm17,dword ptr [r8+rax] \n00007FF69F123E36 vmovss dword ptr [rcx+rax-8],xmm26 \n destination[i0].element_[2] = src1.element_[2][0] * src2[i0].element_[0] + src1.element_[2][1] * src2[i0].element_[1] + src1.element_[2][2] * src2[i0].element_[2] + src1.element_[2][3] * src2[i0].element_[3];\n00007FF69F123E3E vmulss xmm26,xmm18,dword ptr [r8+rax-0Ch] \n00007FF69F123E46 vfmadd231ss xmm26,xmm19,dword ptr [r8+rax-8] \n00007FF69F123E4E vfmadd231ss xmm26,xmm20,dword ptr [r8+rax-4] \n00007FF69F123E56 vfmadd231ss xmm26,xmm21,dword ptr [r8+rax] \n00007FF69F123E5D vmovss dword ptr [rcx+rax-4],xmm26 \n destination[i0].element_[3] = src1.element_[3][0] * src2[i0].element_[0] + src1.element_[3][1] * src2[i0].element_[1] + +src1.element_[3][2] * src2[i0].element_[2] + src1.element_[3][3] * src2[i0].element_[3];\n00007FF69F123E65 vmulss xmm26,xmm22,dword ptr [r8+rax-0Ch] \n00007FF69F123E6D vfmadd231ss xmm26,xmm23,dword ptr [r8+rax-8] \n00007FF69F123E75 vfmadd231ss xmm26,xmm24,dword ptr [r8+rax-4] \n00007FF69F123E7D vfmadd231ss xmm26,xmm25,dword ptr [r8+rax] \n00007FF69F123E84 vmovss dword ptr [rcx+rax],xmm26 \n for (uint32_t i0{}; i0 < vector_count; ++i0) {\n00007FF69F123E8B add rax,10h \n00007FF69F123E8F cmp rax,3A980Ch \n00007FF69F123E95 jne mat4_mul_vec4_cpp+0A0h (07FF69F123DF0h) \n00007FF69F123E9B jmp mat4_mul_vec4_cpp+2FEh (07FF69F12404Eh) \n00007FF69F123EA0 vbroadcastss ymm0,xmm0 \n00007FF69F123EA5 vbroadcastss ymm1,xmm1 \n00007FF69F123EAA vbroadcastss ymm2,xmm2 \n00007FF69F123EAF vbroadcastss ymm3,xmm3 \n00007FF69F123EB4 vbroadcastss ymm4,xmm4 \n00007FF69F123EB9 vbroadcastss ymm5,xmm5 \n00007FF69F123EBE vbroadcastss ymm16,xmm16 \n00007FF69F123EC4 vbroadcastss ymm17,xmm17 \n00007FF69F123ECA vbroadcastss ymm18,xmm18 \n00007FF69F123ED0 vbroadcastss ymm19,xmm19 \n00007FF69F123ED6 vbroadcastss ymm20,xmm20 \n00007FF69F123EDC vbroadcastss ymm21,xmm21 \n00007FF69F123EE2 vbroadcastss ymm22,xmm22 \n00007FF69F123EE8 vbroadcastss ymm23,xmm23 \n00007FF69F123EEE vbroadcastss ymm24,xmm24 \n00007FF69F123EF4 vbroadcastss ymm25,xmm25 \n00007FF69F123EFA xor eax,eax \n destination[i0].element_[0] = src1.element_[0][0] * src2[i0].element_[0] + src1.element_[0][1] * src2[i0].element_[1] + src1.element_[0][2] * src2[i0].element_[2] + src1.element_[0][3] * src2[i0].element_[3];\n00007FF69F123EFC vmovups xmm26,xmmword ptr [r8+rax] \n00007FF69F123F03 vmovups xmm27,xmmword ptr [r8+rax+10h] \n00007FF69F123F0B vmovups xmm28,xmmword ptr [r8+rax+20h] \n00007FF69F123F13 vmovups xmm29,xmmword ptr [r8+rax+30h] \n00007FF69F123F1B vinsertf32x4 ymm26,ymm26,xmmword ptr [r8+rax+40h],1 \n00007FF69F123F24 vinsertf32x4 ymm27,ymm27,xmmword ptr [r8+rax+50h],1 \n00007FF69F123F2D vinsertf32x4 ymm28,ymm28,xmmword ptr [r8+rax+60h],1 \n00007FF69F123F36 vinsertf32x4 ymm29,ymm29,xmmword ptr [r8+rax+70h],1 \n00007FF69F123F3F vshufps ymm30,ymm26,ymm27,14h \n00007FF69F123F46 vshufps ymm31,ymm29,ymm28,41h \n00007FF69F123F4D vshufps ymm6,ymm30,ymm31,6Ch \n00007FF69F123F54 vmulps ymm7,ymm6,ymm0 \n destination[i0].element_[1] = src1.element_[1][0] * src2[i0].element_[0] + src1.element_[1][1] * src2[i0].element_[1] + src1.element_[1][2] * src2[i0].element_[2] + src1.element_[1][3] * src2[i0].element_[3];\n00007FF69F123F58 vmulps ymm8,ymm6,ymm4 \n destination[i0].element_[0] = src1.element_[0][0] * src2[i0].element_[0] + src1.element_[0][1] * src2[i0].element_[1] + src1.element_[0][2] * src2[i0].element_[2] + src1.element_[0][3] * src2[i0].element_[3];\n00007FF69F123F5C vshufps ymm30,ymm30,ymm31,39h \n destination[i0].element_[2] = src1.element_[2][0] * src2[i0].element_[0] + src1.element_[2][1] * src2[i0].element_[1] + src1.element_[2][2] * src2[i0].element_[2] + src1.element_[2][3] * src2[i0].element_[3];\n00007FF69F123F63 vmulps ymm31,ymm6,ymm18 \n destination[i0].element_[3] = src1.element_[3][0] * src2[i0].element_[0] + src1.element_[3][1] * src2[i0].element_[1] + +src1.element_[3][2] * src2[i0].element_[2] + src1.element_[3][3] * src2[i0].element_[3];\n00007FF69F123F69 vmulps ymm6,ymm6,ymm22 \n destination[i0].element_[0] = src1.element_[0][0] * src2[i0].element_[0] + src1.element_[0][1] * src2[i0].element_[1] + src1.element_[0][2] * src2[i0].element_[2] + src1.element_[0][3] * src2[i0].element_[3];\n00007FF69F123F6F vfmadd231ps ymm7,ymm30,ymm1 \n destination[i0].element_[1] = src1.element_[1][0] * src2[i0].element_[0] + src1.element_[1][1] * src2[i0].element_[1] + src1.element_[1][2] * src2[i0].element_[2] + src1.element_[1][3] * src2[i0].element_[3];\n00007FF69F123F75 vfmadd231ps ymm8,ymm30,ymm5 \n destination[i0].element_[2] = src1.element_[2][0] * src2[i0].element_[0] + src1.element_[2][1] * src2[i0].element_[1] + src1.element_[2][2] * src2[i0].element_[2] + src1.element_[2][3] * src2[i0].element_[3];\n00007FF69F123F7B vfmadd231ps ymm31,ymm30,ymm19 \n destination[i0].element_[3] = src1.element_[3][0] * src2[i0].element_[0] + src1.element_[3][1] * src2[i0].element_[1] + +src1.element_[3][2] * src2[i0].element_[2] + src1.element_[3][3] * src2[i0].element_[3];\n00007FF69F123F81 vfmadd231ps ymm6,ymm23,ymm30 \n destination[i0].element_[0] = src1.element_[0][0] * src2[i0].element_[0] + src1.element_[0][1] * src2[i0].element_[1] + src1.element_[0][2] * src2[i0].element_[2] + src1.element_[0][3] * src2[i0].element_[3];\n00007FF69F123F87 vshufps ymm26,ymm26,ymm27,0BEh \n00007FF69F123F8E vshufps ymm27,ymm29,ymm28,0EBh \n00007FF69F123F95 vshufps ymm28,ymm26,ymm27,6Ch \n00007FF69F123F9C vfmadd231ps ymm7,ymm28,ymm2 \n destination[i0].element_[1] = src1.element_[1][0] * src2[i0].element_[0] + src1.element_[1][1] * src2[i0].element_[1] + src1.element_[1][2] * src2[i0].element_[2] + src1.element_[1][3] * src2[i0].element_[3];\n00007FF69F123FA2 vfmadd231ps ymm8,ymm28,ymm16 \n destination[i0].element_[2] = src1.element_[2][0] * src2[i0].element_[0] + src1.element_[2][1] * src2[i0].element_[1] + src1.element_[2][2] * src2[i0].element_[2] + src1.element_[2][3] * src2[i0].element_[3];\n00007FF69F123FA8 vfmadd231ps ymm31,ymm28,ymm20 \n destination[i0].element_[3] = src1.element_[3][0] * src2[i0].element_[0] + src1.element_[3][1] * src2[i0].element_[1] + +src1.element_[3][2] * src2[i0].element_[2] + src1.element_[3][3] * src2[i0].element_[3];\n00007FF69F123FAE vfmadd231ps ymm6,ymm24,ymm28 \n destination[i0].element_[0] = src1.element_[0][0] * src2[i0].element_[0] + src1.element_[0][1] * src2[i0].element_[1] + src1.element_[0][2] * src2[i0].element_[2] + src1.element_[0][3] * src2[i0].element_[3];\n00007FF69F123FB4 vshufps ymm26,ymm26,ymm27,39h \n00007FF69F123FBB vfmadd231ps ymm7,ymm26,ymm3 \n destination[i0].element_[1] = src1.element_[1][0] * src2[i0].element_[0] + src1.element_[1][1] * src2[i0].element_[1] + src1.element_[1][2] * src2[i0].element_[2] + src1.element_[1][3] * src2[i0].element_[3];\n00007FF69F123FC1 vfmadd231ps ymm8,ymm26,ymm17 \n destination[i0].element_[2] = src1.element_[2][0] * src2[i0].element_[0] + src1.element_[2][1] * src2[i0].element_[1] + src1.element_[2][2] * src2[i0].element_[2] + src1.element_[2][3] * src2[i0].element_[3];\n00007FF69F123FC7 vfmadd231ps ymm31,ymm26,ymm21 \n destination[i0].element_[3] = src1.element_[3][0] * src2[i0].element_[0] + src1.element_[3][1] * src2[i0].element_[1] + +src1.element_[3][2] * src2[i0].element_[2] + src1.element_[3][3] * src2[i0].element_[3];\n00007FF69F123FCD vfmadd231ps ymm6,ymm25,ymm26 \n00007FF69F123FD3 vpunpckldq ymm26,ymm7,ymm8 \n00007FF69F123FD9 vpunpckldq ymm27,ymm31,ymm6 \n00007FF69F123FDF vpunpckhdq ymm28,ymm7,ymm8 \n00007FF69F123FE5 vpunpckhdq ymm29,ymm31,ymm6 \n00007FF69F123FEB vpunpcklqdq ymm30,ymm26,ymm27 \n00007FF69F123FF1 vpunpckhqdq ymm26,ymm26,ymm27 \n00007FF69F123FF7 vpunpcklqdq ymm27,ymm28,ymm29 \n00007FF69F123FFD vpunpckhqdq ymm28,ymm28,ymm29 \n00007FF69F124003 vinsertf32x4 ymm29,ymm30,xmm26,1 \n00007FF69F12400A vmovups ymmword ptr [rcx+rax],ymm29 \n00007FF69F124011 vinsertf32x4 ymm29,ymm27,xmm28,1 \n00007FF69F124018 vmovups ymmword ptr [rcx+rax+20h],ymm29 \n00007FF69F124020 vshuff64x2 ymm26,ymm30,ymm26,3 \n00007FF69F124027 vmovupd ymmword ptr [rcx+rax+40h],ymm26 \n00007FF69F12402F vshuff64x2 ymm26,ymm27,ymm28,3 \n00007FF69F124036 vmovupd ymmword ptr [rcx+rax+60h],ymm26 \n for (uint32_t i0{}; i0 < vector_count; ++i0) {\n00007FF69F12403E sub rax,0FFFFFFFFFFFFFF80h \n00007FF69F124042 cmp rax,3A9800h \n00007FF69F124048 jne mat4_mul_vec4_cpp+1ACh (07FF69F123EFCh) \n }\n}\n00007FF69F12404E vmovaps xmm6,xmmword ptr [rsp] \n00007FF69F124053 vmovaps xmm7,xmmword ptr [rsp+10h] \n00007FF69F124059 vmovaps xmm8,xmmword ptr [rsp+20h] \n00007FF69F12405F add rsp,38h \n00007FF69F124063 vzeroupper \n00007FF69F124066 ret \nRun Code Online (Sandbox Code Playgroud)\n这是使用相同的源和目标向量数组时基准测试的调用代码:
\nint main() {\n SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS);\n\n SetThreadPriorityBoost(GetCurrentThread(), false);\n SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);\n\n mat4f_t matrix{ {0, 1, 2, 3,\n 4, 5, 6, 7,\n 8, 9, 10, 11,\n 12, 13, 14, 15} };\n\n vec4f_t* dst_vector = new vec4f_t[1\'000\'000]{};\n vec4f_t* src_vector = new vec4f_t[1\'000\'000]{};\n\n vec3f_t* dst_vector0 = new vec3f_t[1\'000\'000]{};\n vec3f_t* src_vector0 = new vec3f_t[1\'000\'000]{};\n\n for (uint32_t i0{}; i0 < 1000000; ++i0) {\n src_vector[i0] = vec4f_t{ (float)i0, (float)i0, (float)i0, (float)i0 };\n src_vector0[i0] = vec3f_t{ (float)i0, (float)i0, (float)i0 };\n }\n\n set_mxcsr0(0b1111\'1111\'1100\'0000);\n\n for (uint64_t i0{}; i0 < 30000; ++i0) {\n for (uint32_t i1{}; i1 < 16; ++i1) {\n reinterpret_cast<float*>(matrix.element_)[i1] = 1. / i0;\n }\n mat4_mul_vec4_avx512(src_vector, matrix, src_vector, 240000);\n mat4_mul_vec4_cpp(src_vector, matrix, src_vector, 240000);\n mat4_mul_vec3_avx512(src_vector0, matrix, src_vector0, 240000);\n mat4_mul_vec3_cpp(src_vector0, matrix, src_vector0, 240000);\n fps();\n }\n\n for (uint32_t i0{}; i0 < 1000000; ++i0) {\n std::cout << src_vector0[i0] << std::endl;\n std::cout << src_vector[i0] << std::endl;\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n当使用不同的源和目标数组进行测试时,src_vector/src_vector0 作为第一个参数将被 dst_vector/dst_vector0 替换。
\n这些是使用相同源和目标数组时的基准测试结果:
\n\n这些是使用不同源阵列和目标阵列时的基准测试结果:
\n不同的源/目标数组,Assembly 的性能几乎与 C++ 相同
\n这些基准测试是在配备第 11 代 i7-11850H Tiger Lake CPU 的 Windows 11 计算机上使用 Intel C++ Compiler 2024 以及如前所述的 Intel VTune Profiler 创建的。
\n有几件事我不明白:
\n为什么使用不同/相同的源阵列和目标阵列时速度会有所不同?\n在这种情况下与缓存有什么关系吗?
\n为什么在使用不同的源数组和目标数组时,C++ 对应版本甚至能在汇编变体附近达到如此好的性能?
\n感谢您的帮助。我真的很感激。
\n您的第一个 VTune 屏幕截图(源 = 目标版本)显示了运行 100% 标量运算的 cpp 版本(最右列),以及运行 100% 打包 SIMD FP 运算的单独目标情况。
自动矢量化通常会检查重叠,如果输出与任何输入重叠,则返回到标量循环,而不是制作专门用于 dst=src 的第三个版本(例如,如果仍然与 C++ 语义匹配,则可能首先加载所有内容)那种情况)。 因此它可能使用标量回退,因为它的检查检测到重叠。 (在总是具有不相交的 dst 和 src 的代码中,您可以使用__restrict承诺并优化这些检查。它可能会编译为在完美重叠 dst=src 情况下工作的 asm,但这仍然是 UB 所以不要这样做,它可能会在将来中断或使用不同的周围代码来内联。)
如果您的源特殊情况 dst=src 情况,则可能会自动矢量化。
还根据静态分析工具 uica 和 llvm-mca 速度快了数倍。uica.uops.info
事情并没有那么极端;uiCA 预测您的 asm 与 Tiger Lake 上的 LLVM 相比,速度可提高 1.75 倍。
float4)。通过完美的调度,后端端口吞吐量瓶颈为 13.33 个周期,但显然它预测太多的洗牌将被调度到端口 1 并从 MUL/FMA 窃取周期。float4float4)。正如预期的那样,如果加载/存储不是瓶颈,则无序执行可以毫无问题地跨迭代重叠工作,并且使用 FMA 使端口 0 饱和,使用洗牌使端口 5 饱和。索引寻址模式不是问题,因为您不将其用作 ALU 指令的内存操作数,仅用于纯加载/纯存储。float4,因此,如果另一个逻辑核心有一些可以在端口 1 和 6 上运行的标量整数工作,或者当该线程在加载/存储带宽上停止时,则对超线程更加友好。因此,在最好的无失速情况下,14.14/2 / 4.02 = 1.756速度比以周期/ 为单位float4。也许您错过了每次迭代因素的工作(查看指针增量和寻址模式偏移),或者您复制/粘贴了整个函数而不仅仅是内部循环?uiCA 将 asm 视为循环体,无论它是否以分支结尾,假设不采用任何较早的分支。
内存/缓存带宽瓶颈可能是限制因素,导致两者运行速度大致相同? 对于等效代码(与使用 256 位向量进行大量额外洗牌的情况不同),未对齐的数据可能会使使用 512 位向量的 Skylake Xeon 上的 DRAM 带宽比使用 256 位向量时差 15%(其中只有每隔一个负载/ store 是缓存行分割。)
源 = 目标更适合缓存占用空间,因为您只接触一半的数据。对于内存带宽来说更好:如果没有 NT 存储,如果数据在缓存中还不是热的,则写入成本为读+写,包括读取所有权以获取缓存行的旧值和独占所有权。(我不确定整个 64 字节缓存行的对齐存储是否有任何优化,例如是否可以在不执行 RFO 的情况下使其他副本无效,但不会像 NT 存储那样绕过/逐出缓存,而是像如何rep movsb/rep stosb微码有效。)无论如何,如果 DRAM 带宽是一个瓶颈,您会期望就地速度比复制快 1.5 倍,并以其他因素(例如混合读+写)为 DRAM 本身带来一些开销。
因此,就地情况可能会使您的汇编摆脱带宽瓶颈的束缚,并导致 C++ 在运行标量代码时面对植物,从而放大差异。
看起来您的代码每次都使用相同的矩阵,但使用不同的向量。然后将 4 个float4向量加载为一个 ZMM 向量。
通过在 MUL+FMA 之前进行加载+洗牌的软件流水线(实际上没有必要),所有具有 AVX-512 的 CPU 都具有相当深的乱序执行缓冲区,以找到跨迭代的指令级并行性。这是一个相当短的依赖链。
另外,您加载 + 洗牌最终向量,但不进行 FMA + 存储它。您可以从最后一次迭代的末尾剥离最后一组 MUL/FMA + 存储,并让循环计数器少执行一次迭代。
对于 Zen 4,您可以在cmp/jne指针上使用 on,而不是在单独的计数器上使用sub/ jnz。Zen 可以宏融合,cmp/jcc但不能sub/jcc。
看起来 ICX 展开后每次迭代处理 128 字节的数据,但它对每个输入向量执行大量的shuffle uops。每个乘法一个vpermps(或者vpermt2ps如果它需要从 2x YMM 而不是默认的 1x ZMM 中提取数据-mprefer-vector-width=256)应该更好,但它并没有发明除立即数之外的洗牌常量vshufps。
看起来它使用 16xvbroadcastss指令将矩阵的每个标量元素广播到单独的 YMM 寄存器。所以这需要洗牌来排列结果。
即使“客户端”芯片具有 1/时钟 ZMM FMA/mul/add 但 2/时钟 YMM,由于前端和后端执行端口吞吐量瓶颈,我预计您的 asm 会更快洗牌。即使您的数据未按 64 对齐,因此每次加载和存储都是缓存行拆分。(512 位向量使得对齐数据比 256 位向量更重要。)即使使用 512 位向量可能会降低时钟速度。
您可能不需要在asm 中编写此内容。_mm512_loadu_ps您可能会得到与和 等内在函数相同的汇编_mm512_fmadd_ps。 asm{}确实可以让您align 32针对该特定循环来帮助 uop 缓存,但 Tiger Lake 有一个有效的 LSD(循环缓冲区)。(Skylake-X 只有 uop 缓存,没有 LSD,而且它的 JCC 勘误表为循环底部创建了更多代码对齐坑洞。)
| 归档时间: |
|
| 查看次数: |
206 次 |
| 最近记录: |