将高斯函数转换为 SSE
Converting gausian function into SSE
您好,我正在研究高斯模糊。在应用一维高斯核后,我使用下面的函数来计算像素值。我想将此功能转换为非常高效的 SSE,以便我可以获得显着的性能改进,但我从未使用过它,因此无法编写合适的功能。有人可以帮我解决这个问题吗?
struct PixelValue
{
uint32_t R; // 32bpp so that we don't overflow on below add
uint32 G;
uint32 B;
uint32 A;
};
Pixel FindPixelvalue(short* gausianFilter, short filterSize, unsigned int* pixels)
{
Pixel out;
const char* srcByte = reinterpret_cast< const char * >( pixels );
while ( filterSize > 0 )
{
short value = *gausianFilter;
out.R = out.R + *srcByte++ * value;
out.G = out.G + *srcByte++ * value;
out.B = out.B + *srcByte++ * value;
out.A = out.A + *srcByte++ * value;
gausianFilter++;
filterSize--;
}
return out;
}
要获得最大的加速,您可能需要一次计算多个像素。尝试一次获得单个像素的 SIMD 加速将需要更多的洗牌。
我假设您的像素颜色分量是 uint8_t
,即使您实际上将它们转换为 char
。 (char
可以是有符号的或无符号的。它在 Linux 或 Windows 64 位 ABI 中的 IDK,因为如果重要的话你做错了什么。)
这是对数据移动方式的第一次尝试。我认为这是次优的,洗牌太多。英特尔的 AVX case-study 并行计算多行的结果,因此他们可以在乘法之前 广播 单个高斯系数,而不需要将多个系数打乱成一个模式。
加载8个高斯系数(一个16B向量的8个词)
加载 8 个连续像素(两个 16B 向量,每个 4 像素):{R1 G1 B1 A1 R2 G2 B2 A2 ...}
、{R5 G5 B5 A5 ...}
- 交错下半部分 (
punpcklbw
) 所以你有 {R1 R5 G1 G5 B1 B5 A1 A5 R2 R6 ... }
。 (稍后,用高半部分重复此操作)
用零(punpcklbw
/ punpckhbw
)解包成两个单词元素向量
将高斯系数打乱为{C1 C5 C1 C5 C1 C5 ...}
pmaddwd
系数和像素数据之间。它垂直相乘,然后将水平对添加到 32 位元素中。这就是早期交织的动机,并安排高斯系数进行匹配。
- 对其他三组像素重复,系数
{C2 C6 C2 C6 ...}
- 将结果添加到累加器 (
paddd
)。
最后,您将得到一个包含四个元素的向量:{R G B A}
。
请参阅 x86 wiki 页面以获取指南链接(例如英特尔的内在函数指南,可帮助您找到所需指令的 C 内在函数)。
正如我所说,这可能不是最佳选择。 pmaddwd
是一个非常好的乘法加法,具有 16 位输入和 32 位输出,但是打乱数据以便可以将可以加在一起的元素水平相邻可能比仅使用较慢的 pmulld
( SSE4.1 正常 32 位压缩乘法)。这将使一次处理多个像素成为可能,并在系数数组的时间广播一个词。 (AVX2 vpbroadcastw
,或两步洗牌。)
您好,我正在研究高斯模糊。在应用一维高斯核后,我使用下面的函数来计算像素值。我想将此功能转换为非常高效的 SSE,以便我可以获得显着的性能改进,但我从未使用过它,因此无法编写合适的功能。有人可以帮我解决这个问题吗?
struct PixelValue
{
uint32_t R; // 32bpp so that we don't overflow on below add
uint32 G;
uint32 B;
uint32 A;
};
Pixel FindPixelvalue(short* gausianFilter, short filterSize, unsigned int* pixels)
{
Pixel out;
const char* srcByte = reinterpret_cast< const char * >( pixels );
while ( filterSize > 0 )
{
short value = *gausianFilter;
out.R = out.R + *srcByte++ * value;
out.G = out.G + *srcByte++ * value;
out.B = out.B + *srcByte++ * value;
out.A = out.A + *srcByte++ * value;
gausianFilter++;
filterSize--;
}
return out;
}
要获得最大的加速,您可能需要一次计算多个像素。尝试一次获得单个像素的 SIMD 加速将需要更多的洗牌。
我假设您的像素颜色分量是 uint8_t
,即使您实际上将它们转换为 char
。 (char
可以是有符号的或无符号的。它在 Linux 或 Windows 64 位 ABI 中的 IDK,因为如果重要的话你做错了什么。)
这是对数据移动方式的第一次尝试。我认为这是次优的,洗牌太多。英特尔的 AVX case-study 并行计算多行的结果,因此他们可以在乘法之前 广播 单个高斯系数,而不需要将多个系数打乱成一个模式。
加载8个高斯系数(一个16B向量的8个词)
加载 8 个连续像素(两个 16B 向量,每个 4 像素):
{R1 G1 B1 A1 R2 G2 B2 A2 ...}
、{R5 G5 B5 A5 ...}
- 交错下半部分 (
punpcklbw
) 所以你有{R1 R5 G1 G5 B1 B5 A1 A5 R2 R6 ... }
。 (稍后,用高半部分重复此操作) 用零(
punpcklbw
/punpckhbw
)解包成两个单词元素向量将高斯系数打乱为
{C1 C5 C1 C5 C1 C5 ...}
pmaddwd
系数和像素数据之间。它垂直相乘,然后将水平对添加到 32 位元素中。这就是早期交织的动机,并安排高斯系数进行匹配。- 对其他三组像素重复,系数
{C2 C6 C2 C6 ...}
- 将结果添加到累加器 (
paddd
)。
最后,您将得到一个包含四个元素的向量:{R G B A}
。
请参阅 x86 wiki 页面以获取指南链接(例如英特尔的内在函数指南,可帮助您找到所需指令的 C 内在函数)。
正如我所说,这可能不是最佳选择。 pmaddwd
是一个非常好的乘法加法,具有 16 位输入和 32 位输出,但是打乱数据以便可以将可以加在一起的元素水平相邻可能比仅使用较慢的 pmulld
( SSE4.1 正常 32 位压缩乘法)。这将使一次处理多个像素成为可能,并在系数数组的时间广播一个词。 (AVX2 vpbroadcastw
,或两步洗牌。)