【问题标题】:Permute content of AVX register置换 AVX 寄存器的内容
【发布时间】:2016-11-22 01:31:43
【问题描述】:

我有一个具有四个双精度值的 AVX 寄存器。现在我需要对每个元素单独执行一些算术。我需要做的事情的简化如下。

Situation:
  a = [a4 a3 a2 a1]
  w = [ 0  0  0 w1]
  x = [ 0  0  0 x1]
  y = [ 0  0  0 y1]
  z = [ 0  0  0 z1]

Desired result:
  w = [-- -- -- w1+a1]
  x = [-- -- -- x1+a2]
  y = [-- -- -- y1+a3]
  z = [-- -- -- z1+a4]

除了期望的结果不仅仅是两个值的总和,而是它们的更复杂的算术表达式。我在哪里放--,我的意思是我不关心那些值,它们会被丢弃。

我发现我可以使用置换操作置换寄存器a(参见例如Reverse a AVX register containing doubles using a single AVX intrinsic)。我唯一的问题是这些内在函数需要立即数,即编译时值,而我需要动态执行此操作。

我发现了对包含在其他寄存器中的整数进行操作的置换内在函数,例如 _mm256_permutevar_pd,但它们都不会跨通道置换(例如,使用这些指令首先不可能有 a3。唯一的方法使用这些说明做我想做的事情是使用if,我宁愿避免这样做。

我是否应该在 128 位通道上进行排列,以符合 if 条件,然后在通道内进行动态排列?还是有更好的解决方案?我对性能和可维护性都感兴趣。我最多可以使用 AVX2 指令。组装是一种选择,但我更喜欢内在。

【问题讨论】:

  • 作为向量存储在内存中,作为标量读回?
  • 当我放弃这样的解决方案时,我的应用程序得到了巨大的加速。我已经实现了“存储在内存中”的解决方案。现在我想看看整个操作在寄存器中完成时的区别。
  • 所以你需要你的结果作为 4 个单独的标量双精度数? (这与将它放在向量的低元素中而其他元素无关)是一样的)。非编译时间常数洗牌从哪里来?我不明白为什么你不能只用vextractf128 解包,然后用vmovhlps 或其他东西将每个__m128d 的高半部分转换为标量double。你知道哪个向量元素与哪个变量名搭配...
  • 正确,我需要将这些值视为单独的标量。指令vextractf128(用于像_mm256_extractf128_pd 这样的内在函数)要求索引是静态值。我需要索引是动态的。我无法在编译时知道取值的顺序。 [另一个用例是当我使用for 循环遍历这些值时。由于我同时支持 floatdouble 作为基本类型,因此我不能手动展开循环 for (int i = 0; i < packSize; ++i),因为它需要针对不同类型进行不同的迭代。]
  • 我有点错过了这个问题 - 你不是总是需要相同的 3 个排列吗?即把第 2、3、4 个值降到第 1 位?

标签: c++ c intrinsics avx avx2


【解决方案1】:

理想情况下,您可以在将[ z y x w ] 打包到向量中之后使用 SIMD 操作来执行 + 所代表的任何事情。但如果不是:

以正常方式提取所有 4 个元素以标量 doubles,然后做任何你想做的事情:

void unpack_256_to_scalar(__m256d a) {
    // unpack to two 128b halves
    __m128d a01 = _mm256_castpd256_pd128(a);  // extractf128_pd(a, 0) should also compile the same way, if you like more-consistent C instead of code that matches the asm you expect
    __m128d a23 = _mm256_extractf128_pd(a, 1);

    // and then halves of each 128b vector
    double a0 = _mm_cvtsd_f64(a01);
    double a1 = _mm_cvtsd_f64(_mm_unpackhi_pd(a01,a01));
    double a2 = _mm_cvtsd_f64(a23);
    double a3 = _mm_cvtsd_f64(_mm_unpackhi_pd(a23,a23));
    ...
    // use the results
}

这个compiles (on the Godbolt compiler explorer) 只使用了 3 条带有 clang 的指令,或者 4 条带有 gcc 的指令,因为它在寄存器分配方面很笨:

unpack_256_to_scalar(double __vector(4)):
    vextractf128    xmm1, ymm0, 0x1
    vunpckhpd       xmm2, xmm0, xmm0
    vmovapd xmm3, xmm1                 # gcc should have use vunpckhpd xmm3, xmm1,xmm1.  This wasted mov is a missed-optimization bug.
    vunpckhpd       xmm1, xmm1, xmm1
    # the empty asm statement emitted the empty string here.
    vzeroupper
    ret

三个指令中的每一个都产生一个不同的元素作为其向量结果的低元素。不需要常量,甚至不需要立即常量(这就是为什么我选择unpackhi_pd 而不是shufpdvpermilpd,clang 在从数据移动的内部表示生成随机播放时使用。)

在这里使用需要向量作为控制掩码的变量随机播放会很疯狂。这似乎不需要任何动态/变量洗牌或提取。

顺便说一句,有关编写高性能代码的一些链接,请参阅 标签 wiki。

【讨论】:

    猜你喜欢
    • 2019-12-22
    • 1970-01-01
    • 2021-07-21
    • 2012-11-05
    • 2016-03-06
    • 1970-01-01
    • 2021-03-29
    • 1970-01-01
    • 2016-06-11
    相关资源
    最近更新 更多