C优化问题

时间:2010-07-27 14:38:49

标签: c optimization

我想知道什么是我写代码的最快方法。我有一个循环,在一些int上执行添加。循环将执行很多次,所以我想进行比较以检查是否有任何操作数为零,所以不应该认为它们被添加,如下所示:

if (work1 == 0)
{
    if (work2 == 0)
        tempAnswer = toCarry;
    else
        tempAnswer = work2 + toCarry; 
}
else if (work2 == 0)
    tempAnswer = work1 + toCarry;
else
    tempAnswer = work1 + work2 + toCarry;

我认为顶部的嵌套IF已经是一个优化,因为它比用&&'s编写一系列比较要快,因为我会不止一次地检查(work1 == 0)。 / p>

可悲的是,我无法说出work1和work2的频率为零,所以假设它可能是IF语句的每个可能结果的均衡分布。

那么,鉴于此,上述代码是否比仅仅编写tempAnswer = work1 + work2 + toCarry更快或者所有比较是否会导致很多拖累?

由于

9 个答案:

答案 0 :(得分:26)

这是无稽之谈。

  • 比较两个整数只需添加两个整数即可。
  • 做一个分支需要花费很多,比添加更长的时间(很多,不可否认的是老版本(见注释),CPU)
  • 在更现代的架构中,瓶颈是从内存中访问值,因此该方案仍无法满足需要。

    另外,从逻辑上考虑一下 - 为什么单独将零视为特殊情况下的一个值?为什么不检查一个,并使用tempAnswer++?当你考虑所有可能性时,你会发现这是一个毫无意义的练习。

答案 1 :(得分:17)

答案一如既往地描述您的代码。写两种方式,计时,看哪种方式更快。<​​/ p>

那就是说,我的钱会直接加成比一堆比较更快。每次比较都意味着一个潜在的分支,分支可能会对处理器中的流水线造成严重破坏。

答案 2 :(得分:2)

分支很可能比添加速度慢,所以这可能会适得其反。无论如何,阅读起来要困难得多。在你有充分的证据表明你需要它之​​前,你真的不应该尝试优化到这个水平。对代码的负面影响通常不值得。

答案 3 :(得分:2)

不,它并不快。分支错误预测比添加更加痛苦。

答案 4 :(得分:1)

在执行添加之前有条件检查的唯一情况是节省时间,如果可以避免“昂贵”的写操作。例如,像:

  if (var1 != 0)
    someobject.property1 += var1;
如果写入propert1的速度很慢,

可以节省时间,特别是如果该属性尚未优化写出已存在的值。在极少数情况下,人们可能会受益于:

  if (var1 != 0)
    volatilevar2 += var1;

如果多个处理器都经常重新读取volatilevar2,则var1通常为零。令人怀疑的是,那种有益的比较会“自然地”发生,尽管可能会有人作风。一个稍微有点人为的版本:

  if (var1 != 0)
    Threading.Interlocked.Add(volatilevar2, var1);

在一些自然发生的场景中可能是有益的。

当然,如果添加的目的地是不与其他处理器共享的本地临时变量,则节省时间的可能性基本上为零。

答案 5 :(得分:1)

除了比较通常和添加一样快(因此平均来说你有更多的操作),以及在许多架构分支的情况下,如果CPU无法猜测哪个分支是昂贵的这一事实它的方式,也有代码的位置。

现代处理器尽可能地保留在处理器中的缓存中,或者可能在主板上。点击主存储器相对较慢,并且在存储器页面中读取相对非常慢。从快速和小到慢和大的层次结构。性能的一个重要方面是尝试保持在该层次结构的“快速和小”方面。

您的代码将处于循环中。如果该循环适合一个或两个缓存行,那么你的状态很好,因为CPU可以用极短的时间执行循环来获取指令,而不会从缓存中踢出其他内存。

因此,在进行微优化时,您应该尝试让内部循环包含小代码,这通常意味着简单和简短。在你的情况下,当你没有比较和两个加法时,你有三个比较和几个加法。此代码比更简单的tempAnswer = work1 + work2 + toCarry; 更容易导致缓存未命中。

答案 6 :(得分:1)

最快是一个相对术语。这个平台是什么?它有缓存吗?如果它具有高速缓存,则可能在可以在单个时钟周期内执行添加的平台上,因此不需要优化添加。下一个问题是比较是减去减法和添加通过相同的alu并采取相同的时间添加,所以对于大多数平台旧的和新的交易比较(减法)添加不会保存你的任何东西,你最终看着分支成本,管道冲洗等。即使使用ARM平台,您仍然可以烧掉一些或几个。对于这样的优化,您必须做的第一件事是查看编译器输出,编译器选择哪些指令? (假设这是编译器,每个编译此代码的编译器都使用相同的编译器选项等)。例如,在加/减占用时钟或大量时钟的芯片上,xor或和/或操作可能需要更少的时钟。您可以使用按位操作在某些处理器上进行零比较,从而节省时钟。编译器是否已将其解决并使用更快的操作?

作为对您的问题的一般目的回答,基于那里的处理器以及您正在使用或未使用的处理器的可能性。单行:

tempAnswer = work1 + work2 + toCarry;

是最优化的代码。对于大多数处理器或我猜你可能正在使用的处理器,编译器会将其转换为两个或三个指令。

您更大的担忧不是添加或比较或分支或分支预测,您最担心的是这些变量保存在寄存器中。如果他们都必须来回堆栈/ ram,这将减慢你的循环,即使使用缓存。循环中的其他代码将确定这一点,并且您可以在代码中执行一些操作以最大限度地减少寄存器使用,从而允许这些代码基于寄存器。再次,反汇编代码以查看编译器正在做什么。

答案 7 :(得分:1)

我同意其他评论的一般主张 - “优化”实际上是一种“悲观化”,使得代码更难以编写,阅读和维护。

此外,'优化'代码比简单代码更大。

示例函数

$ cat yy.c
int optimizable(int work1, int work2, int toCarry)
{
    int tempAnswer;
    if (work1 == 0)
    {
        if (work2 == 0)
            tempAnswer = toCarry;
        else
            tempAnswer = work2 + toCarry; 
    }
    else if (work2 == 0)
        tempAnswer = work1 + toCarry;
    else
        tempAnswer = work1 + work2 + toCarry;

    return tempAnswer;
}
$ cat xx.c
int optimizable(int work1, int work2, int toCarry)
{
    int tempAnswer;
    tempAnswer = work1 + work2 + toCarry;
    return tempAnswer;
}
$

编译器

$ gcc --version
gcc (GCC) 4.1.2 20080704 (Red Hat 4.1.2-44)
Copyright (C) 2006 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

具有不同优化级别的对象文件大小

$ gcc -c yy.c xx.c
$ size xx.o yy.o
   text    data     bss     dec     hex filename
     86       0       0      86      56 xx.o
    134       0       0     134      86 yy.o
$ gcc -O -c yy.c xx.c
$ size xx.o yy.o
   text    data     bss     dec     hex filename
     54       0       0      54      36 xx.o
     71       0       0      71      47 yy.o
$ gcc -O1 -c yy.c xx.c
$ size xx.o yy.o
   text    data     bss     dec     hex filename
     54       0       0      54      36 xx.o
     71       0       0      71      47 yy.o
$ gcc -O2 -c yy.c xx.c
$ size xx.o yy.o
   text    data     bss     dec     hex filename
     54       0       0      54      36 xx.o
     70       0       0      70      46 yy.o
$ gcc -O3 -c yy.c xx.c
$ size xx.o yy.o
   text    data     bss     dec     hex filename
     54       0       0      54      36 xx.o
     70       0       0      70      46 yy.o
$ gcc -O4 -c yy.c xx.c
$ size xx.o yy.o
   text    data     bss     dec     hex filename
     54       0       0      54      36 xx.o
     70       0       0      70      46 yy.o
$

代码是针对AMD x86-64上的64位RedHat Linux编译的。

这两个功能携带相同的基础设施行李(3个参数,1个本地,1个返回)。最好的情况是,优化函数比未优化函数长16个字节。将额外的代码读入内存会降低性能,执行该代码所需的额外时间是另一个。

答案 8 :(得分:0)

这是经典的警告:“避免早期优化”。

这个功能真的很重要吗?它被调用了很多次,你必须对它进行优化吗?

现在,让我们看看@ Jonathan的回答并考虑“技术债务”,即可维护性。在您的特定环境中思考:在一两年内,有人会查看您的代码并发现它更难以理解,或者更糟糕的是,他/她会误解它!

最重要的是,比较xx.c和yy.c:哪一段代码有更高的错误机会?

祝你好运!