如何在 sse 中实现有符号定点数学中向零的衰减?

Posted

技术标签:

【中文标题】如何在 sse 中实现有符号定点数学中向零的衰减?【英文标题】:How to implement decay towards zero in signed fixed point math, in sse? 【发布时间】:2018-10-03 15:34:22 【问题描述】:

有许多类似衰减的物理事件(例如体摩擦电荷泄漏),通常在像x' = x * 0.99这样的迭代器中建模,这通常非常易于用浮点算术编写。

但是,我需要在 sse 中以 16 位“8.8”签名定点方式执行此操作。为了在典型 ALU 上有效实现,提到的公式可以重写为 x = x - x/128;x = x - (x>>7) 其中 >> 是“算术”,符号扩展右移。

我卡在这里,因为_mm_sra_epi16() 产生完全违反直觉的行为,这很容易通过以下示例验证:

#include <cstdint>
#include <iostream>
#include <emmintrin.h>

using namespace std;

int main(int argc, char** argv) 
    cout << "required: ";
    for (int i = -1; i < 7; ++i) 
        cout << hex << (0x7fff >> i) << ", ";
    
    cout << endl;
    cout << "produced: ";
    __m128i a = _mm_set1_epi16(0x7fff);
    __m128i b = _mm_set_epi16(-1, 0, 1, 2, 3, 4, 5, 6);
    auto c = _mm_sra_epi16(a, b);
    for (auto i = 0; i < 8; ++i) 
        cout << hex << c.m128i_i16[i] << ", ";
    
    cout << endl;
    return 0;

输出如下:

required: 0, 7fff, 3fff, 1fff, fff, 7ff, 3ff, 1ff,
produced: 0, 0, 0, 0, 0, 0, 0, 0,

它只适用于所有的第一个转变,就像它实际上是_mm_sra1_epi16 函数一样,意外地命名为sra 并给__m128i 第二个参数提供了一个无缘无故的有趣子句。所以这不能在 SSE 中使用。

另一方面,我听说除法算法非常复杂,因此 _mm_div_epi16 在 SSE 中不存在,也无法使用。 该做什么以及如何实现/矢量化这种流行的“衰减”技术?

【问题讨论】:

_mm_sra_epi16 使用第二个源向量的低 64 位作为适用于所有元素的 64 位移位计数。这不是白痴,但每个元素的移位计数需要 AVX2(用于 32/64 位元素)或 AVX512BW 用于_mm_srav_epi16 或 64 位算术右移,这对于您尝试使用它的方式是有意义的。 (但移位计数是无符号的,所以-1 也将移出所有位)。 _mm_sra_epi16()“白痴”,因为它不是一个可变的转变?它基于最低通道将所有元素移动相同的量。如果你想要一个 16 位宽度的可变移位,你需要 AVX512。 @Mysticial 为什么_mm_add_epi16() 没有“基于最低车道”添加相同的数量?我认为它的行为不足并且违反了一致的接口,从而破坏了移植/矢量化。实际上,该指令应该命名为_mm_sra1_epi16() @PeterCordes 那么你认为这个任务没有解决方案吗? 为什么您认为每个元素需要不同的班次计数? 【参考方案1】:

x -= x&gt;&gt;7 使用 SSE2 实现是微不足道的,使用恒定的移位计数来提高效率。如果 AVX 可用,这将编译为 2 条指令,否则需要 movdqa 在破坏性右移之前复制 v

__m128i downscale(__m128i v)
    __m128i dec = _mm_srai_epi16(v, 7);
    return _mm_sub_epi16(v, dec);

GCC 甚至自动对其进行矢量化 (Godbolt)。

void foo(short *__restrict a) 
    for (int i=0 ; i<10240 ; i++) 
        a[i] -= a[i]>>7;  // inner loop uses the same psraw / psubw
    


float 不同,定点在整个范围内具有恒定的绝对精度,而不是恒定的相对精度。因此,对于小的正数,v&gt;&gt;7 将为零,您的递减将停止。 (负输入下溢到-1,因为算术右移向 -infinity 舍入。)

如果移位可能下溢到 0 的小输入,您可能需要与 _mm_set1_epi16(1) 进行 OR 以确保递减量不为零。对大输入的影响可以忽略不计。但是,这最终会使缩减链从 0 变为 -1。 (然后回到 0,因为 -1 | 1 == -1 在 2 的补码中)。

__m128i downscale_nonzero(__m128i v)
    __m128i dec = _mm_srai_epi16(v, 7);
    dec = _mm_or_si128(dec, _mm_set1_epi16(1));
    return _mm_sub_epi16(v, dec);

如果从负数开始,序列将是 -large,对数直到 -128,线性直到 -4、-3、-2、-1、0、-1、0、-1,...


您的代码全为零,因为_mm_sra_epi16 使用第二个源向量的低 64 位作为适用于所有元素的 64 位移位计数。 Read the manual。因此,您将所有位从每个 16 位元素中移出。

这不是白痴,但每个元素的移位计数需要 AVX2(用于 32/64 位元素)或 AVX512BW 用于 _mm_srav_epi16 或 64 位算术右移,这对于您尝试的方式是有意义的用它。 (但移位计数是无符号的,所以-1 也将移出所有位)。

确实,该指令应该命名为_mm_sra1_epi16()

是的,这是有道理的。但请记住,当这些被命名时,AVX2 _mm_srav_* 还不存在。此外,该特定名称并不理想,因为 1i 在视觉上并不是最明显的。 (i 是立即数,对于 psraw xmm1, imm16 形式而不是 asm 指令的 psraw xmm1, xmm2/m128 形式:http://felixcloutier.com/x86/PSRAW:PSRAD:PSRAQ.html)。

另一种有意义的方式是 MMX/SSE2 asm 指令有两种形式:立即数(当然,所有元素的计数相同)和向量。向量版本不是强迫您将计数广播到所有元素,而是采用向量寄存器底部的标量计数。我认为预期的用例是在 movd xmm0, eax 之后。


如果您需要不使用 AVX512 的每个元素变量的移位计数,请参阅有关模拟它的各种问答,例如Shifting 4 integers right by different values SIMD.

一些变通方法使用乘以 2 的幂进行可变左移,然后右移将数据放在需要的位置。 (但是您需要以某种方式准备好1&lt;&lt;n SIMD 向量,因此如果将同一组计数用于许多向量,或者特别是如果它是编译时常量,则此方法有效。

对于 16 位元素,您可以只使用一个 _mm_mulhi_epi16 来执行运行时变量右移计数,而不会造成精度损失或范围限制。 mulhi(x*y)(x*(int)y) &gt;&gt; 16 完全相同,因此您可以使用 y=1&lt;&lt;14 在该元素中右移 16-14 = 2。

【讨论】:

我知道了。刚刚看到一个任务x -= x &gt;&gt; y,在 Haswell 之前无法有效地矢量化。很高兴他们添加了指令,但很惊讶为什么通用 sra 没有与 addmulandor 基本指令一起添加。添加的是sra1,它被错误地命名为sra,并且用例更少。 我认为我做错了,我错过了另一种在这种情况下使用的衰减算法。至于位,它不需要收敛到零,因为 x*=.99 永远不会为零。 @xakepp35 你不是唯一一个注意到 SIMD 指令不一致并且遗漏很多情况的人。大多数问题已在 AVX512 中得到修复。迟到了,但至少英特尔有人注意到了。 AFAICT 现在,AVX512 中的大多数“明显但缺失”的功能要么没人使用,要么难以在硬件中实现。所以他们终于做对了。 我解决了! _mm_sub_epi16(x, _mm_srai_epi16(x, a))几乎等于_mm_slli_epi16(_mm_mulhi_epi16(x, b), 1),其中b = (1&lt;&lt;15) - (1&lt;&lt;(15-a))..几乎-是因为slli。虽然失去了一点精度,但第二种情况允许更精细的参数调整。

以上是关于如何在 sse 中实现有符号定点数学中向零的衰减?的主要内容,如果未能解决你的问题,请参考以下文章

在 Python 中,整数除法中向零舍入的好方法是啥?

定点数学比 armv7-a 上的浮点运算快吗?

如何在 numpy 中实现定点二进制支持

是否可以在没有 SSE4 的情况下在 VC++ 中向量化乘法?

如何通过遵循全局步骤在Keras中实现指数衰减学习率

设置定点数学属性