【发布时间】:2019-07-30 04:11:06
【问题描述】:
我正在学习如何使用 SIMD 内在函数和自动向量化。幸运的是,我正在处理一个有用的项目,它似乎非常适合 SIMD,但对于像我这样的新手来说仍然很棘手。
我正在为计算 2x2 像素的平均值的图像编写过滤器。我通过将两个像素的总和累加到一个像素中来完成部分计算。
template <typename T, typename U>
inline void accumulate_2x2_x_pass(
T* channel, U* accum,
const size_t sx, const size_t sy,
const size_t osx, const size_t osy,
const size_t yoff, const size_t oyoff
) {
const bool odd_x = (sx & 0x01);
size_t i_idx, o_idx;
// Should be vectorizable somehow...
for (size_t x = 0, ox = 0; x < sx - (size_t)odd_x; x += 2, ox++) {
i_idx = x + yoff;
o_idx = ox + oyoff;
accum[o_idx] += channel[i_idx];
accum[o_idx] += channel[i_idx + 1];
}
if (odd_x) {
// << 1 bc we need to multiply by two on the edge
// to avoid darkening during render
accum[(osx - 1) + oyoff] += (U)(channel[(sx - 1) + yoff]) * 2;
}
}
但是,godbolt 显示我的循环不可自动矢量化。 (https://godbolt.org/z/qZxvof) 我将如何构造 SIMD 内在函数来解决这个问题?我可以控制 accum 的对齐方式,但不能控制通道。
(我知道有一个平均内在函数,但在这里不合适,因为我需要生成多个 mip 级别,并且该命令会导致下一个级别的精度损失。)
谢谢大家。 :)
【问题讨论】:
-
看起来像 SSSE3
_mm_hadd_epi32或_mm_hadd_epi16的用例 T 是int16_t而不是int。它的成本与 2 个 shuffle + 一个垂直添加相同,但无论如何您都需要将 2 个输入打包为 1。如果您想解决 Intel CPU 上的 shuffle-port 瓶颈,您可以考虑在输入上使用 qword 移位,然后将结果与shufps混在一起。 -
哇,太酷了!我一直认为 SIMD 中不可能进行“水平”操作。我明天试试这个。对于它的价值,此操作的主要用例是 uint8_t -> uint16_t
-
我没有意识到你正在扩大,这完全改变了事情。 (此外,您在 Godbolt 上显示 short -> int;您的目标是什么 SSE/AVX 版本?您在 Godbolt 上使用了
-march=native,即 Skylake-AVX512 = AVX512BW)。无论如何,当U与T的宽度不同时,_mm_hadd_*没有用处。您可能希望pmaddwd或pmaddubsw的乘数为 1 以将水平对添加到更宽的结果中。 -
如果您知道这是您的目标,您应该始终使用
-march=haswell或类似名称。这设置了重要的调整选项以及指令集。并且不要使用-march=corei7,这有点无意义/令人困惑,因为它基本上是-march=nehalem(第一代核心i7)。 -
在 Godbolt 上,您可以像普通人一样使用
#include <stddef.h>和使用size_t。请注意,gcc 确实为 uint8_t -> uint16_t 自动矢量化了您的代码。不是特别很好,但确实做到了。
标签: c++ sse simd intrinsics avx