使用AVX而不是AVX2进行64位元素的转置

3

我希望使用avx而不是avx2来实现64位转置操作。它应该实现以下功能:

// in  = Hh Hl Lh Ll
//        |   X   |
// out = Hh Lh Hl Ll

使用avx2时,它会呈现如下:

#define SIMD_INLINE inline __attribute__ ((always_inline))

static SIMD_INLINE __m256i
x_mm256_transpose4x64_epi64(__m256i a)
{
  return _mm256_permute4x64_epi64(a, _MM_SHUFFLE(3,1,2,0));
}

这是我能想到的最有效的解决方案,没有使用 avx2(只使用了3个avx指令):

static SIMD_INLINE __m256i
x_mm256_transpose4x64_epi64(__m256i a)
{
  __m256d in, x1, x2;
  // in = Hh Hl Lh Ll
  in = _mm256_castsi256_pd(a);
  // only lower 4 bit are used
  // in = Hh Hl Lh Ll
  //       0  1  0  1  = (0,0,1,1)
  // x1 = Hl Hh Ll Lh
  x1 = _mm256_permute_pd(in, _MM_SHUFFLE(0,0,1,1));
  // all 8 bit are used
  // x1 = Hl Hh Ll Lh
  //       0  0  1  1
  // x2 = Ll Lh Hl Hh
  x2 = _mm256_permute2f128_pd(x1, x1, _MM_SHUFFLE(0,0,1,1));
  // only lower 4 bit are used
  // in = Hh Hl Lh Ll
  // x2 = Ll Lh Hl Hh
  //       0  1  1  0 = (0,0,1,2)
  // ret: Hh Lh Hl Ll
  return _mm256_castpd_si256(_mm256_blend_pd(in, x2, _MM_SHUFFLE(0,0,1,2)));
}

问题在于大多数 AVX 打乱操作(例如 unpack)是在 128 位车道上进行的,不会跨越车道边界。

有人能够提供更有效率的实现吗?非常感谢!


codereview.stackexchange.com - 在我看来,这是更适合此类问题的网站。 - Gluttton
1
@Gluttton:我不同意 - 优化问题在这里是完全适合讨论的 - codereview 更多地关注可以在习惯或风格上得到改进的工作代码。 - Paul R
@Ralf:考虑到AVX的所有限制,我怀疑你能否改进当前的三条指令解决方案,但也许有人会证明我是错的。 - Paul R
2
@PaulR 非特定的优化问题在 Code Review 上是可以讨论的。然而,这个问题的标题中有一个具体的问题,所以我认为它是一个很好的适合在 Stack Overflow 上提问的问题。 - 200_success
1
https://dev59.com/o3jZa4cB1Zd3GeqPbkD- - Z boson
1个回答

4
我认为使用3个指令是最好的选择。_mm256_blend_pd非常便宜(就像vblendpsvpblendd一样),在SnB/IvB上运行2个端口,在Haswell及更高版本中运行所有3个向量执行端口。(即与向量XOR或AND一样便宜。)其他两个都需要洗牌端口,这是不可避免的。
vblendpd将其数据从FP域转发到整数指令时,在SnB系列CPU上会有1个周期的旁路延迟。虽然在AVX1中,没有任何256位整数指令可以转发到。
(来源:请参见Agner Fog的insn表,链接自标签wiki。他的优化汇编指南还有一些漂亮的洗牌表,但并不专注于AVX/AVX2的内部挑战。)

这个模式使用两条指令几乎可以实现,但并非完全可行。

vshufpd (_mm256_shuffle_pd) 可以提供一个在lane内的2源洗牌,但对数据移动有限制。与原始的SSE2版本一样,每个目标元素只能来自固定的源元素。8位立即数可以编码从四个源元素中选择两个元素,但他们保持硬件简单,并且只为每个目标元素使用了1位选择器。256b版本允许每个128b lane使用不同的洗牌方式,因此imm8的4位对于 vpshufd ymm 是重要的。

无论如何,由于上层lane需要从原始向量中取其高元素,而下层lane需要从perm128向量中取其高元素,所以src1、src2排序的任何选择都不能满足我们的需求。


vshufpdvpermilpd imm8编码要短一个字节。只有在需要加载和洗牌时,才使用vpermilps/vpermilpd的立即数形式。(当两个源操作数相同时,vshufpd仅作为完整的轨道内洗牌)。不确定vpermilpd是否会使用更少的能量或其他东西,因为它只有一个源。

当然,编译器可以使用任何指令来完成任务;他们可以像使用+运算符一样优化使用内部函数的代码(这并不总是编译为add指令)。实际上,Clang忽略了使用内部函数进行指令选择的尝试,因为它在自己的内部格式中表示混洗,并对其进行优化。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接