【问题标题】:converting SSE code to AVX - cost of _mm256_and_ps将 SSE 代码转换为 AVX - _mm256_and_ps 的成本
【发布时间】:2013-12-28 19:09:08
【问题描述】:

我正在转换 SSE2 正弦和余弦函数(来自 Julien Pommier 的 sse_mathfun.h;基于 CEPHES sinf 函数)以使用 AVX 以接受 8 个浮点向量或 4 个双精度。

因此,Julien 的函数 sin_ps 变为 sin_ps8(8 个浮点数)和 sin_pd4 4 个双精度数。 (这里的“高级”编辑器无法接受我的代码,请访问http://arstechnica.com/civis/viewtopic.php?f=20&t=1227375查看。)

在运行 2011 Core2 i7 @ 2.7Ghz 的 Mac OS X 10.6.8 下使用 clang 3.3 进行测试,基准测试结果如下所示:

  • sinf .. -> 2770 万次向量评估/秒超过 5.56e+07 iters(标准,标量 sinf() 函数)

    sin_ps .. -> 4100 万次矢量评估/秒以上 8.22e+07 次

    sin_pd4 .. -> 4020 万次矢量评估/秒以上 8.06e+07 次

    sin_ps8 .. -> 250 万次矢量评估/秒以上 5.1e+06 次

sin_ps8 的成本非常可怕,似乎是因为使用了 _mm256_castsi256_ps 。实际上,注释掉“poly_mask = _mm256_castsi256_ps(emmm2);”这一行导致更正常的性能。 sin_pd4 使用 _mm_castsi128_pd,但在 sin_ps8 中咬我的似乎不是(只是)SSE 和 AVX 指令的混合:当我通过 2 次调用 _mm_castsi128_ps 来模拟 _mm256_castsi256_ps 调用时,性能并没有提高。 emm2 和 emm0 是指向 emmm2 和 emmm0 的指针,它们都是 v8si 实例,因此(先验)正确对齐到 32 位边界。

有关可编译代码,请参阅sse_mathfun.hsse_mathfun_test.c

有没有一种(简单的)方法可以避免我看到的惩罚?

【问题讨论】:

  • 非常抱歉缺少代码:预览看起来不错,但我的文字被拒绝发布...
  • Agner Fog 的 optimization tables 表明,无论参数大小如何,ANDPSVANDPS 在 Ivy Bridge 和 Haswell 上都有 1 个周期的延迟。此外,_mm256_castsi256_ps 实际上并不发出任何指令——它是真正的类型转换,纯粹在编译器中。您的问题可能出在其他地方。
  • 我想提出几点。首先,如果您可以发布 Julien 和您的所有函数的汇编代码,我们会很高兴。在 Mac OS X 10.6.8 上,您可以使用 otool -tV -p <name of function prefixed with underscore> 执行此操作。其次,由于您使用的是内在函数,我希望编译器会选择不会导致 SSE-AVX 转换惩罚的 VEX 前缀指令编码。我怀疑您的问题是由于 SSE-AVX 转换造成的。
  • 第三,您在这里使用了一些 256 位整数数学。根据您的 CPU 的发布日期以及它具有 AVX 的事实,它是 Intel Sandy Bridge。 Sandy Bridge 和 Ivy Bridge 只支持 AVX 指令集,不像 Haswell 也支持 AVX2。 AVX 指令集仅包含完整的 256 位宽度的 浮点 操作,而整数指令仍然是 128 位宽度。 AVX2 是也有全角整数运算的指令集。
  • 我没有看到他在任何地方使用 256 位整数数学

标签: c sse avx


【解决方案1】:

将寄存器中的内容传输到内存中通常不是一个好主意。每次存储到指针时都会这样做。

而不是这个:

{ ALIGN32_BEG v4sf *yy ALIGN32_END = (v4sf*) &y;
         emm2[0] = _mm_and_si128(_mm_add_epi32( _mm_cvttps_epi32( yy[0] ), _v4si_pi32_1), _v4si_pi32_inv1),
         emm2[1] = _mm_and_si128(_mm_add_epi32( _mm_cvttps_epi32( yy[1] ), _v4si_pi32_1), _v4si_pi32_inv1);
         yy[0] = _mm_cvtepi32_ps(emm2[0]),
         yy[1] = _mm_cvtepi32_ps(emm2[1]);
      }

/* get the swap sign flag */
emm0[0] = _mm_slli_epi32(_mm_and_si128(emm2[0], _v4si_pi32_4), 29),
emm0[1] = _mm_slli_epi32(_mm_and_si128(emm2[1], _v4si_pi32_4), 29);

/* get the polynom selection mask
there is one polynom for 0 <= x <= Pi/4
and another one for Pi/4<x<=Pi/2

Both branches will be computed.
*/
emm2[0] = _mm_cmpeq_epi32(_mm_and_si128(emm2[0], _v4si_pi32_2), _mm_setzero_si128()),
emm2[1] = _mm_cmpeq_epi32(_mm_and_si128(emm2[1], _v4si_pi32_2), _mm_setzero_si128());

((v4sf*)&poly_mask)[0] = _mm_castsi128_ps(emm2[0]);
((v4sf*)&poly_mask)[1] = _mm_castsi128_ps(emm2[1]);
swap_sign_bit = _mm256_castsi256_ps(emmm0);

试试这样的:

__m128i emm2a = _mm_and_si128(_mm_add_epi32( _mm256_castps256_ps128(y), _v4si_pi32_1), _v4si_pi32_inv1);
__m128i emm2b = _mm_and_si128(_mm_add_epi32( _mm256_extractf128_ps(y, 1), _v4si_pi32_1), _v4si_pi32_inv1);

y = _mm256_insertf128_ps(_mm256_castps128_ps256(_mm_cvtepi32_ps(emm2a)), _mm_cvtepi32_ps(emm2b), 1);

/* get the swap sign flag */
__m128i emm0a = _mm_slli_epi32(_mm_and_si128(emm2a, _v4si_pi32_4), 29),
__m128i emm0b = _mm_slli_epi32(_mm_and_si128(emm2b, _v4si_pi32_4), 29);

swap_sign_bit = _mm256_castsi256_ps(_mm256_insertf128_si256(_mm256_castsi128_si256(emm0a), emm0b, 1));

/* get the polynom selection mask
there is one polynom for 0 <= x <= Pi/4
and another one for Pi/4<x<=Pi/2

Both branches will be computed.
*/
emm2a = _mm_cmpeq_epi32(_mm_and_si128(emm2a, _v4si_pi32_2), _mm_setzero_si128()),
emm2b = _mm_cmpeq_epi32(_mm_and_si128(emm2b, _v4si_pi32_2), _mm_setzero_si128());

poly_mask = _mm256_castsi256_ps(_mm256_insertf128_si256(_mm256_castsi128_si256(emm2a), emm2b, 1));

如 cmets 中所述,cast 内在函数纯粹是编译时的,不发出任何指令。

【讨论】:

  • Re: cast内在函数:确实。我比较了纯 _mm*_cast*_ps 内在函数,它们都是 fast。所以我有 SSE2 整数代码和两个 AVX 演员表,如果我对这两个演员表(或者实际上是一个特定的演员表,IIRC 来计算 poly_mask)进行评判,性能会突然飙升。我必须稍微仔细解释一下,因为 sse_mathfun_test 将基准函数结果存储到其操作数中以避免循环展开的方式(性能可能会超出顶部;)),但这仍然表明我正在采取因转化而命中。
  • 对齐问题,加载/存储延迟,我对汇编程序编码知之甚少,但我已经从 Apple 的 Shark 工具中看到了足够多的反馈,我怀疑这种情况正在发生。 (不,Shark 在这里没有帮助我,我的版本显然还不知道 AVX。)事实上,我的 CPU 还没有 AVX2,很遗憾。
  • 像我在这里所做的那样删除内存使用有帮助吗?
  • 你测试过你的代码吗?注意:令我感到羞耻的是,我发布的代码一定存在转换错误,因为这些函数不能计算正确的正弦和余弦。如此依赖别人的单元测试......
  • 但要回答你的问题,是的。或者更确切地说,当我使用 avx_ssemathfun.h 并修改它以使用我的方法和 2 个指向 __m256 的 _m128 指针时,结果比通过临时的 Garberoglio 投射要慢。联盟。事实证明,甚至可以使用内联函数: inline void copy_xmm_to_imm( v4si xmm0, v4si xmm1_, v8si *imm_ ) { ALIGN32_BEG imm_xmm_union u ALIGN32_END; u.xmm[0]=xmm0_,u.xmm[1]=xmm1_; *imm_ = u.imm; }
【解决方案2】:

也许您可以将您的代码与 Julien Pommier SSE 数学函数的 AVX 扩展进行比较?

http://software-lisc.fbk.eu/avx_mathfun/

此代码在 GCC 中有效,但在 MSVC 中无效,并且仅支持浮点数 (float8),但我认为您可以轻松地将其扩展为使用双精度数 (double4)。对sin 函数的快速比较表明,除了 SSE2 整数部分之外,它们非常相似。

【讨论】:

  • 我记得我必须做一些工作才能让 sse_mathfun.h 在 MSVC 下编译,我大概可以用 AVX 版本做同样的事情。老实说,我还没有开始寻找是否有人做了这项工作,部分原因是我确实这样做也是为了锻炼。也就是说,使用临时联合和“标量指针”而不是使用指向允许将该变量作为 __m128[2] 访问该变量的 __m256 实例的指针,以他的方式进行转换真的有优势吗?如果转换真的只在编译时完成,没有对齐调整,应该没有区别。
  • 另外,我尝试使用包含 __m256 和 __m128[2] 的联合......但是 gcc 和 clang 都拒绝了。无论如何,当我进行快速比较时,我有一个明显的印象,即真正不同的整数部分是针对 AVX2 的......猜猜最简单的事情是去时间 avx_sse_mathfun.h :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-10-08
  • 1970-01-01
  • 1970-01-01
  • 2015-08-31
  • 1970-01-01
相关资源
最近更新 更多