用于灰度到 ARGB 转换的 C++ SSE2 或 AVX2 内在函数
C++ SSE2 or AVX2 intrinsics for grayscale to ARGB conversion
我想知道是否要执行 SSE2/AVX2 整数指令或指令序列(或内在函数)以实现以下结果:
给定一行 8 字节像素,格式如下:
A = {a, b, c, d, e, f, g, h}
有没有办法将这些像素加载到包含 8 个 32 位 ARGB 像素的 YMM 寄存器中,以便将初始灰度值广播到每个相应 32 位像素的其他 2 个字节?结果应该是这样的:(0 是 alpha 值)
B = {0aaa, 0bbb, 0ccc, 0ddd, 0eee, 0fff, 0ggg, 0hhh}
我是矢量扩展的完全初学者,所以我什至不确定如何处理这个问题,或者是否有可能。
如有任何帮助,我们将不胜感激。谢谢!
更新1
感谢您的回答。我仍然有问题:
我把这个小例子放在一起,用 VS2015 在 x64 上编译。
int main()
{
unsigned char* pixels = (unsigned char*)_aligned_malloc(64, 32);
memset(pixels, 0, 64);
for (unsigned char i = 0; i < 8; i++)
pixels[i] = 0xaa + i;
__m128i grayscalePix = _mm_load_si128((const __m128i*)pixels);
__m256i rgba = _mm256_cvtepu8_epi32(grayscalePix);
__m256i mulOperand = _mm256_set1_epi32(0x00010101);
__m256i result = _mm256_mullo_epi32(rgba, mulOperand);
_aligned_free(pixels);
return 0;
}
问题是做了之后
__m256i rgba = mm256_cvtepu8_epi32(grayscalePix)
rgba 只设置了前四个双字。后四位都是0.
英特尔开发人员手册说:
VPMOVZXBD ymm1, xmm2/m64
Zero extend 8 packed 8-bit integers in the low 8
bytes of xmm2/m64 to 8 packed 32-bit integers in
ymm1.
我不确定这是有意为之还是我仍然遗漏了什么。
谢谢。
您可以将压缩字节加载到寄存器中,
call __m256i _mm256_cvtepu8_epi32 (__m128i a)
转换为 32 位值,然后乘以 0x00010101 将灰度复制到 R、G 和 B。
更新:@chtz 的回答是一个更好的主意,使用便宜的 128->256 广播负载而不是 vpmovzx
来馈送 vpshufb
,节省随机端口带宽。
按照 Mark 的建议从 PMOVZX 开始。
但在那之后,PSHUFB (_mm256_shuffle_epi8
) 将比 PMULLD 快得多,只是它与 PMOVZX 竞争 shuffle 端口。 (而且它在车道内运行,所以你仍然需要 PMOVZX)。
因此,如果您只关心吞吐量而不关心延迟,那么 _mm256_mullo_epi32
很好。但是,如果延迟很重要,或者如果您的吞吐量瓶颈不是每个向量 2 个洗牌端口指令,那么 PSHUFB 复制每个像素内的字节应该是最好的。
实际上,即使对于吞吐量,_mm256_mullo_epi32
在 HSW 和 BDW 上也很糟糕:p0 为 2 uops(10c 延迟),因此一个端口为 2 uops。
在 SKL 上,p01 为 2 微指令(10c 延迟),因此它可以维持与 VPMOVZXBD 相同的每个时钟吞吐量。但这是一个额外的 1 uop,使其更容易成为瓶颈。
(在所有支持 AVX2 的英特尔 CPU 上,对于端口 5,VPSHUFB 为 1 uop,1c 延迟。)
一个vbroadcasti128
和两个vpshufb
可以转换16个像素。广播不需要端口 5,如果它直接从内存加载,因此 shuffle 可以充分利用该端口(它仍然会在该端口或存储回内存时成为瓶颈)。
void gray2rgba(char const* input, char* output, size_t length)
{
length &= size_t(-16); // lets just care about sizes multiples of 16 here ...
__m256i shuflo = _mm256_setr_epi32(
0x80000000, 0x80010101, 0x80020202, 0x80030303,
0x80040404, 0x80050505, 0x80060606, 0x80070707
);
__m256i shufhi = _mm256_setr_epi32(
0x80080808, 0x80090909, 0x800a0a0a, 0x800b0b0b,
0x800c0c0c, 0x800d0d0d, 0x800e0e0e, 0x800f0f0f
);
for(size_t i=0; i<length; i+=16)
{
__m256i in = _mm256_broadcastsi128_si256(*reinterpret_cast<const __m128i*>(input+i));
__m256i out0 = _mm256_shuffle_epi8(in, shuflo);
__m256i out1 = _mm256_shuffle_epi8(in, shufhi);
_mm256_storeu_si256(reinterpret_cast<__m256i*>(output+4*i), out0);
_mm256_storeu_si256(reinterpret_cast<__m256i*>(output+4*i+32), out1);
}
}
我想知道是否要执行 SSE2/AVX2 整数指令或指令序列(或内在函数)以实现以下结果:
给定一行 8 字节像素,格式如下:
A = {a, b, c, d, e, f, g, h}
有没有办法将这些像素加载到包含 8 个 32 位 ARGB 像素的 YMM 寄存器中,以便将初始灰度值广播到每个相应 32 位像素的其他 2 个字节?结果应该是这样的:(0 是 alpha 值)
B = {0aaa, 0bbb, 0ccc, 0ddd, 0eee, 0fff, 0ggg, 0hhh}
我是矢量扩展的完全初学者,所以我什至不确定如何处理这个问题,或者是否有可能。
如有任何帮助,我们将不胜感激。谢谢!
更新1
感谢您的回答。我仍然有问题:
我把这个小例子放在一起,用 VS2015 在 x64 上编译。
int main()
{
unsigned char* pixels = (unsigned char*)_aligned_malloc(64, 32);
memset(pixels, 0, 64);
for (unsigned char i = 0; i < 8; i++)
pixels[i] = 0xaa + i;
__m128i grayscalePix = _mm_load_si128((const __m128i*)pixels);
__m256i rgba = _mm256_cvtepu8_epi32(grayscalePix);
__m256i mulOperand = _mm256_set1_epi32(0x00010101);
__m256i result = _mm256_mullo_epi32(rgba, mulOperand);
_aligned_free(pixels);
return 0;
}
问题是做了之后
__m256i rgba = mm256_cvtepu8_epi32(grayscalePix)
rgba 只设置了前四个双字。后四位都是0.
英特尔开发人员手册说:
VPMOVZXBD ymm1, xmm2/m64
Zero extend 8 packed 8-bit integers in the low 8 bytes of xmm2/m64 to 8 packed 32-bit integers in ymm1.
我不确定这是有意为之还是我仍然遗漏了什么。
谢谢。
您可以将压缩字节加载到寄存器中,
call __m256i _mm256_cvtepu8_epi32 (__m128i a)
转换为 32 位值,然后乘以 0x00010101 将灰度复制到 R、G 和 B。
更新:@chtz 的回答是一个更好的主意,使用便宜的 128->256 广播负载而不是 vpmovzx
来馈送 vpshufb
,节省随机端口带宽。
按照 Mark 的建议从 PMOVZX 开始。
但在那之后,PSHUFB (_mm256_shuffle_epi8
) 将比 PMULLD 快得多,只是它与 PMOVZX 竞争 shuffle 端口。 (而且它在车道内运行,所以你仍然需要 PMOVZX)。
因此,如果您只关心吞吐量而不关心延迟,那么 _mm256_mullo_epi32
很好。但是,如果延迟很重要,或者如果您的吞吐量瓶颈不是每个向量 2 个洗牌端口指令,那么 PSHUFB 复制每个像素内的字节应该是最好的。
实际上,即使对于吞吐量,_mm256_mullo_epi32
在 HSW 和 BDW 上也很糟糕:p0 为 2 uops(10c 延迟),因此一个端口为 2 uops。
在 SKL 上,p01 为 2 微指令(10c 延迟),因此它可以维持与 VPMOVZXBD 相同的每个时钟吞吐量。但这是一个额外的 1 uop,使其更容易成为瓶颈。
(在所有支持 AVX2 的英特尔 CPU 上,对于端口 5,VPSHUFB 为 1 uop,1c 延迟。)
一个vbroadcasti128
和两个vpshufb
可以转换16个像素。广播不需要端口 5,如果它直接从内存加载,因此 shuffle 可以充分利用该端口(它仍然会在该端口或存储回内存时成为瓶颈)。
void gray2rgba(char const* input, char* output, size_t length)
{
length &= size_t(-16); // lets just care about sizes multiples of 16 here ...
__m256i shuflo = _mm256_setr_epi32(
0x80000000, 0x80010101, 0x80020202, 0x80030303,
0x80040404, 0x80050505, 0x80060606, 0x80070707
);
__m256i shufhi = _mm256_setr_epi32(
0x80080808, 0x80090909, 0x800a0a0a, 0x800b0b0b,
0x800c0c0c, 0x800d0d0d, 0x800e0e0e, 0x800f0f0f
);
for(size_t i=0; i<length; i+=16)
{
__m256i in = _mm256_broadcastsi128_si256(*reinterpret_cast<const __m128i*>(input+i));
__m256i out0 = _mm256_shuffle_epi8(in, shuflo);
__m256i out1 = _mm256_shuffle_epi8(in, shufhi);
_mm256_storeu_si256(reinterpret_cast<__m256i*>(output+4*i), out0);
_mm256_storeu_si256(reinterpret_cast<__m256i*>(output+4*i+32), out1);
}
}