夹紧unsigned char

我有一个简单的C函数如下:

unsigned char clamp(short value){ if (value  0xff) return 0xff; return value; } 

是否可以在不使用任何if / else分支的情况下重写它,同时提高效率?

编辑:

我基本上希望看看是否可以进行一些基于位运算的基于算法的钳位实现。 目标是在GPU(图形处理单元)上处理图像。 这种类型的代码将在每个像素上运行。 我想如果可以避免分支,那么GPU上的整体吞吐量会更高。

像(值 255)?255:值))这样的解决方案只是if / else分支与语法糖的重复。 所以我不是在寻找它。

编辑2:

我可以把它减少到一个如果如下,但我无法想得更好:

 unsigned char clamp(short value){ int more = value >> 8; if(more){ int sign = !(more >> 7); return sign * 0xff; } return value; } 

编辑3:

刚刚在FFmpeg代码中看到了一个非常好的实现:

 /** * Clip a signed integer value into the 0-255 range. * @param a value to clip * @return clipped value */ static av_always_inline av_const uint8_t av_clip_uint8_c(int a) { if (a&(~0xFF)) return (-a)>>31; else return a; } 

这肯定有效,如果很好的话可以减少到一个。

你写的是你想避免在GPU上进行分支。 确实,在并行环境中分支可能非常昂贵,因为必须评估两个分支或者必须应用同步。 但是如果分支足够小,代码将比大多数算术更快。 CUDA C最佳实践指南描述了原因:

有时,编译器可以[..]通过使用分支预测来优化if或switch语句。 在这些情况下,任何翘曲都不会发生变化。 [..]

当使用分支预测时,不会跳过执行取决于控制条件的指令。 相反,它们中的每一个都与基于控制条件设置为真或假的每线程条件代码或谓词相关联,并且尽管这些指令中的每一个都被调度执行,但实际上只执行具有真实谓词的指令。 带有错误谓词的指令不会写入结果,也不会评估地址或读取操作数。

分支预测很快。 血腥快! 如果你看一下优化编译器生成的中间PTX代码,你会发现它优于偶数适度算术。 所以像davmac的答案中的代码可能会尽可能快。

我知道您没有具体询问CUDA,但大多数最佳实践指南也适用于OpenCL以及AMD GPU编程的大部分内容。

顺便说一句:在几乎所有GPU代码的情况下,我见过大部分时间花在内存访问上,而不是算术上。 一定要描述! http://en.wikipedia.org/wiki/Program_optimization

如果你只想避免实际的if / else,使用? : ? :运营商:

 return value < 0 ? 0 : (value > 0xff ? 0xff : value); 

但是,就效率而言,这应该没有任何不同。

在实践中,你不应该担心这样的微不足道的效率。 让编译器进行优化。

你可以做一个2D查找表:

 unsigned char clamp(short value) { static const unsigned char table[256][256] = { ... } const unsigned char x = value & 0xff; const unsigned char y = (value >> 8) & 0xff; return table[y][x]; } 

当然这看起来很奇怪(这个琐碎计算的64 KB表)。 但是,考虑到你提到你想在GPU上执行此操作,我认为上面的内容可能是纹理查找,我相信它在GPU上非常快。

此外,如果你的GPU使用OpenGL,你当然可以直接使用内置的clip:

 clamp(value, 0, 255); 

这不会进行类型转换(似乎GLSL中没有8位整数类型),但仍然如此。

if使用?:如另一张海报所示,或者使用abs()有趣属性,可以计算两个值的最大值或最小值,则可以不明确地执行此操作。

例如,表达式(a + abs(a))/2为正数返回a ,否则返回0a0最大值)。

这给了

 unsigned char clip(short value) { short a = (value + abs(value)) / 2; return (a + 255 - abs(a - 255)) / 2; } 

为了说服自己这是有效的,这是一个测试程序:

 #include  unsigned char clip(short value) { short a = (value + abs(value)) / 2; return (a + 255 - abs(a - 255)) / 2; } void test(short value) { printf("clip(%d) = %d\n", value, clip(value)); } int main() { test(0); test(10); test(-10); test(255); test(265); return 0; } 

运行时,会打印出来

 clip(0) = 0 clip(10) = 10 clip(-10) = 0 clip(255) = 255 clip(265) = 255 

当然,有人可能认为abs()可能存在测试,但gcc -O3例如可以线性编译:

 clip: movswl %di, %edi movl %edi, %edx sarl $31, %edx movl %edx, %eax xorl %edi, %eax subl %edx, %eax addl %edi, %eax movl %eax, %edx shrl $31, %edx addl %eax, %edx sarl %edx movswl %dx, %edx leal 255(%rdx), %eax subl $255, %edx movl %edx, %ecx sarl $31, %ecx xorl %ecx, %edx subl %ecx, %edx subl %edx, %eax movl %eax, %edx shrl $31, %edx addl %edx, %eax sarl %eax ret 

但请注意,这将比您的原始函数效率低得多,后者编译为:

 clip: xorl %eax, %eax testw %di, %di js .L1 movl $-1, %eax cmpw $255, %di cmovle %edi, %eax .L1: rep ret 

但至少它回答了你的问题:)

怎么样:

 unsigned char clamp (short value) { unsigned char r = (value >> 15); /* uses arithmetic right-shift */ unsigned char s = !!(value & 0x7f00) * 0xff; unsigned char v = (value & 0xff); return (v | s) & ~r; } 

但我严重怀疑它的执行速度比原始版本涉及分支的速度要快。

假设两个字节短,并且代价是代码的可读性:

 clipped_x = (x & 0x8000) ? 0 : ((x >> 8) ? 0xFF : x); 

你应该花时间这个丑陋但仅算术的版本。

 unsigned char clamp(short value){ short pmask = ((value & 0x4000) >> 7) | ((value & 0x2000) >> 6) | ((value & 0x1000) >> 5) | ((value & 0x0800) >> 4) | ((value & 0x0400) >> 3) | ((value & 0x0200) >> 2) | ((value & 0x0100) >> 1); pmask |= (pmask >> 1) | (pmask >> 2) | (pmask >> 3) | (pmask >> 4) | (pmask >> 5) | (pmask >> 6) | (pmask >> 7); value |= pmask; short nmask = (value & 0x8000) >> 8; nmask |= (nmask >> 1) | (nmask >> 2) | (nmask >> 3) | (nmask >> 4) | (nmask >> 5) | (nmask >> 6) | (nmask >> 7); value &= ~nmask; return value; } 

使其高效的一种方法是将此函数声明为内联以避免函数调用开销。 您也可以使用第三个运算符将其转换为宏,但这将删除编译器的返回类型检查。