C++中饱和短 (int16)

Saturate short (int16) in C++

本文关键字:int16 C++      更新时间:2023-10-16

我正在优化瓶颈代码:

int sum = ........
sum = (sum >> _bitShift);
if (sum > 32000)
sum = 32000; //if we get an overflow, saturate output
else if (sum < -32000)
sum = -32000; //if we get an underflow, saturate output
short result = static_cast<short>(sum);

我想将饱和条件编写为一个"if 条件",甚至更好,没有"if 条件">以使此代码更快。我不需要完全在值 32000 处饱和,任何类似的值(如 32768(都是可以接受的。

根据此页面,ARM中有一个饱和指令。x86/x64 中有什么类似的东西吗?

我完全不相信试图消除if语句可能会有任何真正的好处。快速检查表明给定以下代码:

int clamp(int x) {
if (x < -32768)
x = -32768;
else if (x > 32767)
x = 32767;
return x;
}

。GCC 和 Clang 都产生如下无分支结果:

clamp(int):
cmp edi, 32767
mov eax, 32767
cmovg edi, eax
mov eax, -32768
cmp edi, -32768
cmovge eax, edi
ret

你可以做一些类似x = std::min(std::max(x, -32768), 32767);的事情,但这会产生相同的序列,并且源代码似乎不太可读,至少对我来说是这样。

如果你使用英特尔的向量指令,你可以做得比这好得多,但可能只有当你愿意投入相当多的工作时 - 特别是,你可能需要同时对整个(小(值向量进行操作才能以这种方式完成很多工作。如果你确实这样做,你通常希望采取一种与你现在看起来不同的方法来完成任务。现在,您显然依赖于int是 32 位类型,因此您在 32 位类型上进行算术运算,然后将其截断回(饱和(16 位值。

对于像AVX这样的东西,你通常需要使用像_mm256_adds_epi16这样的指令来获取16个值的向量(每个值16位(,并一次对所有这些值进行饱和加法(或者,同样,_mm256_subs_epi16进行饱和减法(。

由于您正在编写C++,因此我上面给出的是x86处理器的大多数当前编译器(gcc,icc,clang,msvc(中使用的编译器内部函数的名称。如果你直接编写汇编语言,指令将分别是vpaddsw和vpsubsw。

如果您可以依靠真正最新的处理器(支持 AVX 512 指令的处理器(,则可以使用它们来同时对 32 个 16 位值的向量进行操作。

你确定你能在这个方面击败编译器吗?

下面是启用了最大大小优化的 x64 零售。Visual Studio v15.7.5.

ECX 包含此块开头的初始值。 EAX 完成后填充饱和值。

return (x > 32767) ? 32767 : ((x < -32768) ? -32768 : x);
mov         edx,0FFFF8000h  
movzx       eax,cx  
cmp         ecx,edx  
cmovl       eax,edx  
mov         edx,7FFFh  
cmp         ecx,edx  
movzx       eax,ax  
cmovg       eax,edx