C++中饱和短 (int16)
Saturate short (int16) in C++
我正在优化瓶颈代码:
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