浮点数的乘法和加法

Multiplying and adding float numbers

我的任务是将一些 c++ 代码转换为 asm,我想知道我的想法是否有意义。首先,我会将整数转换为浮点数。我想获取数组数据到 sse 寄存器,但这里有问题,因为我只想要 3 个而不是 4 个整数,有没有办法克服这个问题?然后我将使用 CVTDQ2PS 将这些整数转换为浮点数,并将这些数字保存在内存中。对于像 0.393 这样的常量数字,我会制作 3 个浮点向量,然后我会做三次相同的操作,所以我只会考虑 sepiaRed。为此,我会将转换后的整数放入 sse 寄存器中,然后将这些数字相乘,得到 xmm0 寄存器中的结果。现在如何将它们加在一起?

我想我的两个问题是:如何从数组中获取 3 个项目到 sse 寄存器,这样我就可以避免任何问题。然后如何将xmm0寄存器中的三个数加在一起。

    tmpGreen = (float)pixels[i + 1];
    tmpRed = (float)pixels[i + 2];
    tmpBlue = (float)pixels[i];

    sepiaRed = (int)(0.393 * tmpRed + 0.769 * tmpGreen + 0.189 * tmpBlue); //red
    sepiaGreen = (int)(0.349 * tmpRed + 0.686 * tmpGreen + 0.168 * tmpBlue); //green
    sepiaBlue = (int)(0.272 * tmpRed + 0.534 * tmpGreen + 0.131 * tmpBlue); //blue

您不能轻易地将 3 个数字水平相加; Fastest way to do horizontal SSE vector sum (or other reduction)

您可以有效地做的是并行映射 4 个像素,使用 4 个红色、4 个绿色和 4 个蓝色的矢量。 (您希望从平面而非交错的像素数据加载。数组结构,而不是结构数组。)

不过,如果您仅使用 movdqu 加载 4 个整数并使用乘数0.0cvtdq2ps后的高位元素。然后,您可以对 4 个元素进行正常的水平求和,而不必对其进行调整。 (嗯,虽然做 3 会让你在第一次添加的同时进行第二次洗牌,而不是之后。)

低效地使用 SIMD 会失去一些好处;请参阅 https://whosebug.com/tags/sse/info especially https://deplinenoise.wordpress.com/2015/03/06/slides-simd-at-insomniac-games-gdc-2015/ 中的指南:人们经常如何尝试使用一个 SIMD 向量来保存一个 x、y、z 几何向量,然后发现 SIMD 并没有多大帮助。

如果您关心速度,则应避免通过浮点域并仅使用定点(并使用 8/16 位算法)。

尽管(双精度)浮点因子在 10 进制中很短,但在 2 进制中并不短:

0.393 = 3.93000000000000015987211554602E-1 == 0x3FD926E978D4FDF4
0.168 = 1.68000000000000010436096431476E-1 == 0x3FC5810624DD2F1B
etc.

假设原始整数 r、g、b 数据限制在 0..255 范围内,因子中最右边的位没有贡献。因此我们也可以截断或舍入二进制表示。

如果7位的系数精度足够了,我们可以得出

的系数矩阵
50   98   24 == 0x32 0x62 0x18
45   88   22 == 0x2d 0x58 0x16
35   68   17 == 0x23 0x44 0x11 

7 位,因为在 SSE 中计算小点积的最快方法是 _mm_maddubs_epi16,它可以将 uint8_t RGB 与 8 位有符号(或 7 位无符号)系数相乘。

然后我们需要正确安排输入矩阵和系数矩阵。

选项 1:交错

R0G0B0R1G1B1R2G2B2R3G3B3R4G4B4R5G5B5R6G6B6...

选项 2:平面:

R0R1R2R3...   + G0G1G2G3...     + B0B1B2B3....

无论哪种方式,目标都是将数据重新洗牌到

xmm0 = R0G0R1G1R2G2R3G3R4G4R5G5R6G6R7G7
xmm1 = B0xxB1xxB2xxB3xxB4xxB5xxB6xxB7xx

rg0  = 326232623262...
b0.  = 180018001800...

r_new_0 = _mm_maddubs_epi16(xmm0, rg0);
g_new_0 = _mm_maddubs_epi16(xmm0, rg1);
b_new_0 = _mm_maddubs_epi16(xmm0, rg2);

r_new_1 = _mm_maddubs_epi16(xmm1, b0);
g_new_1 = _mm_maddubs_epi16(xmm1, b1);
b_new_1 = _mm_maddubs_epi16(xmm1, b2);

r_new_0 = _mm_add_epi16(r_new_0, r_new_1);
g_new_0 = _mm_add_epi16(g_new_0, g_new_1);
b_new_0 = _mm_add_epi16(b_new_0, b_new_1);

然后我们需要右移7位,转换为uint8_t。这个转换需要饱和,因为每一列的系数之和都大于128。

 r_new_0 = _mm_srli_epi16(r_new_0, 7);
 r_new_0 = _mm_packus_epi16(r_new_0, r_new_0);
 ... and same for g_new_0, b_new_0

这最后一步显示出非常低的效率,因为一半的寄存器容量丢失了;消耗 24 个字节的输入,我们产生了 8+8+8 个输出。

开始使用 16+16+16 个输入字节可能会更好,这会导致 12 次乘法,第一次加法及时完成。

如果你绝对坚持使用浮点数,我会使用类似这样的交错数据(如你的情况):

    auto data_ptr = reinterpret_cast<const uint32_t *>(pixels);

    __m128i rgbi = _mm_cvtsi32_si128(*data_ptr);
#if SSE4_ENABLED
    rgbi = _mm_cvtepu8_epi32(rgbi);
#elif SSE3_ENABLED
    auto const k0123 = _mm_set_epi32(-1,2,1,0);
    rgbi = _mm_shuffle_epi32(rgbi, k0123);
#else
    rgbi = _mm_unpacklo_epi8(rgbi, _mm_setzero_si128());
    rgbi = _mm_unpacklo_epi16(rgbi, _mm_setzero_si128());
#endif
    // having expanded 3 x uint8_t -> 3 x int32_t + garbage
    auto rgb_f = _mm_cvtepi32_ps(rgbi);

    // shuffle the rgb to gbr and brg
    auto gbr_f = _mm_shuffle_ps(rgb_f, rgb_f, 0b00001001);
    auto brg_f = _mm_shuffle_ps(rgb_f, rgb_f, 0b00010010);

    // now we multiply the permuted rgb vectors with
    // permuted coefficients
    rgb_f = _mm_mul_ps(rgb_f, coeffs0);
    gbr_f = _mm_mul_ps(gbr_f, coeffs1);
    brg_f = _mm_mul_ps(brg_f, coeffs2);

    // sum up vertically
    // for rgb_f[0] = R, rgb_f[1] = G, rgb_f[2] = B
    rgb_f = _mm_add_ps(rgb_f, gbr_f);
    rgb_f = _mm_add_ps(rgb_f, brg_f);

    rgbi = _mm_cvtepi32_ps(rgb_f); // back to 32-bit integer

 

然后在饱和时转换回 uint8_t -- SSE4.1 有 _mm_packus_epi32 和 SSE2 有 _mm_packus_epi16,可以代替使用,因为 [=20 的预期范围=] 正好在 0 到 345 之间,因此适合 int16_t。但是不幸的是,使用 packus_epi16 会在连续的输出通道之间留下零,如果没有 _mm_shuffle_epi8 就很难重新洗牌,而 _mm_shuffle_epi8 只能从 SSSE3 开始使用。

无论如何我们看到,预先安排有助于消除水平积累,但我们也看到我们失去了大约 25% 的计算能力,因为不使用 #3 车道并花时间进行洗牌。输入的布局应该修改...