我是一位相当称职的Java程序员,对C来说很新。我正在尝试优化具有四种操作模式的例程。
我遍历图像中的所有像素,并根据传递的“模式”计算新的像素值。
我的问题是关于两个嵌套for循环中switch语句的开销。我对任何有关基本C语句,数学和逻辑运算的相对效率的文档链接感兴趣。
代码如下:
for (x = 0; x < width; x++) {
for (y = 0; y < height; y++) {
switch (mode) /* select the type of calculation */
{
case 0:
weight = dCentre / maxDistanceEdge;
case 1:
weight = (float)x/width;
break;
case 2:
weight = (float)y/height;
break;
case 3:
weight = dBottomLeft / maxDistanceCorner;
break;
case 4:
weight = dTopRight / maxDistanceCorner;
break;
default:
weight = 1;
break;
}
// Calculate the new pixel value given the weight
...
}
}
如果超过5000 x 5000像素的图像,你会期望看到很多开销吗?我试过做一些测试但是我的结果到处都是,因为系统(移动设备)在后台运行各种各样的东西可能会导致结果偏差。
另一种选择是为每种模式设置一个单独的方法,每种方法都有自己的四个循环。这显然会引入冗余代码,但效率是这里游戏的名称。
提前致谢!
GAV株系
答案 0 :(得分:19)
将语句编译为连续值的跳转表和稀疏值的一堆if-else语句。在任何情况下,如果您关心性能,则不希望在内部循环中使用switch语句进行图像处理。你想改为如下。
另外,请注意我将重量计算移出内循环(并为了实现此目的而交换了案例2的循环)。这种思维方式,将内容移出内部循环,将为您提供C语言所需的性能。
switch (mode) /* select the type of calculation */
{
case 0:
weight = dCentre / maxDistanceEdge;
for (x = 0; x < width; x++) {
for (y = 0; y < height; y++) {
// Calculate the new pixel value given the weight
...
}
}
break;
case 1:
for (x = 0; x < width; x++) {
weight = (float)x/width;
for (y = 0; y < height; y++) {
// Calculate the new pixel value given the weight
...
}
}
break;
case 2:
// note - the loops have been swapped to get the weight calc out of the inner loop
for (y = 0; y < height; y++) {
weight = (float)y/height;
for (x = 0; x < width; x++) {
// Calculate the new pixel value given the weight
...
}
}
break;
case 3:
weight = dBottomLeft / maxDistanceCorner;
for (x = 0; x < width; x++) {
for (y = 0; y < height; y++) {
// Calculate the new pixel value given the weight
...
}
}
break;
case 4:
weight = dTopRight / maxDistanceCorner;
for (x = 0; x < width; x++) {
for (y = 0; y < height; y++) {
// Calculate the new pixel value given the weight
...
}
}
break;
default:
weight = 1;
for (x = 0; x < width; x++) {
for (y = 0; y < height; y++) {
// Calculate the new pixel value given the weight
...
}
}
break;
// etc..
}
答案 1 :(得分:10)
如果效率比代码大小更重要,那么你应该创建冗余例程。 case语句是你在C中可以做的较低开销之一,但它不是零 - 它必须根据模式进行分支,因此需要时间。如果你真的想要最大的性能,那么即使以重复循环为代价,也要将案例排除在外。
答案 2 :(得分:6)
Switch语句尽可能高效。他们被编译成跳转表。事实上,这就是为什么开关受限制的原因:你只能编写一个开关,你可以 根据固定值编译跳转表。
答案 3 :(得分:5)
与您在循环中进行的数学运算相比,交换机的开销可能很小。话虽如此,唯一可以确定的方法是为两种不同的方法创建不同的版本,并为它们计时。
答案 4 :(得分:3)
与if / else的等价物相比,Switch / case非常快:它通常被实现为跳转表。但是它仍有成本。
在优化事物的同时:
1)尝试循环遍历行而不是通过列(切换x和y“表示”循环),由于缓存管理,一个解决方案可能比另一个解决方案快得多。
2)通过(预先计算的)逆的乘法来替换所有除法将给你显着的增益,并且可能是可接受的精度损失。
答案 5 :(得分:2)
为了提高效率,最好将switch
移到循环之外。
我会使用像这样的函数指针:
double fun0(void) { return dCentre/maxDistanceEdge; }
double fun1(void) { return (float)x/width; }
/* and so on ... */
double (*fun)(void);
switch (mode) /* select the type of calculation */
{
case 0: fun = fun0;
break;
case 1: fun = fun1;
break;
case 2: fun = fun2;
break;
case 3: fun = fun3;
break;
case 4: fun = fun3;
break;
default : fun = fun_default;
break;
}
for (x = 0; x < width; x++) {
for (y = 0; y < height; y++) {
weight = fun();
// Calculate the new pixel value given the weight
...
}
}
它增加了函数调用开销,但它不应该太大,因为你没有将params传递给函数。我认为在性能和可读性之间进行良好的权衡。
编辑:如果您使用GCC,要取消函数调用,您可以使用goto
和labels as values:在交换机中找到正确的标签,然后跳转到它每次。我认为它应该可以节省更多的周期。
答案 6 :(得分:1)
交换机不应该产生任何显着的开销,它们会在低端编译成一种指针数组,然后就是有效的情况:
jmp {baseaddress} + switchcasenum
答案 7 :(得分:1)
这可能取决于CPU的分支预测器有多好,以及编译器如何为交换机生成代码。对于这么少的情况,它可能会生成一个决策树,在这种情况下,正常的CPU分支预测应该能够消除大部分开销。如果生成切换表,情况可能会更糟......
尽管如此,找出答案的最好方法是对其进行分析并查看。
答案 8 :(得分:1)
除了Jim的建议,尝试交换循环的顺序。环路交换是否适用于案例1需要测试,但我怀疑它是。您几乎总是希望在内部循环中使用x坐标以提高分页性能,因为这会使您的函数更容易在每次迭代时保持在相同的常规内存区域。具有有限资源的移动设备可能具有足够低的ram,这将强调这种差异。
答案 9 :(得分:1)
很抱歉碰到这个帖子,但在我看来,问题的转换是FAR。
在这种情况下,效率的真正问题在于分歧。在我看来,除法运算的所有分母都是常数(宽度,高度,最大......),这些在整个图像过程中都不会改变。如果我的猜测是正确的,那么这些是简单的变量,可以根据加载的图像进行更改,以便可以在运行时使用任何大小的图像,现在这允许加载任何图像大小,但这也意味着编译器无法优化它们进入更简单的乘法运算,如果它们被声明为“const”,它可以做到。我的建议是预先计算这些常数的反转并乘以。据我所知,乘法运算需要大约10个时钟周期,其中除法大约需要70个。每个像素增加60个周期,而上面提到的5000x5000,估计速度增加了1.5秒。 1 GHz CPU。
答案 10 :(得分:0)
取决于芯片和编译器以及代码的细节,但是......但这通常会被实现为跳转表,这应该非常快。
BTW--理解这种事情是一个很好的理由,可以花几周时间在你职业生涯的某个阶段学习一些集会......答案 11 :(得分:0)
使用开关可能对速度和编程时间都更好。您正在减少冗余代码,并且可能不需要新的堆栈帧。
交换机非常高效,可以用于非常奇怪和令人困惑的black magic。
答案 12 :(得分:0)
但效率是这里游戏的名称。
迭代图像缓冲区以计算新的像素值听起来像一个典型的令人尴尬的并行问题,在这个意义上你可能想要考虑将一些工作推入工作线程,这应该比你的操作更加明显加快微观优化,如开关/案例问题。
此外,您不必每次都执行分支指令,而是可以从函数指针数组中调用函数指针,其中索引用作模式标识符。
这样你最终会收到如下呼叫:
computeWeight[mode](pixel);
对于5000x5000像素,通过调用一系列像素而不是单个像素的函数,也可以减少函数调用开销。
您还可以使用循环展开和参考/指针传递参数,以便进一步优化。
答案 13 :(得分:0)
已经给出了很多好处。我唯一能想到的就是在交换机中移动最频繁的情况,并且频繁地降低频率。
因此,如果案例4的发生频率高于案例1,那么它应该高于它:
switch (mode) {
case 4:
// ..
break;
case 1:
// ..
break;
}
太糟糕了,你没有使用c ++,因为那时switch语句可以用多态替换。
干杯!
答案 14 :(得分:0)
在这个线程中有很多创意建议,无需编写5个单独的函数。
除非您从文件或类型输入中读取“模式”,否则可以在编译时确定计算方法。作为一般规则,您不希望将计算从编译时间移动到运行时。
无论哪种方式代码都更容易阅读,没有人会对你是否打算在第一种情况下放入break语句感到困惑。
此外,如果您在周围代码中遇到错误,则无需查询枚举是否设置为错误的值。
答案 15 :(得分:0)
关于内部循环... 0-> var最好做var-> 0,因为var--触发零标志(6502天)。这种方法还意味着“宽度”被加载到x中并且可以被忽略,“高度”也是如此。同样,内存中的像素通常在左->右,上->底,因此绝对将x作为内循环。
for (y = height; y--;) {
for (x = width; x--;) {
weight = fun();
// Calculate the new pixel value given the weight
...
}
}
也...而且非常重要的是,您的switch语句只有2种情况使用x或y。其余的是常量。
switch (mode) /* select the type of calculation */
{
case 0:
weight = dCentre / maxDistanceEdge;
break;
//case 1:
// weight = (float)x/width;
// break;
//case 2:
// weight = (float)y/height;
// break;
case 3:
weight = dBottomLeft / maxDistanceCorner;
break;
case 4:
weight = dTopRight / maxDistanceCorner;
break;
default:
weight = 1;
break;
}
所以基本上,除非在循环之前计算了模式1或2的权重。
... Y loop code here
if (mode == 2) { weight = (float)y/height; } // calc only once per Y loop
... X loop here
if (mode == 1) { weight = (float)x/width; } // after this all cases have filled weight
calc_pixel_using_weight(weight);
如果数据稀疏,我发现switch语句非常不友好。对于<4个元素,我会选择if-then-else,并确保最常见的情况排在最前面。如果第一个条件涵盖了90%的情况,那么您基本上已经实现了本垒打。同样,如果某些其他条件<1%,则将其放在最后。