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 condition" 甚至更好,没有 "if condition"使这段代码更快。我不需要恰好在值 32000 处的饱和度,任何类似的值如 32768 都是可以接受的。
根据this page,ARM中有饱和指令。 x86/x64有没有类似的东西?
你确定你能打败编译器吗?
这是启用了最大尺寸优化的 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
我完全不相信尝试消除 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);
的事情,但这会产生相同的序列,而且源代码似乎不太可读,至少对我来说是这样。
如果您使用 Intel 的矢量指令,您可以做得比这好得多,但可能前提是您愿意在其中投入大量工作——特别是,您可能需要在整个(小)价值向量同时以这种方式完成很多事情。如果你真的这样做了,你通常会想采取一种与你现在似乎正在采取的方法有所不同的方法来完成这项任务。现在,您显然依赖于 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 位值的向量进行运算。
我正在优化瓶颈代码:
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 condition" 甚至更好,没有 "if condition"使这段代码更快。我不需要恰好在值 32000 处的饱和度,任何类似的值如 32768 都是可以接受的。
根据this page,ARM中有饱和指令。 x86/x64有没有类似的东西?
你确定你能打败编译器吗?
这是启用了最大尺寸优化的 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
我完全不相信尝试消除 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);
的事情,但这会产生相同的序列,而且源代码似乎不太可读,至少对我来说是这样。
如果您使用 Intel 的矢量指令,您可以做得比这好得多,但可能前提是您愿意在其中投入大量工作——特别是,您可能需要在整个(小)价值向量同时以这种方式完成很多事情。如果你真的这样做了,你通常会想采取一种与你现在似乎正在采取的方法有所不同的方法来完成这项任务。现在,您显然依赖于 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 位值的向量进行运算。