我正在尝试向量化一个进行大量迭代(超过 300 000 000 次)的循环以获得一些计算时间:
uint16_t* samples = (uint16_t*)pixmap->samples;
Image image(pixmap->w, pixmap->h);
uint8_t *dest = (uint8_t*)image[0];
for (int x = 0; x < len; x++)
{
dest[x] = samples[x] & 0xFF;
}
但是 qvec-report 说由于原因 1300,它无法矢量化。
根据MSDN,原因 1300 是:
循环体不包含或很少包含计算。
确实,我的循环体执行的计算很少,但由于迭代很多,所以仍然需要一些时间。
为什么在这种情况下没有完成矢量化?是因为不值得吗?如果是,为什么?
如果没有,有什么方法或技巧可以“强制”它吗?
基本上,循环体非常简单,因此按原样进行编译比对其进行矢量化更有效,因为矢量化的运行时成本会比按原样执行代码更大。
尝试强制它确实没有意义,因为编译器告诉您矢量化版本的效率低于非矢量化版本。如果您向循环添加更多计算,编译器可能会选择对其进行向量化。
您可能会混淆自动矢量化器(SSE/AVX)和自动并行化器(线程)。但这不是真正的问题,我怀疑:
dest[x] = samples[x] & 0xFF
真正的意思是dest[x] = static_cast<uint8_t>(static_cast<int>(samples[x]) & 0xFF)
。由于所有这些转换为不同的宽度,很难想出等效的 SSE 代码。 SSE2 允许您以 16x8 或 8x16 位组织 128 位寄存器,但在这里您混合了 8 和 16 位类型以及 32 位文字。
矢量化通常并不完美,并且错过了应该矢量化的东西。
它确实没有“计算”。但即使是简单的内存移动也可以矢量化,这样做是有道理的。在您的情况下,存在元素缩小,这可以在具有许多元素的并行指令中同时完成,并且元素非常小。
所以这个函数确实是一个很好的向量化候选函数,如果不是,那么它就是一个错过的优化。
我已经让你的重现变得独立,用多个版本的MSVC在Godbolt上编译它,并观察到它从17.10开始矢量化:https://godbolt.org/z/5fqcP61M1
当您希望向量化的函数未向量化时您可以执行的操作: