C# 如何在两个AVX2向量之间交换128位部分
问题:我有4 x 256位AVX2向量(A、B、C、D),我需要对它们各自的128位部分以及两个不同向量之间执行交换操作。这是我需要做的转变C# 如何在两个AVX2向量之间交换128位部分,c#,c++,.net,avx2,C#,C++,.net,Avx2,问题:我有4 x 256位AVX2向量(A、B、C、D),我需要对它们各自的128位部分以及两个不同向量之间执行交换操作。这是我需要做的转变 Original Transformed || Low Lane || High Lane|| || Low Lane || High Lane|| A = || L1 || H1 || = > || L1 || L2
Original Transformed
|| Low Lane || High Lane|| || Low Lane || High Lane||
A = || L1 || H1 || = > || L1 || L2 ||
B = || L2 || H2 || = > || H1 || H2 ||
C = || L3 || H3 || = > || L3 || L4 ||
D = || L4 || H4 || = > || H3 || H4 ||
基本上,我需要按以下顺序将输出存储到数组中:L1、L2、L3、L4、H1、H2、H3、H4。
我当前的解决方案正在使用:4x\u mm256\u blend\u epi32(最坏情况:延迟1,吞吐量0.35)
4x毫米256字节2x128毫米si256(最坏情况:延迟3,吞吐量1)
如果我理解正确的话,我想你可以不用2x4转置的混合指令,创建新的变量来选择你想要的车道。比如:
__m256i a; // L1 H1
__m256i b; // L2 H2
__m256i c; // L3 H3
__m256i d; // L4 H4
__m256i A = _mm256_permute2x128_si256(a, b, 0x20); // L1 L2
__m256i B = _mm256_permute2x128_si256(a, b, 0x31); // H1 H2
__m256i C = _mm256_permute2x128_si256(c, d, 0x20); // L3 L4
__m256i D = _mm256_permute2x128_si256(c, d, 0x31); // H3 H4
仍然有vperm2i128
指令的3个周期延迟,但当数据跨越128位通道时,总是会有3个周期延迟。这4个洗牌是独立的,因此它们可以管道化(ILP);英特尔和Zen 2具有vperm2i128
(,)的1/时钟吞吐量
如果幸运的话,编译器会将L1、L2和L3、L4混洗优化为vinserti128
,AMD Zen 1运行效率更高(1个uop而不是8个;车道交叉混洗被拆分为多个128位uop)
这4个随机播放端口(Intel上的端口5)需要4个UOP;Intel和Zen2对于这些洗牌只有1/时钟洗牌吞吐量。如果这将是你循环中的一个瓶颈,考虑@ ChTZ的答案,通过做2次洗牌来提高4前端的吞吐量,以准备廉价的共混物(<代码> VPBLDEND )。相关:您可以使用两个permutes和4个混合液进行操作,提供2个循环的绝对吞吐量:
void foo(
__m256i a, // L1 H1
__m256i b, // L2 H2
__m256i c, // L3 H3
__m256i d, // L4 H4
__m256i* outputPtr
)
{
// permute. Port usage: 1*p5, Latency 3 on both inputs
__m256i BA = _mm256_permute2x128_si256(a, b, 0x21); // H1 L2
__m256i DC = _mm256_permute2x128_si256(c, d, 0x21); // H3 L4
// blend. Port usage: 1*p015, Latency 1 on both inputs
__m256i A = _mm256_blend_epi32(a, BA, 0xf0); // L1 L2
__m256i B = _mm256_blend_epi32(BA, b, 0xf0); // H1 H2
__m256i C = _mm256_blend_epi32(c, DC, 0xf0); // L3 L4
__m256i D = _mm256_blend_epi32(DC, d, 0xf0); // H3 H4
_mm256_store_si256(outputPtr+0, A);
_mm256_store_si256(outputPtr+1, B);
_mm256_store_si256(outputPtr+2, C);
_mm256_store_si256(outputPtr+3, D);
}
但是,根据上下文(特别是如果
a
,…,d
最初是从内存中读取的,m128
内存操作数也最好使用and指令序列。您将有两倍的负载,但在端口5上没有层间延迟和瓶颈——关于延迟和端口使用,基于内存的vinsert128
表现得像一个混合。这是一个消除4x混合操作的伟大解决方案!仍在学习AVX2集..我可以使用_mm256_permute2x128_si256而不是_mm256_permute2f128_ps吗?还是有具体的原因?好的,它也适用于第一个变量…不,您可以使用整数版本。我误读了您最初的示例,没有抓住您有整数数据的事实。我会编辑。很有趣,因为我对代码矢量化非常陌生,我不熟悉“端口”这个术语,你能详细说明一下吗?我正在用broutcast从内存中读取数据<代码>uint*状态=新的uint[32]{0、1、2、3、20、21、22、23、4、5、6、7、24、25、26、27、8、9、10、11、28、29、30、31、12、13、14、15、32、33、34、35};a=Avx2.BroadcastVector128ToVector256(状态);b=Avx2.BroadcastVector128ToVector256(状态+Vector128.Count);c=Avx2.BroadcastVector128ToVector256(状态+Vector128.Count*2);d=Avx2.BroadcastVector128ToVector256(状态+Vector128.Count*3)代码>我有理由使用“广播”,因为我同时计算2块chacha20密码,只是为了澄清。另外,如何计算绝对吞吐量提供2个周期的绝对吞吐量
@xtremertx:我在Jason的答案中添加了3个周期延迟数的来源链接。“思想输出”仅适用于整个街区,包括周边代码;如果您仍然不会在随机端口吞吐量上出现瓶颈,而是在前端uop吞吐量上出现瓶颈(或者更糟的是,在延迟上),那么请使用Jason答案中的4指令方式,而不是这个答案中的6指令方式。e、 g.如果在循环中的这些洗牌步骤之间有大量和/或/或移位工作,可能会优化为更少的指令。@xtremertx是直接在“转置”之前发生的广播负载吗?或者两者之间是否有指示?另外,我正确理解了C#-AVX语法:在广播a={0,1,2,3,0,1,2,3}
,b={20,21,22,23,20,21,22,23}
等之后,@chtz很可能有多种解决方案如何使用AVX2对chacha20进行矢量化,但是这种讨论需要一个新的主题。我使用下面的方法更详细地解释了一些事情,在发布时,它们比chromium项目有更好的性能。我基本上是使用AVX2同时计算两个密钥流块(他们在论文中称之为双四分之一)。
__m256i a; // L1 H1
__m256i b; // L2 H2
__m256i c; // L3 H3
__m256i d; // L4 H4
__m256i A = _mm256_permute2x128_si256(a, b, 0x20); // L1 L2
__m256i B = _mm256_permute2x128_si256(a, b, 0x31); // H1 H2
__m256i C = _mm256_permute2x128_si256(c, d, 0x20); // L3 L4
__m256i D = _mm256_permute2x128_si256(c, d, 0x31); // H3 H4
void foo(
__m256i a, // L1 H1
__m256i b, // L2 H2
__m256i c, // L3 H3
__m256i d, // L4 H4
__m256i* outputPtr
)
{
// permute. Port usage: 1*p5, Latency 3 on both inputs
__m256i BA = _mm256_permute2x128_si256(a, b, 0x21); // H1 L2
__m256i DC = _mm256_permute2x128_si256(c, d, 0x21); // H3 L4
// blend. Port usage: 1*p015, Latency 1 on both inputs
__m256i A = _mm256_blend_epi32(a, BA, 0xf0); // L1 L2
__m256i B = _mm256_blend_epi32(BA, b, 0xf0); // H1 H2
__m256i C = _mm256_blend_epi32(c, DC, 0xf0); // L3 L4
__m256i D = _mm256_blend_epi32(DC, d, 0xf0); // H3 H4
_mm256_store_si256(outputPtr+0, A);
_mm256_store_si256(outputPtr+1, B);
_mm256_store_si256(outputPtr+2, C);
_mm256_store_si256(outputPtr+3, D);
}