仅使用 avx 而不是 avx2 转置 64 位元素
transpose of 64bit elements using only avx, not avx2
我想仅使用 avx 而不是 avx2 实现 64 位转置操作。它应该这样做:
// in = Hh Hl Lh Ll
// | X |
// out = Hh Lh Hl Ll
这是 with 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 swizzle 操作(例如解包)都在 128 位通道上运行并且不跨越通道边界。
谁能提供更高效的实施方案?非常感谢!
我认为 3 条指令是你能做的最好的。 _mm256_blend_pd
非常便宜(如 vblendps
和 vpblendd
),运行 在 SnB/IvB 的 2 个端口上,以及 Haswell 和更高版本中的所有 3 个矢量执行端口。 (即与向量 XOR 或 AND 一样便宜。)其他两个都需要随机端口,这是不可避免的。
当 vblendpd
将其数据从 FP 域转发到整数指令时,您将在 SnB 系列 CPU 上有 1 个周期的旁路延迟。尽管使用 AVX1,但没有任何 256b 整数指令可以转发到。
(来源:参见 Agner Fog 的 insn 表,链接自 x86 标签 wiki。他的优化装配指南也有一些不错的洗牌表,但没有关注 AVX/AVX2.)
这种模式几乎可以用两条指令实现,但不完全是。
vshufpd
(_mm256_shuffle_pd
) 为您提供通道内 2 源随机播放,但对数据移动有限制。与最初的 SSE2 版本一样,每个目标元素只能来自一个固定的源元素。 8 位立即数有空间对来自四个源元素的两个选择进行编码,但它们使硬件保持简单并且只为每个目标元素使用一个 1 位选择器。 256b 版本确实允许对每个 128b 通道进行不同的洗牌,因此 imm8 的 4 位对于 vpshufd ymm
.
很重要
总之,由于上路需要从原始向量中取其高位元素,而低路需要从perm128向量中取其高位元素,因此无论选择src1,src2顺序都不能满足我们的需要。
vshufpd
我认为编码比 vpermilpd imm8
短一个字节。 vpermilps
/ vpermilpd
的直接形式的唯一用例似乎是加载和随机播放。 (vshufpd
仅在两个源操作数相同时用作完整的通道内洗牌)。 IDK if vpermildp
可能会使用更少的能源或其他东西,因为它只有一个来源。
当然,编译器可以使用他们想要完成工作的任何指令;他们被允许使用内在函数优化代码,就像使用 +
运算符(并不总是编译成 add
指令)优化代码一样。 Clang 实际上确实基本上忽略了使用内在函数进行指令选择的尝试,因为它以自己的内部格式表示随机播放,并对其进行了优化。
我想仅使用 avx 而不是 avx2 实现 64 位转置操作。它应该这样做:
// in = Hh Hl Lh Ll
// | X |
// out = Hh Lh Hl Ll
这是 with 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 swizzle 操作(例如解包)都在 128 位通道上运行并且不跨越通道边界。
谁能提供更高效的实施方案?非常感谢!
我认为 3 条指令是你能做的最好的。 _mm256_blend_pd
非常便宜(如 vblendps
和 vpblendd
),运行 在 SnB/IvB 的 2 个端口上,以及 Haswell 和更高版本中的所有 3 个矢量执行端口。 (即与向量 XOR 或 AND 一样便宜。)其他两个都需要随机端口,这是不可避免的。
当 vblendpd
将其数据从 FP 域转发到整数指令时,您将在 SnB 系列 CPU 上有 1 个周期的旁路延迟。尽管使用 AVX1,但没有任何 256b 整数指令可以转发到。
(来源:参见 Agner Fog 的 insn 表,链接自 x86 标签 wiki。他的优化装配指南也有一些不错的洗牌表,但没有关注 AVX/AVX2.)
这种模式几乎可以用两条指令实现,但不完全是。
vshufpd
(_mm256_shuffle_pd
) 为您提供通道内 2 源随机播放,但对数据移动有限制。与最初的 SSE2 版本一样,每个目标元素只能来自一个固定的源元素。 8 位立即数有空间对来自四个源元素的两个选择进行编码,但它们使硬件保持简单并且只为每个目标元素使用一个 1 位选择器。 256b 版本确实允许对每个 128b 通道进行不同的洗牌,因此 imm8 的 4 位对于 vpshufd ymm
.
总之,由于上路需要从原始向量中取其高位元素,而低路需要从perm128向量中取其高位元素,因此无论选择src1,src2顺序都不能满足我们的需要。
vshufpd
我认为编码比 vpermilpd imm8
短一个字节。 vpermilps
/ vpermilpd
的直接形式的唯一用例似乎是加载和随机播放。 (vshufpd
仅在两个源操作数相同时用作完整的通道内洗牌)。 IDK if vpermildp
可能会使用更少的能源或其他东西,因为它只有一个来源。
当然,编译器可以使用他们想要完成工作的任何指令;他们被允许使用内在函数优化代码,就像使用 +
运算符(并不总是编译成 add
指令)优化代码一样。 Clang 实际上确实基本上忽略了使用内在函数进行指令选择的尝试,因为它以自己的内部格式表示随机播放,并对其进行了优化。