C语言中switch语句的开销

gav*_*gav 12 c overhead switch-statement

我是一个相当称职的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
             ...
            }             

    }
Run Code Online (Sandbox Code Playgroud)

如果这是超过5000 x 5000像素的图像,你会期望看到很多开销吗?我试过做一些测试,但我的结果到处都是,因为系统(移动设备)有各种各样的东西在后台运行,可能会扭曲结果.

另一种选择是为每种模式设置一个单独的方法,每种方法都有自己的四个循环.这显然会引入冗余代码,但效率是这里游戏的名称.

提前致谢!

GAV

Jim*_*uck 19

Switch语句编译为连续值的跳转表和稀疏值的一堆if-else语句.在任何情况下,如果您关心性能,则不希望在内部循环中使用switch语句进行图像处理.你想改为如下.

另外,请注意我将权重计算移出内部循环(并为了实现此目的而交换了案例2的循环).这种思维方式,从内循环中移出东西,将为您提供您想要的性能.

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..
}
Run Code Online (Sandbox Code Playgroud)

  • 如果你使用GCC编译,-funswitch-loops选项就完全可以了. (14认同)
  • 优秀的答案,除了你埋葬的关键点.你*从不*想在循环内做一些不变的东西(例如,开关的选择; 6次中的重量4等) (12认同)
  • 无法保证交换机的成本甚至是他的瓶颈.它可能是一个内存读取,比较和跳转 - 所有这些都将在缓存中,并且很可能由CPU准确预测,因此我们正在讨论每个循环的少数时钟周期的开销.如果"计算给定权重的新像素值"必须完全输出到主存储器,那么它可能已经比开关开销大一个数量级.我同意Neil的上述答案 - 他应该尝试两种方法并测量哪种方法更具性能. (3认同)
  • @nils:使用-O3还包括-funswitch-loops等等.我更倾向于使用它. (2认同)
  • 只是想注意到原始答案中的“Jumptables”不正确。如果标签没有密集的值范围,包括 MSVC 在内的许多编译器都会决定数据集是否要转换为跳转表或二进制排序级联。 (2认同)

Mic*_*hne 10

如果效率比代码大小更重要,那么您应该创建冗余例程.case语句是你在C中可以做的较低开销之一,但它不是零 - 它必须根据模式进行分支,因此需要时间.如果你真的想要最大性能,那么即使以重复循环为代价,也要将情况从循环中解脱出来.

  • 很容易,25,000,000会慢一些.但我们是否正在谈论每个循环慢5个时钟周期(完全随机挑选,谁知道它有多贵.)这是125,000,000个时钟周期,即1 GHz CPU上的.125秒.如果他的图像处理目前需要一秒钟,这可能是显而易见的.如果需要一分钟怎么办?甚至只需5秒钟?如果是这样,这不一定是他应该花费精力的地方,并且可能不值得追求整体可读性. (4认同)

Cha*_*tin 6

Switch语句尽可能高效.他们被编译成跳转表.实际上,这就是为什么切换受限制的原因:您只能编写一个开关,您可以根据固定值编译跳转表.

  • litb,首先,编译器无需将ANYTHING编译为跳转表; 尽管如此,对开关的约束仍然存在,所以*可能*编译成跳转表.K&R在他们的C书中谈到了这一点.其次,我没有说"零成本",只是"尽可能高效". (3认同)
  • 实际上,交换机不需要它可以编译成跳转表以便您编写它.如果您选择了奇怪的值,编译器可能会完全决定使用普通分支而不是跳转表.看到一个开关并认为它是零成本是误导.我猜测量会话或gcc -S会话被调用:) (2认同)

小智 5

与您在循环中执行的数学运算相比,交换机的开销可能很小.话虽如此,唯一可以肯定的方法是为两种不同的方法创建不同的版本,并为它们计时.