我有一个简单的C函数如下:
unsigned char clamp(short value){
if (value < 0) return 0;
if (value > 0xff) return 0xff;
return value;
}
是否有可能在不使用任何if / else分支的情况下重写它而效率高?
编辑:
我基本上希望看看是否可以进行基于位运算的基于算法的实现。目标是在GPU(图形处理单元)上处理图像。这种类型的代码将在每个像素上运行。我想如果可以避免分支,那么GPU上的整体吞吐量会更高。
像(值&lt; 0?0 :((值> 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;
}
这肯定有效,如果很好的话可以减少到一个。
答案 0 :(得分:3)
您写道,您希望避免在GPU上进行分支。确实,在并行环境中分支可能非常昂贵,因为必须评估两个分支或者必须应用同步。但是如果分支足够小,代码将比大多数算术更快。 CUDA C best practices guide描述了原因:
有时候,编译器可能会[...] 优化if if或switch语句 通过使用分支预测来代替。 在这些情况下,永远不会扭曲 发散。 [..]
使用分支预测时没有 执行的指令 取决于控制条件 被跳过了。相反,他们每个人都是 与每线程条件相关联 设置为true的代码或谓词 或基于控制的假 条件,虽然每一个 指令计划安排 执行,只有指令 实际上是一个真正的谓词 执行。说明有误 谓词不写结果,而且 也不评估地址或阅读 操作数。
分支预测很快。血腥快!如果你看一下优化编译器生成的中间PTX代码,你会发现它优于偶数适度算术。所以davmac答案中的代码可能和它一样快。
我知道您没有具体询问CUDA,但大多数最佳实践指南也适用于OpenCL以及AMD GPU编程的大部分内容。
BTW:在几乎所有GPU代码的情况下,我见过大部分时间花在内存访问上,而不是算术上。一定要描述! http://en.wikipedia.org/wiki/Program_optimization答案 1 :(得分:2)
如果您只想避免实际的if / else,请使用? :
运算符:
return value < 0 ? 0 : (value > 0xff ? 0xff : value);
然而,就效率而言,这应该没有任何不同。
在实践中,你不应该担心效率这么微不足道的事情。让编译器进行优化。
答案 2 :(得分:2)
您可以在没有明确if
的情况下使用另一张海报所示的?:
或使用abs()
的有趣属性来计算两个值的最大值或最小值。< / p>
例如,表达式(a + abs(a))/2
返回a
表示正数,0
表示a
表示最多0
和unsigned char clip(short value)
{
short a = (value + abs(value)) / 2;
return (a + 255 - abs(a - 255)) / 2;
}
。
这给出了
#include <stdio.h>
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
但请注意,这将比原始函数效率低得多,后者编译为:
{{1}}
但至少它回答了你的问题:)
答案 3 :(得分:2)
您可以执行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,你当然可以直接使用clamp
内置版:
clamp(value, 0, 255);
这不会进行类型转换(似乎GLSL中没有8位整数类型),但仍然如此。
答案 4 :(得分:1)
怎么样:
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;
}
但是我非常怀疑它的执行速度比你原来涉及分支的版本要快。
答案 5 :(得分:0)
假设两个字节短,并且以代码的可读性为代价:
clipped_x = (x & 0x8000) ? 0 : ((x >> 8) ? 0xFF : x);
答案 6 :(得分:0)
你应该把这个丑陋但仅算术的版本计时。
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;
}
答案 7 :(得分:-1)
提高效率的一种方法是将此函数声明为内联函数,以避免函数调用开销。您也可以使用第三个运算符将其转换为宏,但这将删除编译器的返回类型检查。