如何避免在此操作中使用C分支

时间:2013-07-19 14:43:05

标签: c performance optimization

有没有办法删除以下if语句来检查该值是否低于0?

int a = 100;
int b = 200;
int c = a - b;

if (c < 0)
{
    c += 3600;
}

c的值应介于0到3600之间。ab都已签名。 a的值也应介于0到3600之间。(是的,它是0.1度的计数值)。该值通过中断复位到3600,但如果该中断来得太晚,它会下溢,这不是问题,但软件应该仍然能够处理它。它是做什么的。

我们在相当一些我们计算位置的地方进行if (c < 0)检查。 (计算新职位等)

我习惯用pythons模运算符来使用除数的符号,我们的编译器(C89)正在使用除数签名。

有没有办法以不同方式进行此计算? 示例结果:

 a  -  b  = c
100 - 200 = 3500  
200 - 100 = 100

5 个答案:

答案 0 :(得分:12)

好问题!这个怎么样?

c += 3600 * (c < 0);

这是我们保留分支预测器插槽的一种方法。

答案 1 :(得分:7)

这个怎么样(假设32位整数):

c += 3600 & (c >> 31);

c >> 31将所有位设置为原始MSB,负数为1,其他为2 - 补码为0。

负数移位权是根据C标准文档正式实现定义的,但它几乎总是通过MSB复制实现(通用处理器可以在单个指令中完成)。

这肯定会导致没有分支,不像(c < 0),在某些情况下可能会使用分支实现。

答案 2 :(得分:5)

你为什么担心这个分支? [在对问题的评论中解释的原因。]

替代方案如下:

((a - b) + 3600) % 3600

这假设ab已经在0..3600范围内;如果他们不受控制,更通用的解决方案是Drew McGowen suggests

((a - b) % 3600 + 3600) % 3600

分支错过必须非常昂贵才能进行那么多计算。

答案 3 :(得分:4)

@skjaidev展示了如何在没有分支的情况下做到这一点。以下是当int s为二进制补码时如何自动避免乘法:

#if ((3600 & -0) == 0) && ((3600 & -1) == 3600)
c += 3600 & -(c < 0);
#else
c += 3600 * (c < 0);
#endif

答案 4 :(得分:0)

你想要做的是模块化算术。你的2的补码机器已经用整数数学做了这个。因此,通过将您的值映射到2的补码算法,您可以免费获得模运算。

技巧是将你的角度表示为介于0和1-epsilon之间的360度的一小部分。当然,那么你的恒定角度必须具有相似的表现,但这不应该很难;它只是一些数学,我们可以隐藏在转换函数(呃,宏)。

这个想法中的值是,如果你添加或减去角度,你将得到一个你想要的分数部分的值,以及你要丢弃的整数部分。如果我们将分数表示为32位固定点数,其二进制点为2 ^ 32(例如,通常被认为是符号位的左侧),则该分数的任何溢出都会从该顶部溢出。 32位免费价值。因此,您执行所有整数数学运算,并且“溢出”删除是免费的。

所以我会重写你的代码(保留度数10的想法):

  typedef unsigned int32 angle; // angle*3600/(2^32) represents degrees
  #define angle_scale_factor 1193046.47111111 // = 2^32/3600
  #define make_angle(degrees)  (unsigned int32)((degrees%3600)*angle_scale_factor ) 
  #define make_degrees(angle) (angle/(angle_scale_factor*10)) // produces float number

  ...

  angle a = make_angle(100);  // compiler presumably does compile-time math to compute 119304647 
  angle b = make_angle(200);  // = 238609294
  angle c = a - b; // compiler should generate integer subtract, which computes 4175662649

  #if 0 // no need for this at all; other solutions execute real code to do something here
  if (c < 0)  // this can't happen
     { c += 3600; } // this is the wrong representation for our variant
  #endif


  // speed doesn't matter here, we're doing output:
  printf("final angle %f4.2 = \n", make_degrees(c)); // should print 350.00

我没有编译并运行此代码。

使这个学位时间100或1倍的变化非常容易;修改angle_scale_factor。如果你有一台16位机器,切换到16位也同样容易;如果你有32位,并且你仍然只想进行16位数学运算,则需要将要打印的值屏蔽为16位。

这个解决方案有另外一个很好的属性:你已经记录了哪些变量是角度(并且有很好的表示)。 OP的原始代码只是称它们为int,但这不是它们所代表的;未来的维护者会对原始代码感到惊讶,特别是如果他发现从变量中分离出减法。