C++ 删除多个_mm256_blend_p会降低性能,而不是提高性能

C++ 删除多个_mm256_blend_p会降低性能,而不是提高性能,c++,performance,simd,avx,C++,Performance,Simd,Avx,我正在编写一个小模板库,使用AVX内部函数转置任意矩阵。由于我大量使用if constexpr和模板,我想确保编译器应用了我期望的所有优化,并对我的代码进行了基准测试。我遇到了一个我不太明白的结果 这些函数有一个模板参数,用于控制如何处理未使用的寄存器值。一种选择是在执行的操作过程中,采取任何最终到达那里的方法。另一种方法是只写入存储结果所需的条目。我已经删除了所有模板内容,并为7x4矩阵编写了一个简短示例: 编辑:此代码错误---请参阅更新 void Transpose7x4(\uuuuuum

我正在编写一个小模板库,使用AVX内部函数转置任意矩阵。由于我大量使用
if constexpr
和模板,我想确保编译器应用了我期望的所有优化,并对我的代码进行了基准测试。我遇到了一个我不太明白的结果

这些函数有一个模板参数,用于控制如何处理未使用的寄存器值。一种选择是在执行的操作过程中,采取任何最终到达那里的方法。另一种方法是只写入存储结果所需的条目。我已经删除了所有模板内容,并为7x4矩阵编写了一个简短示例:

编辑:此代码错误---请参阅更新

void Transpose7x4(\uuuuuum256 in0、\uuuum256 in1、\uuuum256 in2、\uuuuum256 in3、\uuuuum256&out0、\uuuuuuum256&out1、\uuuuum256&out2、,
__m256&out3、m256&out4、m256&out5、m256&out6)
{
__m256 tout0、tout1、tout2、tout3、tout4、tout5、tout6;
__m256 tmp0、tmp1、tmp2、tmp3;
__m256 tmp4=_mm256_unplo_ps(in3,in0);
__m256 tmp5=_mm256_unpachi_ps(in3,in0);
__m256 tmp6=_mm256_unplo_ps(in1,in2);
__m256 tmp7=_mm256_unpachi_ps(in1,in2);
tmp0=mm256_shuffle_ps(tmp4,tmp6,0x44);
tmp1=mm256_shuffle_ps(tmp6,tmp4,0xee);
tmp2=_mm256_shuffle_ps(tmp5,tmp7,0x44);
tmp3=mm256_shuffle_ps(tmp7,tmp5,0xee);
tout0=_mm256_permute2f128_ps(tmp0,tmp0,0x00);
tout1=_mm256_permute2f128_ps(tmp1,tmp1,0x00);
tout2=_mm256_permute2f128_ps(tmp2,tmp2,0x00);
tout3=_mm256_permute2f128_ps(tmp3,tmp3,0x00);
tout4=_mm256_permute2f128_ps(tmp0,tmp0,0x44);
tout5=_mm256_permute2f128_ps(tmp1,tmp1,0x44);
tout6=_mm256_permute2f128_ps(tmp2,tmp2,0x44);
//不关心写入未使用值的内容
out0=tout0;
out1=tout1;
out2=tout2;
out3=tout3;
out4=tout4;
out5=tout5;
out6=tout6;
//仅写入存储结果所需的值
//out0=mm256_blend_ps(out0,tout0,0xfe);
//out1=mm256_blend_ps(out1,tout1,0xfe);
//out2=mm256_blend_ps(out2,tout2,0xfe);
//out3=mm256_blend_ps(out3,tout3,0xfe);
//out4=mm256_blend_ps(out4,tout4,0xfe);
//out5=mm256_blend_ps(out5,tout5,0xfe);
//out6=mm256_blend_ps(out6,tout6,0xfe);
}
如您所见,不覆盖未使用值的版本需要额外的混合,因此我希望它会稍微慢一点。然而,基准测试的结果(英特尔skylake处理器上的Clang 8.0.0和GCC 8.3.0)告诉我的情况并非如此。100次换位给了我大约430ns的混合版本,而另一个版本大约670ns。我检查了组件是否有奇怪的事情发生,但我什么也看不到:

程序集或多或少是相同的,只有一个版本有
vmovaps
与其他
vblendps
交错(和一个vperm2f128)

考虑到
\u mm256\u permute2f128\u ps
的指令管道,我计算了预期的时钟周期。对于代码,在没有混合的情况下,我提出了17个循环。乘以100,再除以我的处理器频率,得到425ns,这和我在混合版本中得到的差不多。我能理解的唯一原因是,没有混合的版本需要更多时间,因为某种原因,
\u mm256\u permute2f128\u ps
的指令管道无法使用。如果我在假设下计算预期计时,每个
\u mm256\u permute2f128\u ps
需要3个时钟周期,我得到725ns,这与我得到的结果更接近


所以问题是,为什么混合版本比“简单”版本更快(利用指令管道),以及我如何解决这个问题。

找到了解决方案。彼得·科尔德斯的评论把我推向了正确的方向。我的基准有些问题。我正在使用谷歌基准测试,以下是我使用的原始基准测试代码:

#包括
#包括

#包括

每个_mm256_permute2f128_ps需要3个时钟周期不,这是车道交叉洗牌的延迟。在HSW/SKL上,洗牌吞吐量为1/时钟。你是如何衡量业绩的?您确定要让CPU达到最高速度(max turbo,而不是空闲)?在Godbolt链接中,每次执行函数4.3ns看起来很正常:我计算了12条随机指令,因此12个周期/4.3ns=~2.8 GHz,假设这会使随机端口饱和,并且实际上每个时钟运行1条随机指令,而没有其他瓶颈。您还没有指定任何关于您的硬件的内容,或者您是否正在测试tput或latency@PeterCordes我在基准测试本身中发现了问题,并将解决方案添加到我的帖子中。你对时间的怀疑促使我再次检查我的基准。谢谢。我还写了我是如何想出17个周期的。我仍然需要弄清楚,编译器正在做什么来消除3个通道间排列。@彼得科德斯找到了3个
\u mm256\u permute2f128\u ps
被优化掉的原因。请参阅答案中的更新。如果这是完整答案,请将其作为答案发布,而不是作为现在非问题的一部分给它一个特殊的位置。我将尽快将相应的部分移至答案。
---------------------------------------------------------------------------
Benchmark                                 Time             CPU   Iterations
---------------------------------------------------------------------------
FixtureBenchmark_m256/7x4_assign        646 ns          646 ns      1081509
FixtureBenchmark_m256/7x4_blend         380 ns          380 ns      1847485
---------------------------------------------------------------------------
Benchmark                                 Time             CPU   Iterations
---------------------------------------------------------------------------
FixtureBenchmark_m256/7x4_assign       3.27 ns         3.27 ns    214698649
FixtureBenchmark_m256/7x4_blend        4.15 ns         4.14 ns    168642478