shuffle/permute 内在函数如何为 256 位 pd 工作?
How do the shuffle/permute intrinsics work for 256 bit pd?
我正在努力思考 _mm256_shuffle_pd 和 _mm256_permute_pd 内在函数的工作原理。
我似乎无法预测其中一项操作的结果。
首先,_mm_shuffle_ps 一切都很好。我得到的结果是我所期望的。例如:
float b[4] = { 1.12, 2.22, 3.33, 4.44 };
__m128 a = _mm_load_ps(&b[0]);
a = _mm_shuffle_ps(a, a, _MM_SHUFFLE(3, 0, 1, 2));
_mm_store_ps(&b[0], a);
// 3.33 2.22 1.12 4.44
所以一切都在这里。现在我想用 __m256d 试试这个,这就是我目前在我的代码中使用的。
根据我的发现,_mm256_shuffle_ps/pd 内在函数的工作方式不同。
我这里的理解是控制蒙版应用了两次。第一次在 128 位的前半部分,第二次在最后 128 位。
前两对控制位用于从第一个向量中选择(并将值存储在结果向量的第一个和第二个字以及第五个和第六个字中),而最高位对从第二个向量中选择。
例如:
float b[8] = { 1.12, 2.22, 3.33, 4.44, 5.55, 6.66, 7.77, 8.88 };
__m256 a = _mm256_load_ps(&b[0]);
a = _mm256_shuffle_ps(a, a, 0b00000111);
_mm256_store_ps(&b[0], a);
// 4.44 2.22 1.12 1.12 8.88 6.66 5.55 5.55
这里我期望的(我实际得到的)结果是 { 4.44, 2.22, 1.12, 1.12, 8.88, 6.66, 5.55, 5.55 }
这应该按如下方式工作:
(对不起我画的不好)。
对第二个向量(在本例中再次为 a)使用最高的两对(因此 00 00)并填充缺失的 spaces.
进行同样的操作
我认为 _mm256_shuffle_pd 会以同样的方式工作。所以如果我想要第一个 double 我必须移动 00 space 和 01 space 才能正确构造它。
例如:
__m256d a = _mm256_load_pd(&b[0]);
a = _mm256_shuffle_pd(a, a, 0b01000100);
_mm256_store_pd(&b[0], a);
// 1.12 1.12 4.44 3.33
我原以为这会输出 { 1.12, 1.12, 3.33, 3.33 }。
在我的脑海中,我从第一个向量中获取 00 01 ( 1.12 ) 和 00 01 { 3.33 } ,从第二个向量中获取相同的内容,因为它是相同的向量和所有。
我已经为控制蒙版尝试了很多组合,但我无法理解它的使用方式,也无法找到以我能理解的方式解释它的地方。
所以我的问题是:_mm256_shuffle_pd 是如何工作的?我怎样才能得到与 _mm_shuffle_ps(a, a, _MM_SHUFFLE(3, 0, 2, 1)) 相同的结果,有四个双打和一个洗牌(如果可能的话)?
shufps
需要其立即数的所有 8 位,仅用于 4 个元素,每个元素有 4 个可能的来源。所以它没有空间增长到 256 位,唯一的选择是在两个通道中复制相同的洗牌。
但是 128 位 shufpd
只有 2 个元素,每个元素有 2 个源,因此是 2 x 1 位。所以 AVX 版本总共使用 4 位,每条通道 2 位。 (它不是穿线,所以它不如128位shufps
强大。)
http://felixcloutier.com/x86/SHUFPD.html has full docs with a diagram, and detailed pseudocode. Intel's intrinsics guide for _mm256_shuffle_pd
具有相同的伪代码。
AVX2 http://felixcloutier.com/x86/VPERMPD.html (_mm256_permute_pd
, aka _mm256_permute4x64_pd
) 是交叉路口,并且使用其立即数与 128 位 shufps
完全相同:四个 2 位选择器。
唯一的跨车道 2 源洗牌是 vperm2f128
(_mm256_permute2f128_pd
),直到 AVX512F 引入更精细的粒度 vpermt2pd
和 vpermt2ps
(以及等效的整数洗牌。
AVX1 没有任何 粒度小于 128 位的交叉洗牌,甚至没有 1 源版本。如果你需要一个,你必须用 vinsertf128
或 vperm2f128
+ 车道内洗牌来构建它。
因此,在 AVX 中将 3D 向量保留在 SIMD 向量中比在 float
中使用 128 位向量更糟糕。 http://fastcpp.blogspot.com/2011/04/vector-cross-product-using-sse-code.html 可能比标量快,但如果您为 SIMD 设计数据布局,则效果会差很多。
使用单独的连续数组 x[]
、y[]
和 z[]
,这样您就可以并行执行 4x 交叉乘积而无需改组,并利用FMA 指令。 使用 SIMD 并行执行多个向量,而不是加速单个向量。
查看 https://whosebug.com/tags/sse/info, especially https://deplinenoise.wordpress.com/2015/03/06/slides-simd-at-insomniac-games-gdc-2015/ 中的链接,其中很好地解释了数据布局问题,以及使用 SIMD 对循环的哪个级别进行矢量化。
我正在努力思考 _mm256_shuffle_pd 和 _mm256_permute_pd 内在函数的工作原理。 我似乎无法预测其中一项操作的结果。
首先,_mm_shuffle_ps 一切都很好。我得到的结果是我所期望的。例如:
float b[4] = { 1.12, 2.22, 3.33, 4.44 };
__m128 a = _mm_load_ps(&b[0]);
a = _mm_shuffle_ps(a, a, _MM_SHUFFLE(3, 0, 1, 2));
_mm_store_ps(&b[0], a);
// 3.33 2.22 1.12 4.44
所以一切都在这里。现在我想用 __m256d 试试这个,这就是我目前在我的代码中使用的。 根据我的发现,_mm256_shuffle_ps/pd 内在函数的工作方式不同。
我这里的理解是控制蒙版应用了两次。第一次在 128 位的前半部分,第二次在最后 128 位。 前两对控制位用于从第一个向量中选择(并将值存储在结果向量的第一个和第二个字以及第五个和第六个字中),而最高位对从第二个向量中选择。 例如:
float b[8] = { 1.12, 2.22, 3.33, 4.44, 5.55, 6.66, 7.77, 8.88 };
__m256 a = _mm256_load_ps(&b[0]);
a = _mm256_shuffle_ps(a, a, 0b00000111);
_mm256_store_ps(&b[0], a);
// 4.44 2.22 1.12 1.12 8.88 6.66 5.55 5.55
这里我期望的(我实际得到的)结果是 { 4.44, 2.22, 1.12, 1.12, 8.88, 6.66, 5.55, 5.55 }
这应该按如下方式工作:
(对不起我画的不好)。 对第二个向量(在本例中再次为 a)使用最高的两对(因此 00 00)并填充缺失的 spaces.
进行同样的操作我认为 _mm256_shuffle_pd 会以同样的方式工作。所以如果我想要第一个 double 我必须移动 00 space 和 01 space 才能正确构造它。
例如:
__m256d a = _mm256_load_pd(&b[0]);
a = _mm256_shuffle_pd(a, a, 0b01000100);
_mm256_store_pd(&b[0], a);
// 1.12 1.12 4.44 3.33
我原以为这会输出 { 1.12, 1.12, 3.33, 3.33 }。 在我的脑海中,我从第一个向量中获取 00 01 ( 1.12 ) 和 00 01 { 3.33 } ,从第二个向量中获取相同的内容,因为它是相同的向量和所有。
我已经为控制蒙版尝试了很多组合,但我无法理解它的使用方式,也无法找到以我能理解的方式解释它的地方。
所以我的问题是:_mm256_shuffle_pd 是如何工作的?我怎样才能得到与 _mm_shuffle_ps(a, a, _MM_SHUFFLE(3, 0, 2, 1)) 相同的结果,有四个双打和一个洗牌(如果可能的话)?
shufps
需要其立即数的所有 8 位,仅用于 4 个元素,每个元素有 4 个可能的来源。所以它没有空间增长到 256 位,唯一的选择是在两个通道中复制相同的洗牌。
但是 128 位 shufpd
只有 2 个元素,每个元素有 2 个源,因此是 2 x 1 位。所以 AVX 版本总共使用 4 位,每条通道 2 位。 (它不是穿线,所以它不如128位shufps
强大。)
http://felixcloutier.com/x86/SHUFPD.html has full docs with a diagram, and detailed pseudocode. Intel's intrinsics guide for _mm256_shuffle_pd
具有相同的伪代码。
AVX2 http://felixcloutier.com/x86/VPERMPD.html (_mm256_permute_pd
, aka _mm256_permute4x64_pd
) 是交叉路口,并且使用其立即数与 128 位 shufps
完全相同:四个 2 位选择器。
唯一的跨车道 2 源洗牌是 vperm2f128
(_mm256_permute2f128_pd
),直到 AVX512F 引入更精细的粒度 vpermt2pd
和 vpermt2ps
(以及等效的整数洗牌。
AVX1 没有任何 粒度小于 128 位的交叉洗牌,甚至没有 1 源版本。如果你需要一个,你必须用 vinsertf128
或 vperm2f128
+ 车道内洗牌来构建它。
因此,在 AVX 中将 3D 向量保留在 SIMD 向量中比在 float
中使用 128 位向量更糟糕。 http://fastcpp.blogspot.com/2011/04/vector-cross-product-using-sse-code.html 可能比标量快,但如果您为 SIMD 设计数据布局,则效果会差很多。
使用单独的连续数组 x[]
、y[]
和 z[]
,这样您就可以并行执行 4x 交叉乘积而无需改组,并利用FMA 指令。 使用 SIMD 并行执行多个向量,而不是加速单个向量。
查看 https://whosebug.com/tags/sse/info, especially https://deplinenoise.wordpress.com/2015/03/06/slides-simd-at-insomniac-games-gdc-2015/ 中的链接,其中很好地解释了数据布局问题,以及使用 SIMD 对循环的哪个级别进行矢量化。