进行水平 SSE 向量和(或其他简化)的最快方法

2024-04-01

给定一个由三个(或四个)浮点数组成的向量。对它们求和的最快方法是什么?

SSE(movaps、shuffle、add、movd)总是比 x87 快吗? SSE3 中的水平相加指令值得吗?

转移到 FPU,然后是 faddp、faddp 的成本是多少?最快的具体指令序列是什么?

“尝试安排一些事情,以便一次可以对四个向量求和”将不会被接受作为答案。 :-) 例如为了对数组求和,您可以使用多个向量累加器进行垂直求和(以隐藏 addps 延迟),并在循环后减少到一个,但随后您需要对最后一个向量进行水平求和。


一般来说,对于任何类型的向量水平缩减,提取/洗牌高半部分以与低部分对齐,然后垂直相加(或最小/最大/或/和/异或/乘/其他);重复直到 a 只有一个元素(向量的其余部分有大量垃圾)。

如果您从宽度超过 128 位的向量开始,将其缩小一半,直到达到 128 位(然后您可以在该向量上使用此答案中的函数之一)。但如果您需要将结果广播到最后的所有元素,那么您可以考虑一路进行全角洗牌。

更广泛的向量和整数的相关问答,以及FP

  • __m128 and __m128d这个答案(见下文)

  • __m256d对 Ryzen 1 与 Intel 进行性能分析(说明原因vextractf128vperm2f128) 使用 SSE/AVX 获取 __m256d 中存储的值的总和 https://stackoverflow.com/questions/49941645/get-sum-of-values-stored-in-m256d-with-sse-avx

  • __m256 如何水平求和__m256? https://stackoverflow.com/questions/13219146/how-to-sum-m256-horizontally

  • Intel AVX:双精度浮点变量点积的 256 位版本 https://stackoverflow.com/questions/10454150/intel-avx-256-bits-version-of-dot-product-for-double-precision-floating-point-v/47445367#47445367的单个向量。

  • 的点积arrays(不仅仅是 3 或 4 个元素的单个向量):进行垂直 mul/add 或 FMA 到多个累加器 https://stackoverflow.com/questions/45113527/why-does-mulss-take-only-3-cycles-on-haswell-different-from-agners-instruction,最后还有 hsum。完整的 AVX+FMA 数组点积示例 https://stackoverflow.com/questions/59494745/avx2-computing-dot-product-of-512-float-arrays,包括一个高效的 hsumafter循环。 (对于数组的简单求和或其他缩减,请使用该模式但不使用乘法部分,例如使用 add 而不是 fma)。做not为每个 SIMD 向量分别进行水平工作;最后做一次。

    如何使用 SIMD 计算字符出现次数 https://stackoverflow.com/questions/54541129/how-to-count-character-occurrences-using-simd作为一个整数计数的例子_mm256_cmpeq_epi8再次匹配整个数组,仅在最后进行求和。 (值得特别提及的是,先进行一些 8 位累加,然后扩大 8 -> 64 位以避免溢出,而此时无需执行完整的 hsum。)

Integer

  • __m128i32 位元素:这个答案(见下文)。 64 位元素应该是显而易见的:只有一个 pshufd/paddq 步骤。

  • __m128i 8 位无符号uint8_t没有换行/溢出的元素:psadbw https://www.felixcloutier.com/x86/psadbw反对_mm_setzero_si128(),然后对两个 qword 半部进行 hsum(对于更宽的向量,则为 4 或 8)。水平求和 SSE 无符号字节向量的最快方法 https://stackoverflow.com/questions/36998538/fastest-way-to-horizontally-sum-sse-unsigned-byte-vector显示带有 SSE2 的 128 位。使用 AVX 内在函数对 __m512i 中的 8 位整数求和 https://stackoverflow.com/questions/55296777/summing-8-bit-integers-in-m512i-with-avx-intrinsics有一个 AVX512 示例。如何使用 SIMD 计算字符出现次数 https://stackoverflow.com/q/54541129有一个AVX2__m256i例子。

    (For int8_t有符号字节您可以异或 set1_epi8(0x80) 在 SAD 之前翻转为无符号,然后从最终的 hsum 中减去偏差;看详细信息在这里 https://stackoverflow.com/questions/70370454/how-to-horizontally-sum-signed-bytes-in-xmm,还显示了仅从内存中执行 9 个字节而不是 16 个字节的优化)。

  • 16 位无符号:_mm_madd_epi16set1_epi16(1) 是单微指令加宽水平加法:SIMD:累积相邻对 https://stackoverflow.com/questions/55057933/simd-accumulate-adjacent-pairs。然后继续处理 32 位 hsum。

  • __m256i and __m512i具有 32 位元素。使用 AVX512 或 AVX2 计算所有打包 32 位整数之和的最快方法 https://stackoverflow.com/questions/60108658/fastest-method-to-calculate-sum-of-all-packed-32-bit-integers-using-avx512-or-av。对于 AVX512,英特尔添加了一堆“减少”内联函数(不是硬件指令)来为您执行此操作,例如_mm512_reduce_add_ps(以及 pd、epi32 和 epi64)。还减少_min/max/mul/和/或。手动执行会得到基本相同的 asm。

  • 水平最大值(而不是相加):使用 SSE 获取 __m128i 向量中的最大值? https://stackoverflow.com/questions/9877700/getting-max-value-in-a-m128i-vector-with-sse/9878321#9878321


主要回答this问题:大部分是浮动的__m128

以下是一些基于调整的版本Agner Fog 的微架构指南 http://agner.org/optimize/的微架构指南和说明表。另请参阅x86 /questions/tagged/x86标签维基。它们在任何 CPU 上都应该高效,没有重大瓶颈。 (例如,我避免了那些对一个 uarch 有一点帮助但对另一个 uarch 来说很慢的事情)。代码大小也被最小化。

通用SSE3/SSSE3 2xhadd惯用语仅适用于代码大小,而不适用于任何现有 CPU 的速度。它有一些用例(例如转置和添加,见下文),但单个向量不是其中之一。

我还包含了 AVX 版本。任何类型的 AVX / AVX2 水平缩减都应该从vextractf128以及“垂直”操作以减少到 1 个 XMM (__m128)向量。一般来说,对于宽向量,最好的选择是反复缩小一半,直到缩小到 128 位向量,无论元素类型如何。 (除8位整数外,则vpsadbw如果您想对 hsum 进行求和而不溢出到更宽的元素,请作为第一步。)

查看所有这些代码的 asm 输出关于 Godbolt 编译器资源管理器 http://gcc.godbolt.org/#compilers:!((compiler:g6,options:%27-xc+-O3+-Wall+-fverbose-asm+-march%3Dhaswell+-mno-avx%27,sourcez:MQSwdgxgNgrgJgUwAQB4QFt3gC4CdwB0AFgHwBQoksiqAztnDseWQPStLZEi1I9JQQAa2QBZAIa5x2AOS0ANEgBGMbJwDuCcSLicA9kgh70ABxBRk2A0oTZsCXIb2ICbDtHFgA5khhgAZnq42H7SCFAAnkiIECCIvFa%2BtMjoegBu4ia0rLREMP4muuD0Wrp6/kipaURQWWT%2BUHrSSES0MOgA%2BlkdtMkAjAAUHR3ofQBMABxIaQCUSADeSEvLK6tr62vs00gAvEgA2kgAIkgAwkgAPkgAQkgAgkgAumQrw6OTSLn5y3sjnV/%2BBoILq0AZpRTgpAdUSiDoAZQAEgBVABiKIAMgBRAZjRQAZkUAAZFH0ZjMANxLLaHc4nK63B7PV4jcZTNroXhLX6YDriOBwEFgxQAimbDjs3h7Q5HADU51OMrpNxlDzuMtuTOWAI2XKhPKqCBqgoBwvatFFqy2B2W5yWStl8sVTypHAsai4yCMpnMDiQ4jSejifsq6WUUVwCBgtHAPgBLy1Zp1uz1nT5At6Awlwry/gp8aWEZCuDArx5EDS2F6HX8eLGmbNeYAvmR6o1mq12iCesk8UMWR9Zgt81D%2B2yc8m/iN0oa4DATILZpT1lslLgmnAIOJ6Ehwgh0AgwJWkAS%2BvokLjCcO3qzPonuan%2BQvs/k8yttet71O0oaoMac6aOVFLZuC8IgWnEKAKgAWhIAQ9HUcDIOHCUNk/NNu3rDln1zclh0LGBiw2Sdy0rWhq1rTDzVw5sWy2Wh0AgixHCMGhowALwQRQVDUKMYEYqJaEaTRHD8CxeicRAoPY5B%2BHEJATDXJQLHQVsmjUDtOm6Ig0z7d4pkHeYr1HW8OQnHltMfLIhWmV8E1Mj8Uw6CyBSsrMJVsgtbAIkt1mIisqxrOt3OoltQH8RAKmGO4ADUAA1hlU9t2TGABWAA2Lt/QAD101K0psodmT06YhN1P48o6Td6CyCqslZMEPJHYrqhAUCzPQCqECyvBxAgbB/FZJ8kFJSlgNasDWSM5qb3a3lLNBNIhIhECiAtZYtjTTgiGQUrJpWfDCI0rtegQXs0lZNakw2sAijAQQwEsbbPh7aYHGjPQwEUdRuAgMD%2BD0ExsAwCCkECRwYti4ctgGMADHULd7Fu%2BhcBgPqQA%2BhQ/Ruv0oCgJByi2vc/QjQmkAAFluLAwAwdoZjIZtgAPRh/BbNx2HZrY4D0GAlIQMguZ5iwWnZLp02SOtr0mXQ0jgOZFiTBWtjSZNDluK5GSmm8/AisqeW1hB/HABAXNBS6FY2TmPpkNR1CCXAIi%2B5APsiJJkDTOEjkUB7v0cKxUe4bxSa8SQlHELxkCUEAj3UKOwM8JAUQABT9flNY%2BAFsFMT8DSNKz9f8RRiIRwoF1li0tg9En%2BFhkNqlqOA04mXR30/KrsG6QpMxzTOTEag6fL8ysBX8NKyaGHl0NoOAwTgbCyRC2iOY5vhD1rMgcGFzsEDMWtuwQCXRxAJAsrligQHCg2RwhhKitZI/uFH3XOj8ExeqEbgOm3kBR4GLLFBPpcOoth4iggDBwnhdCwzAFBeIeBUZA2/PcOKAhbC8CjJ8f0bswz2CQDHLg3M1APWNjGYMVQKDhGSI3e%2B38yZPx6DmIEn8d51j/lCGE8JkRoixAMPoRJ8SKDGPPchN1z5UJMo/NCj4v4UQfmTf%2BjVJYTGobWOhAIgSNCYSAPoaVMKj0LuwxEqIMTYl4UgYkx4BHzxWFsOE8MTCk1KtgW2O5lIHkrGI9kKjJECmkUFdoeiWggFrH3LyhFB7RkGtGCinjBGAOsRwOEcJMRjFrg3eJ/dHJdR6n1TRMioBEjNjYpJcidzfhLPgmAbUo5OG9GJM8VQkb2D5PjCoclBD2CkHjEwWTcC6FwLWeQWVMDyEvDRDeR1fHdh7D0ISPQGK4xYggXSd9j4zEMisLKs1nK5JYfI3CGytnoV8b/PZeFQklnCVoyYPQgksKbKzReKdMiI1BmudASAJBSFkLwCGLQ7AmAAFzsHoG/acuAGjwQIF6VgABHGACB6DozANkPotY%2BgAE4%2BhkzSqwIg8EoJWCku0KCHV0pQTxfgNiH1sD8VYKisYDKyZ9EJMAeljLmWJXUiLboDEvnLI%2BCfQqywjDIrUIokyABFPiug9hZX2cKjGYrjKNCOHxPGex2RSr5PKl0gTVUgz2AMJAUFFAmuPniJAMpj4AHZ/4pKtVlfKdMVgiu3OK7g%2Br1WORzr%2BVy7QtWz0ldKxqdF2ieuTEas1ZqsqngdRah1KVLU2v/oSJNWV7XH1oQ6p1w5XVKuKuycN3jBQqrVYoD1aqQ2ugMIa41pr63H1TQ6jNWUs3HxzS6xVTUbyNGTIWtVOqkBjQjXW0d0bY3mrTYmh11qkDOoVaK7tHxuCzTURYY0Yay0mU9USGNVaTIjqjQ2rKTbj4TvTWm%2BNmap1pvyjOuduau3ivZLNSeoJGjlpAOXDg6EsjRANlob5hgoCeC8HIT4DChYAyBlgDiuAzlFguWWfyZFAqYXuUAA%3D%3D)),filterAsm:(binary:!t,commentOnly:!t,directives:!t,intel:!t,labels:!t),version:3.另请参阅我的改进Agner Fog 的 C++ 矢量类库 http://www.agner.org/optimize/#vectorclass horizontal_add功能。 (留言板主题 http://www.agner.org/optimize/vectorclass/read.php?i=124,并编写代码github https://github.com/pcordes/vectorclass/commits/master)。我使用 CPP 宏为 SSE2、SSE4 和 AVX 的代码大小选择最佳洗牌,并避免movdqa当 AVX 不可用时。


有一些权衡需要考虑:

  • 代码大小:出于 L1 I-cache 的原因以及从磁盘获取代码(较小的二进制文件)的原因,越小越好。二进制总大小对于整个程序中重复做出的编译器决策至关重要。如果您费心使用内在函数手动编写某些内容,那么如果可以提高速度,那么花费一些代码字节是值得的对于整个程序(要小心使展开看起来不错的微基准)。
  • uop-cache 大小:通常比 L1 I$ 更珍贵。 4 个单微操作指令占用的空间少于 2 个haddps,所以这在这里非常相关。
  • 延迟:有时相关
  • 吞吐量(后端端口):通常不相关,水平总和不应该是最内循环中的唯一内容。端口压力仅作为包含该压力的整个回路的一部分才重要。
  • 吞吐量(总前端融合域 uops):如果周围的代码在 hsum 使用的同一端口上没有出现瓶颈,则这是 hsum 对整个吞吐量的影响的代理。

当水平添加不频繁时:

CPUs 没有 uop 缓存可能会喜欢 2xhaddps如果很少使用:运行时速度很慢,但并不常见。只有 2 条指令可以最大限度地减少对周围代码(I$ 大小)的影响。

CPUs 带有 uop 缓存可能会喜欢需要更少微指令的东西,即使它有更多的指令/更多的 x86 代码大小。使用的 uop 缓存行总数是我们想要最小化的,这并不像最小化 uop 总数那么简单(采用的分支和 32B 边界总是启动一个新的 uop 缓存行)。

无论如何,话虽如此,横向总和得出lot,所以这是我精心制作一些编译良好的版本的尝试。没有在任何真实硬件上进行基准测试,甚至没有经过仔细测试。洗牌常量或其他内容可能存在错误。


如果您正在制作代码的后备/基线版本,请记住只有旧的 CPU 才能运行它;较新的 CPU 将运行您的 AVX 版本或 SSE4.1 或其他版本。

K8、Core2(merom) 及更早版本等旧版 CPU 仅具有 64 位随机单元。 Core2 具有适用于大多数指令的 128 位执行单元,但不适用于洗牌。 (Pentium M 和 K8 将所有 128b 向量指令作为两个 64 位一半处理)。

随机播放像movhlps以 64 位块的形式移动数据(64 位半块内不进行混洗)的速度也很快。

相关:新 CPU 上的 shuffle,以及避免 Haswell 及更高版本上 1/clock shuffle 吞吐量瓶颈的技巧:AVX512 中的 128 位跨通道运算是否能提供更好的性能? https://stackoverflow.com/questions/47646238/do-128bit-cross-lane-operations-in-avx512-give-better-performance

在洗牌速度较慢的旧 CPU 上:

  • movhlps(Merom:1uop)明显快于shufps(梅罗姆:3uop)。在 Pentium-M 上,比movaps。此外,它在 Core2 上的 FP 域中运行,避免了其他 shuffle 造成的旁路延迟。
  • unpcklpdunpcklps.
  • pshufd是慢的,pshuflw/pshufhw速度很快(因为它们只洗牌 64 位的一半)
  • pshufb mm0(MMX) 速度很快,pshufb xmm0是慢的。
  • haddps非常慢(Merom 和 Pentium M 上为 6uops)
  • movshdup(Merom:1uop)很有趣:它是唯一在 64b 元素内进行洗牌的 1uop insn。

shufpsCore2(包括 Penryn)将数据带入整数域,导致绕过延迟将其返回 FP 执行单元addps, but movhlps完全属于 FP 域。shufpd也运行在浮点域中。

movshdup在整数域中运行,但只有一个微指令。

AMD K10、Intel Core2(Penryn/Wolfdale) 以及所有更高版本的 CPU 将所有 xmm shuffle 作为单个 uop 运行。 (但请注意旁路延迟shufps在 Penryn 上,避免使用movhlps)


无需AVX,避免浪费movaps/movdqa说明需要仔细选择洗牌。只有少数随机播放起到复制和随机播放的作用,而不是修改目标。组合来自两个输入的数据的随机播放(例如unpck* or movhlps) 可以与不再需要的 tmp 变量一起使用,而不是_mm_movehl_ps(same,same).

通过采用虚拟参数作为初始洗牌的目的地,其中一些可以变得更快(保存 MOVAPS),但更丑陋/不太“干净”。例如:

// Use dummy = a recently-dead variable that vec depends on,
//  so it doesn't introduce a false dependency,
//  and the compiler probably still has it in a register
__m128d highhalf_pd(__m128d dummy, __m128d vec) {
#ifdef __AVX__
    // With 3-operand AVX instructions, don't create an extra dependency on something we don't need anymore.
    (void)dummy;
    return _mm_unpackhi_pd(vec, vec);
#else
    // Without AVX, we can save a MOVAPS with MOVHLPS into a dead register
    __m128 tmp = _mm_castpd_ps(dummy);
    __m128d high = _mm_castps_pd(_mm_movehl_ps(tmp, _mm_castpd_ps(vec)));
    return high;
#endif
}

SSE1(又名 SSE):

float hsum_ps_sse1(__m128 v) {                                  // v = [ D C | B A ]
    __m128 shuf   = _mm_shuffle_ps(v, v, _MM_SHUFFLE(2, 3, 0, 1));  // [ C D | A B ]
    __m128 sums   = _mm_add_ps(v, shuf);      // sums = [ D+C C+D | B+A A+B ]
    shuf          = _mm_movehl_ps(shuf, sums);      //  [   C   D | D+C C+D ]  // let the compiler avoid a mov by reusing shuf
    sums          = _mm_add_ss(sums, shuf);
    return    _mm_cvtss_f32(sums);
}
    # gcc 5.3 -O3:  looks optimal
    movaps  xmm1, xmm0     # I think one movaps is unavoidable, unless we have a 2nd register with known-safe floats in the upper 2 elements
    shufps  xmm1, xmm0, 177
    addps   xmm0, xmm1
    movhlps xmm1, xmm0     # note the reuse of shuf, avoiding a movaps
    addss   xmm0, xmm1

    # clang 3.7.1 -O3:  
    movaps  xmm1, xmm0
    shufps  xmm1, xmm1, 177
    addps   xmm1, xmm0
    movaps  xmm0, xmm1
    shufpd  xmm0, xmm0, 1
    addss   xmm0, xmm1

我举报了一个关于悲观洗牌的 clang bug https://llvm.org/bugs/show_bug.cgi?id=26491。它有自己的洗牌内部表示,并将其变回洗牌。 gcc 更经常使用与您使用的内在函数直接匹配的指令。

通常,在指令选择未手动调整的代码中,clang 比 gcc 做得更好,或者即使内在函数对于非常量情况而言是最佳的,常量传播也可以简化事情。总的来说,编译器像一个适合内在函数的编译器一样工作,而不仅仅是一个汇编器,这是一件好事。编译器通常可以从标量 C 生成良好的 asm,但它甚至不会尝试按照良好的 asm 的方式工作。最终编译器会将内在函数视为另一个 C 运算符作为优化器的输入。


SSE3

float hsum_ps_sse3(__m128 v) {
    __m128 shuf = _mm_movehdup_ps(v);        // broadcast elements 3,1 to 2,0
    __m128 sums = _mm_add_ps(v, shuf);
    shuf        = _mm_movehl_ps(shuf, sums); // high half -> low half
    sums        = _mm_add_ss(sums, shuf);
    return        _mm_cvtss_f32(sums);
}

    # gcc 5.3 -O3: perfectly optimal code
    movshdup    xmm1, xmm0
    addps       xmm0, xmm1
    movhlps     xmm1, xmm0
    addss       xmm0, xmm1

这有几个优点:

  • 不需要任何movaps副本以解决破坏性洗牌问题(无需 AVX):movshdup xmm1, xmm2的目的地是只写的,所以它创建tmp为我们提供了一个死寄存器。这也是我使用的原因movehl_ps(tmp, sums)代替movehl_ps(sums, sums).

  • 代码尺寸小。洗牌指令很小:movhlps是3个字节,movshdup是 4 个字节(与shufps)。不需要立即字节,因此使用 AVX,vshufps是 5 个字节但是vmovhlps and vmovshdup都是4。

我可以节省另一个字节addps代替addss。由于这不会在内部循环中使用,因此切换额外晶体管的额外能量可能可以忽略不计。上面 3 个元素的 FP 异常不存在风险,因为所有元素都保存有效的 FP 数据。然而,clang/LLVM 实际上“理解”向量洗牌,并且如果它知道只有低元素重要,就会发出更好的代码。

与 SSE1 版本一样,向其自身添加奇数元素可能会导致 FP 异常(如溢出),否则不会发生这种情况,但这应该不是问题。非正规化很慢,但 IIRC 产生 +Inf 结果并不在大多数 uarches 上。


SSE3 优化代码大小

如果代码大小是您主要关心的问题,那么两个haddps (_mm_hadd_ps)指令就可以解决问题(Paul R 的回答)。这也是最容易输入和记住的。这是not fast, 尽管。甚至 Intel Skylake 仍然可以解码每个haddps至 3 uop,具有 6 个周期延迟。因此,尽管它节省了机器代码字节(L1 I-cache),但它在更有价值的 uop-cache 中占用了更多空间。真实用例haddps: 转置求和问题 https://stackoverflow.com/questions/51274287/computing-8-horizontal-sums-of-eight-avx-single-precision-floating-point-vectors,或者在中间步骤进行一些缩放在本次上交所atoi()执行 https://stackoverflow.com/a/35132718/224132.


AVX:

与其他版本相比,此版本节省了一个代码字节。Marat 对 AVX 问题的回答 https://stackoverflow.com/questions/13219146/how-to-sum-m256-horizontally/13222410#13222410.

#ifdef __AVX__
float hsum256_ps_avx(__m256 v) {
    __m128 vlow  = _mm256_castps256_ps128(v);
    __m128 vhigh = _mm256_extractf128_ps(v, 1); // high 128
           vlow  = _mm_add_ps(vlow, vhigh);     // add the low 128
    return hsum_ps_sse3(vlow);         // and inline the sse3 version, which is optimal for AVX
    // (no wasted instructions, and all of them are the 4B minimum)
}
#endif

 vmovaps xmm1,xmm0               # huh, what the heck gcc?  Just extract to xmm1
 vextractf128 xmm0,ymm0,0x1
 vaddps xmm0,xmm1,xmm0
 vmovshdup xmm1,xmm0
 vaddps xmm0,xmm1,xmm0
 vmovhlps xmm1,xmm1,xmm0
 vaddss xmm0,xmm0,xmm1
 vzeroupper 
 ret

双精度:

double hsum_pd_sse2(__m128d vd) {                      // v = [ B | A ]
    __m128 undef  = _mm_undefined_ps();                       // don't worry, we only use addSD, never touching the garbage bits with an FP add
    __m128 shuftmp= _mm_movehl_ps(undef, _mm_castpd_ps(vd));  // there is no movhlpd
    __m128d shuf  = _mm_castps_pd(shuftmp);
    return  _mm_cvtsd_f64(_mm_add_sd(vd, shuf));
}

# gcc 5.3.0 -O3
    pxor    xmm1, xmm1          # hopefully when inlined, gcc could pick a register it knew wouldn't cause a false dep problem, and avoid the zeroing
    movhlps xmm1, xmm0
    addsd   xmm0, xmm1


# clang 3.7.1 -O3 again doesn't use movhlps:
    xorpd   xmm2, xmm2          # with  #define _mm_undefined_ps _mm_setzero_ps
    movapd  xmm1, xmm0
    unpckhpd        xmm1, xmm2
    addsd   xmm1, xmm0
    movapd  xmm0, xmm1    # another clang bug: wrong choice of operand order


// This doesn't compile the way it's written
double hsum_pd_scalar_sse2(__m128d vd) {
    double tmp;
    _mm_storeh_pd(&tmp, vd);       // store the high half
    double lo = _mm_cvtsd_f64(vd); // cast the low half
    return lo+tmp;
}

    # gcc 5.3 -O3
    haddpd  xmm0, xmm0   # Lower latency but less throughput than storing to memory

    # ICC13
    movhpd    QWORD PTR [-8+rsp], xmm0    # only needs the store port, not the shuffle unit
    addsd     xmm0, QWORD PTR [-8+rsp]

存储到内存并返回避免了 ALU uop。如果洗牌端口压力或一般的 ALU uops 是瓶颈,那么这很好。 (注意,不需要sub rsp, 8或任何东西,因为 x86-64 SysV ABI 提供了信号处理程序不会踩踏的红色区域。)

有些人存储到数组并对所有元素求和,但编译器通常没有意识到数组的低位元素仍然存在于存储之前的寄存器中。


Integer:

pshufd是一种方便的复制和随机播放方式。不幸的是,位和字节移位是就位的,并且punpckhqdq将目标的高半部分放入结果的低半部分,与方式相反movhlps可以将高半部分提取到不同的寄存器中。

Using movhlps第一步在某些 CPU 上可能会很好,但前提是我们有一个暂存寄存器。pshufd是一个安全的选择,并且在 Merom 之后一切都很快。

int hsum_epi32_sse2(__m128i x) {
#ifdef __AVX__
    __m128i hi64  = _mm_unpackhi_epi64(x, x);           // 3-operand non-destructive AVX lets us save a byte without needing a mov
#else
    __m128i hi64  = _mm_shuffle_epi32(x, _MM_SHUFFLE(1, 0, 3, 2));
#endif
    __m128i sum64 = _mm_add_epi32(hi64, x);
    __m128i hi32  = _mm_shufflelo_epi16(sum64, _MM_SHUFFLE(1, 0, 3, 2));    // Swap the low two elements
    __m128i sum32 = _mm_add_epi32(sum64, hi32);
    return _mm_cvtsi128_si32(sum32);       // SSE2 movd
    //return _mm_extract_epi32(hl, 0);     // SSE4, even though it compiles to movd instead of a literal pextrd r32,xmm,0
}

    # gcc 5.3 -O3
    pshufd xmm1,xmm0,0x4e
    paddd  xmm0,xmm1
    pshuflw xmm1,xmm0,0x4e
    paddd  xmm0,xmm1
    movd   eax,xmm0

int hsum_epi32_ssse3_slow_smallcode(__m128i x){
    x = _mm_hadd_epi32(x, x);
    x = _mm_hadd_epi32(x, x);
    return _mm_cvtsi128_si32(x);
}

在某些 CPU 上,对整数数据使用 FP shuffle 是安全的。我没有这样做,因为在现代 CPU 上最多可以节省 1 或 2 个代码字节,并且没有速度增益(除了代码大小/对齐效果)。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

进行水平 SSE 向量和(或其他简化)的最快方法 的相关文章

  • 使用 PuLP 进行线性优化,变量附加条件

    我必须用 Pull 解决 Python 中的整数线性优化问题 我解决了基本问题 现在我必须添加额外的约束 有人可以帮助我用逻辑指示器添加条件吗 逻辑限制是 如果 A gt 20 则 B gt 5 这是我的代码 from pulp impor
  • 为什么浮点数有符号零?

    为什么双打有 0也 0 其背景和意义是什么 0 通常 被视为0 当一个negative浮点数非常接近零 可以考虑0 要明确的是 我指的是算术下溢 http en wikipedia org wiki Arithmetic underflow
  • orpd等SSE2指令有什么意义?

    The orpd指令是 压缩双精度浮点值的按位逻辑或 这不是做完 全相同的事情吗por 按位逻辑或 如果是这样 拥有它还有什么意义呢 请记住 SSE1orps https www felixcloutier com x86 orps首先 实
  • 查找二维空间中圆内的所有点

    我表示我的 2D 空间 考虑一个窗口 其中每个像素显示为 2D 数组中的一个单元格 即 100x100 的窗口由相同维度的数组表示 现在给定窗口中的一个点 如果我画一个半径的圆r 我想找到该圆圈中的所有点 我想我应该检查半径周围方形区域中的
  • 如何使用 #pragma 在 G++ 中启用优化

    我想在没有命令行参数的情况下启用 g 优化 我知道 GCC 可以通过写来做到这一点 pragma GCC optimize 2 在我的代码中 但它似乎在 G 中不起作用 此页面可能有帮助 http gcc gnu org onlinedoc
  • IEEE-754 32 位(单精度)指数 -126 而不是 -127

    我知道我是否有这样的号码 1 1001 0001 0011 0011 0000 0001 0101 000 1 sign bit 8 bit biased exponent 23 bit fraction mantissa 我可以通过从有偏
  • 汇编-符号标志和奇偶校验标志

    我不明白什么时候设置标志标志 什么时候设置奇偶校验 据我所知 符号标志表示运算结果的符号 0表示正数 1表示负数 那么为什么在下一个代码中 mov al 5 sub al 124 SF为零 结果是负数 关于PF 为什么a和b中设置了PF a
  • PHP 中检查数字是否为浮点型

    这实在是太奇怪了 我有这段代码 rewardAmt amt if is float rewardAmt print r is float die else print r is not float die amt 的值为 0 01 但它正在
  • 使用列模数的更简洁方法

    我目前有一个人员列表 我已将其分为两列 但在完成代码后 我一直想知道是否有更有效或更干净的方法来完成同样的事情 echo table class area list tr Loop users within areas divided up
  • 为什么 Visual Studio 使用 xchg ax,ax

    我正在查看程序的反汇编 因为它崩溃了 并注意到很多 xchg ax ax 我用谷歌搜索了一下 发现它本质上是一个 nop 但是为什么 Visual Studio 会执行 xchg 而不是 noop 该应用程序是一个C NET3 5 64位应
  • 减法进位标志

    我正在使用 MASM32 有了这个代码 mov eax 5 sub eax 10 CF 状态标志将被设置 但使用我的铅笔和纸 我实际上看到 MSB 没有任何进位 是的 我知道从较少的数字中减去大的数字集CF 但我想知道为什么 因为使用这段代
  • 为什么 RISC-V S-B 和 U-J 指令类型以这种方式编码?

    我正在读一本书 计算机组织与设计RISC V版 我遇到了 S B 和 U J 指令类型的编码 我上面提到的那些类型有奇怪的编码立即字段 S B 类型将直接字段分为两部分 这是有道理的 因为所有指令编码都必须相似 但我无法理解为什么立即字段以
  • 两个基本的 ANTLR 问题

    我正在尝试使用 ANTLR 来获取简单的语法并生成汇编输出 我在 ANTLR 中选择的语言是 Python 许多教程看起来非常复杂或详细阐述与我无关的事情 我真的只需要一些非常简单的功能 所以我有两个问题 将值从一个规则 返回 到另一规则
  • 汇编8086监听键盘中断

    我有与此完全相同的问题 边画边听键盘 https stackoverflow com questions 13970325 8086 listen to keyboard while drawing 但第一个答案 接受的答案 只听键盘一次
  • 为什么 GCC 不将 a*a*a*a*a*a 优化为 (a*a*a)*(a*a*a)?

    我正在对科学应用程序进行一些数值优化 我注意到的一件事是 GCC 会优化调用pow a 2 通过将其编译成a a 但是调用pow a 6 没有优化 实际会调用库函数pow 这大大降低了性能 相比之下 英特尔 C 编译器 http en wi
  • cout 可以以某种方式改变变量吗?

    所以我有一个看起来像这样的函数 float function float x SomeValue return x SomeOtherValue 在某些时候 这个函数会溢出并返回一个非常大的负值 为了尝试准确追踪发生这种情况的位置 我添加了
  • 浮点型、双精度型和十进制最大值与大小的关系[重复]

    这个问题在这里已经有答案了 我在 C 中遇到了这些数据类型的大小和最大值的令人困惑的模式 在使用 Marshal SizeOf 比较这些大小时 我发现了以下结果 Float 4 bytes Double 8 bytes Decimal 16
  • 在 x86 ASM 中测试零通常哪个更快:“TEST EAX, EAX”与“TEST AL, AL”?

    测试 AL 中的字节是否为零 非零通常哪个更快 TEST EAX EAX TEST AL AL 假设之前有一个 MOVZX EAX BYTE PTR ESP 4 指令加载了一个带有零扩展的字节参数到 EAX 的其余部分 防止了我已经知道的组
  • 68HC11计算sin(x)的汇编代码

    68HC11 使用泰勒级数或查找表计算正弦值的汇编代码是什么 显示值只能是整数 查找表如何工作 在这种情况下 如何使用它来实现泰勒级数 http en wikipedia org wiki Taylor series 如果您正在寻找浮点解决
  • 如何减少 JSF 中的 javax.faces.ViewState

    减少 JSF 中视图状态隐藏字段大小的最佳方法是什么 我注意到我的视图状态约为 40k 这会在每次请求和响应时下降到客户端并返回到服务器 特别是到达服务器时 这对用户来说会显着减慢 我的环境 JSF 1 2 MyFaces Tomcat T

随机推荐

  • 优化三角矩阵计算的 CUDA 内核的执行

    我正在开发我的第一个 Cuda 应用程序 并且我的内核 吞吐量低于预期 这似乎是目前最大的瓶颈 内核的任务是计算一个 N N 大小的矩阵 DD 包含数据矩阵上所有元素之间的平方距离 数据矩阵 Y 的大小为 N D 以支持多维数据 并存储为行
  • Java:检测图像格式、调整大小(缩放)并另存为 JPEG

    这是我的代码 它实际上有效 虽然不完美 但确实有效 问题是调整大小的缩略图没有粘贴在白色绘制的矩形上 破坏了图像的长宽比 这是代码 有人可以建议我修复吗为了它 好吗 谢谢 import java awt Color import java
  • Selenium2 + phpunit -> 拖放到 xpath 上

    我使用 PHPUnit Extensions Selenium2TestCase Selenium 和 phpunit 我需要在不同的 emelent 上拖放一个 xpath 元素 就像在此屏幕上一样 更改树 类别树 上的位置 srcDra
  • 使用 malloc() 和 sizeof() 在堆上创建结构体

    我正在尝试使用 malloc 和 sizeof 在堆上创建一个结构 这是我的代码 include
  • 在 C# 中使用 imagemagick.net 在图像上应用水印

    我正在使用以下代码来调整图像大小 现在 我需要使用此图像应用水印魔法网 https magick codeplex com var response client GetObject request ResponseStream Magic
  • OpenSSL HMAC-SHA1 摘要与加密的不匹配

    我花了过去 6 个小时来实现消息签名算法 它根本不起作用 这是生成摘要的 PHP 代码 payload thisisanapple signature hash hmac sha1 payload thisisarandomkey data
  • Eclipse 中的 SSL 密钥库路径

    在我的用户主目录中有一个 keystore文件 每当我这样做时keytool list I get Keystore type JKS Keystore provider SUN Your keystore contains 0 entri
  • 循环中的动作链仅工作一次(Selenium/Python)

    I m trying to implement a cookie clicker bot Cookie clicker it s just a stupid simple game where you can click on the co
  • 实体框架和 SQL Server 2012 分页

    SQL Server 2012 引入了一种使用 FETCH 和 OFFSET 的更有效的分页机制 这可能会对使用大量分页的应用程序的性能产生重大影响 实体框架 5 支持吗 因此 如果我使用 EF 通过 Take Skip 进行分页 如果 E
  • 检查来自 Alamofire 和 Swift 的多个异步响应

    我正在编写一个应用程序 该应用程序依赖于来自各个站点 服务的数据 并涉及根据来自这些不同来源的数据执行计算以生成最终产品 我编写了一个示例类 其中包含下面的两个函数 用于从两个来源收集数据 我选择使函数不同 因为有时我们根据源应用不同的身份
  • 可折叠树示例中的 d3.js v4 古怪链接转换

    如果您玩下面的可折叠树 您会发现当您到达树的末尾并展开和折叠节点时 这些线正在做一些古怪的事情 我不完全确定是什么驱动了这种行为 或者我的重写是否的在此输入链接描述 https bl ocks org mbostock 4339083完全没
  • 车把示例不起作用

    在我的节点服务器提供的 hbs 中 在我的客户端 JavaScript 文件中 var source entry template html var template Handlebars compi
  • SQLAlchemy 中的信号或触发器

    SQLAlchemy 是否有类似于 Django 信号概念的东西 基本上 我想在预保存或后保存某些实体对象时触发一些函数 谢谢 编辑 我只是想要 SQLAlchemy 中的 django signals 的等效项 我认为您正在寻找 ORM
  • 错误 3219 - 无效操作

    我尝试在 Access 项目中编写查询 但在 SQL 查询所在的行中发生此运行时错误 这是我的代码 Private Sub Befehl80 Click Dim rst As DAO Recordset Set rst CurrentDb
  • Autofixture 声明性自动数据参数属性的集合大小

    如何使用 Autofixture 的声明性参数样式传递到测试的属性上的属性来指定列表 枚举的长度 大小 我希望能够在不将参数移入测试主体的情况下使该测试通过 Theory AutoData public void CollectionSiz
  • 如何访问作为 numpy 数组传递给 ctypes 回调的数组?

    我正在尝试使用 numpy 和 ctypes 将一些用 C 编写的数字代码集成到 Python 库中 我已经开始进行实际计算 但现在想将算法中间步骤的进度报告给 Python 代码中的回调函数 虽然我可以成功调用回调函数 但无法检索x数组传
  • Apache Solr 6.6 替换文档而不是更新

    我已配置 solr 6 6 1 进行测试设置 在索引了一些文档后 我必须更新一些字段 我正在使用 python 客户端solr https pythonhosted org solrpy reference html 要更新 以下是我的代码
  • 如果 Microsoft.AspNetCore.Authentication 现已弃用,应使用什么包?

    我有一个 ASP Net WebApi 项目 一些包 例如 Microsoft AspNetCore Authentication 现在被标记为已弃用 应该使用哪些替代包 我猜你的意思是微软 AspNetCore 身份验证2 2 0 htt
  • SSLException:HelloRequest 后跟意外的握手消息

    我正在尝试使用 Apache Commons HttpClient 3 1 通过 SSL 连接到 Web 服务 使用以下命令 String url https archprod service eogs dk cvronline esb L
  • 进行水平 SSE 向量和(或其他简化)的最快方法

    给定一个由三个 或四个 浮点数组成的向量 对它们求和的最快方法是什么 SSE movaps shuffle add movd 总是比 x87 快吗 SSE3 中的水平相加指令值得吗 转移到 FPU 然后是 faddp faddp 的成本是多