优化浮点除法和转换操​​作

ldo*_*dog 3 c c++ algorithm floating-point

我有以下公式

float mean = (r+b+g)/3/255.0f;
Run Code Online (Sandbox Code Playgroud)

我想加快速度.有以下先决条件

0<= mean <= 1  and 0 <= r,g,b <= 255 and r, g, b are unsigned chars
Run Code Online (Sandbox Code Playgroud)

因此,如果我尝试使用>> 8就像除以256的事实,我会使用类似的东西

float mean = (float)(((r+b+g)/3) >> 8);
Run Code Online (Sandbox Code Playgroud)

这将始终返回0.有没有办法跳过昂贵的浮动分区,最终仍然是0到1之间的平均值?

unw*_*ind 16

将您的分区预转换为可乘法常量:

a / 3 / 255
Run Code Online (Sandbox Code Playgroud)

是相同的

a * (1 / (3 * 255))
Run Code Online (Sandbox Code Playgroud)

所以预先计算:

const float AVERAGE_SCALE_FACTOR = 1.f / (3.f * 255.f)
Run Code Online (Sandbox Code Playgroud)

然后就做

float mean = (r + g + b) * AVERAGE_SCALE_FACTOR;
Run Code Online (Sandbox Code Playgroud)

因为乘法通常比分割快很多.

  • gcc有** - freciprocal-math**选项为你做这个优化技巧. (5认同)
  • 如果您的编译器完全正在优化,则乘法应该没问题.移位技巧可能不适用于浮点数.它以位而不是数字运行. (2认同)
  • 将始终计算类似"3/255.0f"的表达式,并由编译器替换为结果常量.它当然是一个好主意,为代码清晰度创建这样的const,但我强烈怀疑它会带来任何性能提升. (2认同)
  • 不,在这种情况下,它不会.C中的除法运算符从左向右关联,因此第一个除法是*整数*除以3,第二个除法是*浮点除法*乘以255.0 - 此序列不会给出与单个除法相同的结果通过(3*255.0),所以编译器不能*自由地做到这一点. (2认同)

Omr*_*dan 8

你明显将平均值与其他东西进行比较,也就是在0和1之间.你怎么把这个东西乘以255呢?


And*_*dge 5

让我们看看真正的编译器实际上用这个代码做了什么?我喜欢mingw gcc 4.3(x86).我用过"gcc test.c -O2 -S -c -Wall"

这个功能:

float calc_mean(unsigned char r, unsigned char g, unsigned char b)
{
    return (r+b+g)/3/255.0f;
}

生成此对象代码(为了清楚起见,删除了函数入口和退出代码.我希望我添加的注释大致正确):

 movzbl 12(%ebp), %edx    ; edx = g
 movzbl 8(%ebp), %eax     ; eax = r
 addl %eax, %edx        ; edx = eax + edx
 movzbl 16(%ebp), %eax    ; eax = b
 addl %eax, %edx        ; edx = eax + edx
 movl $1431655766, %eax ; 
 imull %edx              ; edx *= a const
 flds LC0               ; put a const in the floating point reg
 pushl %edx              ; put edx on the stack
 fidivrl (%esp)            ; float reg /= top of stack

而这个功能:

float calc_mean2(unsigned char r, unsigned char g, unsigned char b)
{
    const float AVERAGE_SCALE_FACTOR = 1.f / (3.f * 255.f);
    return (r+b+g) * AVERAGE_SCALE_FACTOR;
}

生成这个:

 movzbl 12(%ebp), %eax    
 movzbl 8(%ebp), %edx
 addl %edx, %eax
 movzbl 16(%ebp), %edx
 addl %edx, %eax
 flds LC2
 pushl %eax
 fimull (%esp)

如您所见,第二个功能更好.使用-freciprocal-math进行编译会将fidivrl从第一个函数转换为fimull,这应该是一个改进.但第二个功能仍然更好.

但是,如果您认为现代桌面CPU具有类似18级流水线的功能并且每个周期能够执行其中几条指令,您可以看到这些功能的性能将由于数据依赖性而受到停顿的支配.希望你的程序有这个代码片段内联并且有一些循环展开.

考虑到隔离的这种小代码片段并不理想.这有点像用双筒望远镜粘在眼窝上驾驶汽车.缩小男人!