夹紧unsigned char

时间:2011-05-04 12:27:53

标签: c

我有一个简单的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;
}

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

8 个答案:

答案 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表示最多0unsigned 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)

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