单指令,多数据(SIMD)是使每个指令在小块或数据元素矢量上操作的概念。 CPU矢量指令集包括:x86 SSE和AVX,ARM NEON和PowerPC AltiVec。为了有效地使用SIMD指令,数据需要采用数组结构形式,并且应该在更长的流中发生。天真的“SIMD优化”代码通常比原始代码运行速度慢。
我发现很难找到在 Apple Silicon 上运行的良好、完整的汇编示例,特别是 SIMD 类型的操作,而不是不完整、过于通用的片段。 弗...
为什么 .NET 使用 SIMD 而不是 x87 来进行非 SIMD 固有的数学运算?
这首先是好奇心的问题。我正在查看这段代码反汇编(C#,64 位,发布模式,VS 2012 RC): 双a = 10d * Math.Log(20d, 2d); 000000c8月...
我大声尖叫。 这确实让你想知道。 我不敢想象如果我选择“优先考虑尺寸而不是速度”会发生什么。 设置:Visual Studio 2010 最大速度 我大声尖叫。 这真的让你想知道。 我不敢想象如果我选择“优先考虑尺寸而不是速度”会发生什么。 设置:Visual Studio 2010 <Optimization>MaxSpeed</Optimization> <IntrinsicFunctions>true</IntrinsicFunctions> <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed> <EnableEnhancedInstructionSet>StreamingSIMDExtensions2</EnableEnhancedInstructionSet> <FloatingPointModel>Precise</FloatingPointModel> 怎么样: for (i = 0; i < some_num; i++) { one += buf[i] * buf[i]; two += buf[i] * buf[off+i]; } 翻译成这样: 131: for (i = 0; i < some_num; i++) 132: { 133: one += buf[i] * buf[i]; 00404B40 movss xmm0,dword ptr [eax-4] 00404B45 movss xmm7,dword ptr [esp+18h] 00404B4B movss xmm2,dword ptr [eax] 00404B4F cvtps2pd xmm3,xmm2 00404B52 movss xmm4,dword ptr [eax+4] 00404B57 cvtps2pd xmm1,xmm0 00404B5A mulsd xmm3,xmm3 00404B5E movss xmm6,dword ptr [eax+8] 00404B63 mulsd xmm1,xmm1 00404B67 cvtps2pd xmm5,xmm4 00404B6A mulsd xmm5,xmm5 00404B6E cvtps2pd xmm7,xmm7 00404B71 addsd xmm1,xmm7 00404B75 cvtpd2ps xmm1,xmm1 00404B79 cvtss2sd xmm1,xmm1 00404B7D addsd xmm1,xmm3 00404B81 xorps xmm3,xmm3 00404B84 cvtpd2ps xmm1,xmm1 00404B88 cvtss2sd xmm1,xmm1 00404B8C addsd xmm1,xmm5 00404B90 cvtpd2ps xmm1,xmm1 00404B94 cvtss2sd xmm3,xmm1 134: two += buf[i] * buf[off+i]; 00404B98 cvtps2pd xmm0,xmm0 00404B9B cvtps2pd xmm2,xmm2 00404B9E cvtps2pd xmm1,xmm6 00404BA1 mulsd xmm1,xmm1 00404BA5 addsd xmm3,xmm1 00404BA9 xorps xmm1,xmm1 00404BAC cvtpd2ps xmm1,xmm3 00404BB0 cvtps2pd xmm5,xmm1 00404BB3 movss xmm1,dword ptr [eax+0Ch] 00404BB8 cvtps2pd xmm3,xmm1 00404BBB mulsd xmm3,xmm3 00404BBF addsd xmm5,xmm3 00404BC3 xorps xmm3,xmm3 00404BC6 cvtpd2ps xmm3,xmm5 00404BCA cvtps2pd xmm5,xmm3 00404BCD movss xmm3,dword ptr [eax+10h] 00404BD2 cvtps2pd xmm3,xmm3 00404BD5 mulsd xmm3,xmm3 00404BD9 addsd xmm5,xmm3 00404BDD xorps xmm3,xmm3 00404BE0 cvtpd2ps xmm3,xmm5 00404BE4 cvtps2pd xmm5,xmm3 00404BE7 movss xmm3,dword ptr [eax+14h] 00404BEC cvtps2pd xmm3,xmm3 00404BEF mulsd xmm3,xmm3 00404BF3 addsd xmm5,xmm3 00404BF7 xorps xmm3,xmm3 00404BFA cvtpd2ps xmm3,xmm5 00404BFE cvtps2pd xmm5,xmm3 00404C01 movss xmm3,dword ptr [eax+18h] 00404C06 cvtps2pd xmm3,xmm3 00404C09 mulsd xmm3,xmm3 00404C0D addsd xmm5,xmm3 00404C11 xorps xmm3,xmm3 00404C14 cvtpd2ps xmm3,xmm5 00404C18 movss dword ptr [esp+18h],xmm3 00404C1E movss xmm3,dword ptr [ecx-4] 00404C23 cvtps2pd xmm3,xmm3 00404C26 mulsd xmm3,xmm0 00404C2A movss xmm0,dword ptr [esp+10h] 00404C30 cvtps2pd xmm0,xmm0 00404C33 addsd xmm3,xmm0 00404C37 xorps xmm0,xmm0 00404C3A cvtpd2ps xmm0,xmm3 00404C3E movss xmm3,dword ptr [ecx] 00404C42 cvtps2pd xmm0,xmm0 00404C45 cvtps2pd xmm3,xmm3 00404C48 mulsd xmm2,xmm3 00404C4C addsd xmm0,xmm2 00404C50 movss xmm2,dword ptr [ecx+4] 00404C55 cvtpd2ps xmm0,xmm0 00404C59 cvtss2sd xmm0,xmm0 00404C5D cvtps2pd xmm2,xmm2 00404C60 cvtps2pd xmm3,xmm4 00404C63 mulsd xmm2,xmm3 00404C67 addsd xmm0,xmm2 00404C6B movss xmm2,dword ptr [ecx+8] 00404C70 cvtpd2ps xmm0,xmm0 00404C74 cvtss2sd xmm0,xmm0 00404C78 cvtps2pd xmm2,xmm2 00404C7B cvtps2pd xmm1,xmm1 00404C7E cvtps2pd xmm3,xmm6 00404C81 mulsd xmm2,xmm3 00404C85 addsd xmm0,xmm2 00404C89 movss xmm2,dword ptr [ecx+0Ch] 00404C8E cvtpd2ps xmm0,xmm0 00404C92 cvtss2sd xmm0,xmm0 00404C96 cvtps2pd xmm2,xmm2 00404C99 mulsd xmm2,xmm1 00404C9D addsd xmm0,xmm2 00404CA1 cvtpd2ps xmm0,xmm0 00404CA5 xorps xmm1,xmm1 00404CA8 cvtss2sd xmm1,xmm0 00404CAC movss xmm0,dword ptr [ecx+10h] 00404CB1 cvtps2pd xmm2,xmm0 00404CB4 movss xmm0,dword ptr [eax+10h] 00404CB9 cvtps2pd xmm0,xmm0 00404CBC mulsd xmm2,xmm0 00404CC0 addsd xmm1,xmm2 00404CC4 xorps xmm0,xmm0 00404CC7 cvtpd2ps xmm0,xmm1 00404CCB add eax,20h 00404CCE add ecx,20h 00404CD1 cvtps2pd xmm1,xmm0 00404CD4 movss xmm0,dword ptr [ecx-0Ch] 00404CD9 cvtps2pd xmm2,xmm0 00404CDC movss xmm0,dword ptr [eax-0Ch] 00404CE1 cvtps2pd xmm0,xmm0 00404CE4 mulsd xmm2,xmm0 00404CE8 addsd xmm1,xmm2 00404CEC xorps xmm0,xmm0 00404CEF cvtpd2ps xmm0,xmm1 00404CF3 xorps xmm1,xmm1 00404CF6 cvtps2pd xmm1,xmm0 00404CF9 movss xmm0,dword ptr [ecx-8] 00404CFE xorps xmm2,xmm2 00404D01 cvtps2pd xmm2,xmm0 00404D04 movss xmm0,dword ptr [eax-8] 00404D09 cvtps2pd xmm0,xmm0 00404D0C mulsd xmm2,xmm0 00404D10 addsd xmm1,xmm2 00404D14 xorps xmm0,xmm0 00404D17 cvtpd2ps xmm0,xmm1 00404D1B movss dword ptr [esp+10h],xmm0 00404D21 cmp eax,offset buf+84h (42D6A4h) 00404D26 jl gem+290h (404B40h) 135: } 答案是肯定的。 Visual Studio 目前不支持代码矢量化。如果您查看程序集,就会发现这些都是标量 SSE 指令。而且你的循环显然是可矢量化的。 您将必须使用矢量化编译器才能获得更好的结果。或者使用内在函数自行发出向量 SSE 指令。 http://software.intel.com/sites/products/documentation/studio/composer/en-us/2011/compiler_c/intref_cls/common/intref_bk_intro.htm 您可以尝试的另一件事是: 将浮点模式更改为“快速”而不是“精确”。编译器将中间体提升为双精度并将它们转换回来 - 这增加了很多开销。 看起来您在表达式中混合了单精度和双精度类型,这会导致大量不必要的转换。如果你解决这个问题,那么代码应该更小、更高效。 您也可以使用更好的编译器,例如英特尔的 ICC,它很可能能够矢量化此循环,正如 @Mysticial 已经建议的那样。 还有一点 - 我没有太仔细地研究代码,但看起来循环已经展开,所以实际上它可能比最初看起来更有效。 注意这一点: 00404CCB add eax,20h 00404CCE add ecx,20h 循环已展开,可一次处理 8 个 i 值。
如何在 C (AVX2) 中向量化 int8 数组与 int16 常量的乘法,并扩展到 int32 结果数组
如何使用 AVX2 向量化这个 C 函数? static void propogate_neuron(const Short a, const int8_t *b, int *c) { 对于 (int i = 0; i < 32; ++i){ c[i] += a * b[i]; } } (Relat...
如何有效地使用SIMD来统计大型单词搜索网格(包括垂直和对角线)中的4个字符匹配?
在 2024 年代码出现的第 4 天,存在一个问题,您需要查找字符网格中包含多少个“XMAS”字符串,例如 MMMSXXMASM MSAMXMSMSA AMXSXMAAMM 玛萨玛斯...
我正在将使用 SSE2 内在函数编写的矢量化代码迁移到 AVX2 内在函数。 令我失望的是,我发现字节移位指令 _mm256_slli_si256 和 _mm256_srli_si256 运行
SIMD shuffle 可以将哪些执行端口用于 AVX2 和 NEON?
在查看英特尔优化参考手册时,我注意到以下部分:处理端口 5 压力。 它基本上表示 Sandy Bridge 微架构中的端口 5 包括随机播放单元,这些单元的频率...
SIMD 指令可以用于 AVX2 和 NEON 的哪些执行端口?
在查看英特尔优化参考手册时,我注意到以下部分:处理端口 5 压力。 它基本上表示 Sandy Bridge 微架构中的端口 5 包括随机播放单元,这些单元的频率...
在 x86-64 SIMD 指令名称以及可用于从 C/C++ 访问它们的内在函数中,您会发现术语 shuffle(例如 _mm_shuffle_epi32)和 permute(例如 _mm_permute_...
当用 gcc 编译时,函数 sum_of_squares 返回 0。我做错了什么还是这是一个 gcc bug?我知道我不会处理 n 不能被 8 整除的情况。 #包括 用 gcc 编译时,函数 sum_of_squares 返回 0。我做错了什么还是这是 gcc bug?我知道我不会处理 n 不能被 8 整除的情况。 #include <stdio.h> #include <x86intrin.h> int sum_of_squares(int x[], int n) { int sum = 0; __m256i sum8 = _mm256_set1_epi32(0); for (int i = 0; i < n; i += 8) { __m256i x8 = _mm256_load_si256((__m256i *)&x[i]); x8 = _mm256_mul_epi32(x8, x8); sum8 = _mm256_add_epi32(x8, sum8); } int *_sum = (int *)&sum8; for (int i = 0; i < 8; i++) sum += _sum[i]; return sum; } int main() { _Alignas(32) int x[16]; for (int i = 0; i < 15; i++) { x[i] = i; } printf("%d", sum_of_squares(x, 16)); } 将 int* 指向 __m256i(long long 元素的 GNU C 向量)违反了严格别名规则。 因此,GCC14.2 -O2 将您的函数优化为 return 0;。 (xor eax,eax / ret). 我们可以通过使用 -fno-strict-aliasing 并看到非零结果来证明这是问题所在。 (在 Godbolt 上查看两者)不幸的是 -fsanitize=undefined 没有捕获此错误(或 x[] 的未初始化的最终元素)。 但一般来说,当您检查 asm 并看到您的函数优化为 return 0; 时,这通常意味着您要么返回了错误的变量,要么存在编译时可见的 UB。 (有时编译器甚至不设置返回值,甚至省略 ret 指令,因此执行会陷入接下来的情况。) 使用 _mm256_storeu_si256 到 int tmparr[8];(或者对齐 tmp 数组并使用 _mm256_store_si256)。 或者更好,请参阅 使用 AVX512 或 AVX2 计算所有打包 32 位整数之和的最快方法(一般来说 进行水平 SSE 向量和(或其他缩减)的最快方法) - 洗牌将低半部分向下与低半部分对齐并进行垂直 SIMD 加法,每一步减少一半的元素数量,直到你只剩下一个了。 (如果您避免使用 UB,GCC 实际上会将您的归约循环优化为一系列洗牌和添加。) 测试用例中其他不相关的错误 正如 Soonts 提到的,您还需要 vpmulld (_mm256_mullo_epi32) 非展宽 32 位乘法,而不是 vpmuldq (_mm256_mul_epi32) 展宽有符号乘法,后者仅读取偶数元素以产生 64 位结果。 你的严格别名错误就是你得到零的原因;解决这个问题对于获得您想要的非零结果也是必要的,而不仅仅是偶数元素(0,2等)的总和。 您还应该初始化第 16 个元素 (x[15]),正如 Soonts 指出的那样,您当前的代码无法做到这一点。 __attribute__((may_alias)) 让您可以键入 int 的版本,您可以指向任何东西 如果你真的想要,你可以使用 typedef int32_t aliasing_i32 __attribute__((may_alias)),你可以安全地指向任何对齐 4 或更多的东西,使用它代替 int 作为你的指针类型。 (或者如果您使用 __attribute__((may_alias, aligned(1))),则不对齐)。 有趣的事实:GCC 和 Clang 对 __m256i 的定义使用 may_alias,允许您将 __m256i* 指向任何东西,但反之则不然。 但这并没有什么好处;数组不占用“额外”存储空间; __m256i向量优化为仅YMM寄存器。 无论如何,归约循环都会优化为随机播放,但如果没有,无论哪种编写方式都会有 32 字节的堆栈空间,该空间由 vmovdqa 写入并由标量循环读取。 x8 = _mm256_mul_epi32(x8, x8); 指导并没有达到你期望的效果。它仅使用每个 64 位通道中的最低 32 位,并计算 4 个乘积,每个乘积 64 位。 要计算 8 个 32 位乘积,请考虑使用 _mm256_mullo_epi32。 for (int i = 0; i < 15; i++) 请注意,您仅初始化 15 个数字,然后加载 16 个。 int *_sum = (int *)&sum8; 这可行,但通常会编译成缓慢的代码,矢量存储然后标量加载。 要计算向量中所有数字的水平总和,请参阅答案。 #include <stdio.h> #include <x86intrin.h> int sum_of_squares(int x[], int n) { if( 0 != (n+1)%8 ) { // Here n needs to be multiples of 8 - 1 printf( "sum_of_squares data size parameter needs to be multiples of 8-1\n"); printf( "data size n: %d n-1: %d\n", n, n-1); printf( "Allowed values for n: 7, 15, 23, ...\n"); return -1; } int sum = 0; __m256i sum8 = _mm256_set1_epi32(0); for (int i = 0; i < n; i += 8) { __m256i initVal = _mm256_load_si256((__m256i *)&x[i]); __m256i x8 = _mm256_load_si256((__m256i *)&x[i]); // x8 = _mm256_mul_epi32(x8, x8); x8 = _mm256_mullo_epi32( initVal, x8); sum8 = _mm256_add_epi32(x8, sum8); } int *_sum = (int *)&sum8; for (int i = 0; i < 8; i++) sum += _sum[i]; return sum; } int main() { { _Alignas(32) int x[8]; for (int i = 0; i < 8; i++) { x[i] = i; } printf( "sum_of_squares of 0^2+1^2+2^2+..7^2 elements: %d\n", sum_of_squares(x, 8-1)); int n = 8-1; printf( "%d*(%d+1)*(2*%d+1)/6 = %d*%d*(%d+1)/6 = %d*%d*%d/6 = %d\n", n, n, n, n, n+1, 2*n, n, n+1, 2*n+1, n*(n+1)*(2*n+1)/6 ); } { _Alignas(32) int x[16]; for (int i = 0; i < 16; i++) { x[i] = i; } printf( "sum_of_squares of 0^2+1^2+2^2+..15^2 elements: %d\n", sum_of_squares(x, 16-1)); int n = 16-1; printf( "%d*(%d+1)*(2*%d+1)/6 = %d*%d*(%d+1)/6 = %d*%d*%d/6 = %d\n", n, n, n, n, n+1, 2*n, n, n+1, 2*n+1, n*(n+1)*(2*n+1)/6 ); } { _Alignas(32) int x[24]; for (int i = 0; i < 24; i++) { x[i] = i; } printf( "sum_of_squares of 0^2+1^2+2^2+..23^2 elements: %d\n", sum_of_squares(x, 24-1)); int n = 24-1; printf( "%d*(%d+1)*(2*%d+1)/6 = %d*%d*(%d+1)/6 = %d*%d*%d/6 = %d\n", n, n, n, n, n+1, 2*n, n, n+1, 2*n+1, n*(n+1)*(2*n+1)/6 ); } { _Alignas(32) int x[4]; for (int i = 0; i < 4; i++) { x[i] = i; } printf( "sum_of_squares of 0^2+1^2+2^2+..23^2 elements: %d\n", sum_of_squares(x, 4-1)); int n = 4-1; printf( "%d*(%d+1)*(2*%d+1)/6 = %d*%d*(%d+1)/6 = %d*%d*%d/6 = %d\n", n, n, n, n, n+1, 2*n, n, n+1, 2*n+1, n*(n+1)*(2*n+1)/6 ); } { _Alignas(32) int x[13]; for (int i = 0; i < 13; i++) { x[i] = i; } printf( "sum_of_squares of 0^2+1^2+2^2+..23^2 elements: %d\n", sum_of_squares(x, 13-1)); int n = 13-1; printf( "%d*(%d+1)*(2*%d+1)/6 = %d*%d*(%d+1)/6 = %d*%d*%d/6 = %d\n", n, n, n, n, n+1, 2*n, n, n+1, 2*n+1, n*(n+1)*(2*n+1)/6 ); } // I feel better to use mathematical formula instead of wasting memories at RAM and process handlers. return 0; } /* $ /usr/bin/gcc.exe -mavx2 -g -Wall sum_of_squares.c -o ./a.out $ ./a.out sum_of_squares of 0^2+1^2+2^2+..7^2 elements: 140 7*(7+1)*(2*7+1)/6 = 7*8*(14+1)/6 = 7*8*15/6 = 140 sum_of_squares of 0^2+1^2+2^2+..15^2 elements: 1240 15*(15+1)*(2*15+1)/6 = 15*16*(30+1)/6 = 15*16*31/6 = 1240 sum_of_squares of 0^2+1^2+2^2+..23^2 elements: 4324 23*(23+1)*(2*23+1)/6 = 23*24*(46+1)/6 = 23*24*47/6 = 4324 sum_of_squares data size parameter needs to be multiples of 8-1 data size n: 3 n-1: 2 Allowed values for n: 7, 15, 23, ... sum_of_squares of 0^2+1^2+2^2+..23^2 elements: -1 3*(3+1)*(2*3+1)/6 = 3*4*(6+1)/6 = 3*4*7/6 = 14 sum_of_squares data size parameter needs to be multiples of 8-1 data size n: 12 n-1: 11 Allowed values for n: 7, 15, 23, ... sum_of_squares of 0^2+1^2+2^2+..23^2 elements: -1 12*(12+1)*(2*12+1)/6 = 12*13*(24+1)/6 = 12*13*25/6 = 650 */
当用 gcc 编译时,函数 sum_of_squares 返回 0。我做错了什么还是这是一个 gcc bug?我知道我不会处理 n 不能被 8 整除的情况。 #包括 用 gcc 编译时,函数 sum_of_squares 返回 0。我做错了什么还是这是 gcc bug?我知道我不会处理 n 不能被 8 整除的情况。 #include <stdio.h> #include <x86intrin.h> int sum_of_squares(int x[], int n) { int sum = 0; __m256i sum8 = _mm256_set1_epi32(0); for (int i = 0; i < n; i += 8) { __m256i x8 = _mm256_load_si256((__m256i *)&x[i]); x8 = _mm256_mul_epi32(x8, x8); sum8 = _mm256_add_epi32(x8, sum8); } int *_sum = (int *)&sum8; for (int i = 0; i < 8; i++) sum += _sum[i]; return sum; } int main() { _Alignas(32) int x[16]; for (int i = 0; i < 15; i++) { x[i] = i; } printf("%d", sum_of_squares(x, 16)); } x8 = _mm256_mul_epi32(x8, x8); 指导并没有达到你期望的效果。它仅使用每个 64 位通道中的最低 32 位,并计算 4 个乘积,每个乘积 64 位。 要计算 8 个 32 位乘积,请考虑使用 _mm256_mullo_epi32。 for (int i = 0; i < 15; i++) 请注意,您仅初始化 15 个数字,然后加载 16 个。 int *_sum = (int *)&sum8; 这可行,但通常会编译成缓慢的代码,矢量存储然后标量加载。 要计算向量中所有数字的水平总和,请参阅答案。
将ARM NEON中每个字节的高位打包,像AVX512 vpmovb2m一样为64字节?
__builtin_ia32_cvtb2mask512() 是 vpmovb2m k、zmm 的 GNU C 内置函数。 它的 Intel 内在函数是 _mm512_movepi8_mask。 它从每个字节中提取最高有效位,产生一个整数......
为什么对于 RGB 到灰度,SIMD 只提高一点点性能,SIMD 乘法但向量元素的标量加法?
我正在学习如何使用SIMD进行图像处理。但是,我想知道为什么使用SIMD后性能没有太大改善。 图片尺寸:3840*2160 图片格式:PixelFormat。
在 C# 中,我将 RGB 图像数据存储在 byte[] 数组 ([r, g, b, r, g, b, ...]) 中,并尝试将其转换为灰度。我正在 C#(使用指针)和...
sse4.2 _mm_cmpistrm/_mm_cmpesrm 指令得到错误结果
我想使用下面的代码来计算数组a和数组b的交集: #包括 #包括 #包括 无效测试(uint16_t *a,uint16_t *b...
AVX(2)/SIMD 方式获取/设置(至 1)256 位寄存器中的单个位
当前但hacky的方法是这样的: __m256i 位集(__m256i 源,uint8_t 索引){ uint8_t pos_in_64 = 索引 % 64; uint8_t位置=索引/64; uint64_t 位掩码 = 1ULL << pos_i...
c++ 如何编写编译器可以轻松针对 SIMD 进行优化的代码?
我正在使用 Visual Studio 2008,在项目设置中我看到“激活扩展指令集”选项,我可以将其设置为 None、SSE 或 SSE2 所以编译器会尝试批处理...
了解 Clang 的 SIMD 优化,用于将 float 乘以 int 循环计数器
给定以下函数 void foo(float* 结果, int 大小, float y, float delta) { 对于 (int t = 0; t < size; ++t) { result[t] = y + delta * t; } } Clang with -O2 generates the
GCC 可以自动“向量化循环”。 是否还有其他广泛可用的编译器可以执行相同的操作?
我想在SIMD中优化以下代码 拍:[1,2,3,4] 数据:[1,1,3,3] mask: [1, 0, 1, 1] # 1表示相等,0表示可选/不关心 结果:[1,1,1,0] 天真的算法...