我想了解更多有关GCC如何优化C程序的信息。我对优化和未优化的随机函数都做了处理,我想看看其中的一些区别。在我脑海中,经过优化的程序集的跳跃较少,并且似乎大部分使用寄存器,而未经优化的程序集则更多地使用内存。这两个还有什么不同之处要注意? />
经过优化

uint countPairsUpTo(int index, int* intArray, int first, int second)
{
  uint i;
  uint sum = 0;

  for (i = 0; i < index; i++)
    if ((first == intArray[i]) && (second == intArray[i+2]))
      sum++;

  return sum ;
}


评论

根据您的目标,“ Optimized”可能意味着不同的东西-优化速度,优化尺寸,整体优化... GCC中有很多优化选项。您是否已阅读有关这些选项的文档?

我用O1优化的GCC编译了优化的程序。有帮助吗?

就像每个人提到的那样,这可能意味着很多不同的东西,哪些要优化或“优化”将取决于编译器如何选择解释程序的逻辑。这是有关优化的一个特殊功能的博客文章,称为死代码消除或代码运动(bangreverse.me/blog/?p=27)

#1 楼

好吧,由GCC执行了许多优化技术。这样的优化从消除无效代码到展开循环,函数内联等等。在解释优化技术之前,我将从优化之前编译器的工作开始。

优化通常在所提供代码的IR(中间表示)上执行。大多数编译器将输入代码(CC++Fortran,...)转换为IR。此转换由*front-end*执行,它将IR馈送到*middle-end**back-end*将应用优化遍并将其馈送到GCC,然后GCC将生成机器代码。在此链接中简要介绍。 GIMPLE的作用还在于将其对给定代码的SSA表示形式转换为GCC形式(静态单一分配),该形式在1989年的出版物中进行了介绍。

如果您点击此链接,将会发现GCC实现的所有SSA优化步骤,并带有简要说明。而且我认为您还应该检查RTL通过。在这里,您将找到两个目录,其中包含有关GCC确切功能的详细说明。这些是来自CFG的人员,因此是您所能找到的最可靠的参考。例如,可以在某些可能导致创建孤岛的优化之后执行消除死代码的方法,该方法包括从程序的CFG(控制流图)中消除孤岛。假设您有一个类似下面的代码:

   int x = 1;

   //Some code that doesn't alter the value of x

   if (x == 2)
    { BLOCK1; }
   else
      { BLOCK2; }
现在,假设编译器按以下顺序应用优化: >消除死代码
分支预测

当然,第一遍将在GCC中找不到孤岛。
另一方面,第二遍(分支预测)将发现x的值不变,并且x的值不同从2一直到声明和初始化,再到在if条件下使用。这意味着必须删除整个if语句并用BLOCK2代替。因此,在进行分支预测之后,必须进行无效代码消除操作。

另一个优化是循环展开。它包括复制循环主体并增加跨度,以填充CPU管道并具有更好的缓存位置。例如,此简化循环可以展开4次,以获得一些循环并改善缓存访问:

sizeof(float)= 4bytes)展开4次意味着每次迭代访问16bytes。如果此代码在Intel SandyBridge上运行,它将运行良好,但效果不佳(如果未激活预取功能)。为什么呢因为SandyBridge微型体系结构上的缓存行大小为64Bytes,并且循环每次迭代访问1/4缓存行。展开16次肯定会带来更好的性能。

这种优化技术的难点在于为正确的循环找到正确的展开系数。选择太大或太小的值可能会导致性能下降。而且,底层体系结构也起着非常重要的作用(奔腾和Haswell上的缓存大小并不相似,重排序缓冲区和流水线也是如此)。一些编译器所做的是在具有不同展开因子的循环上执行静态或动态分析,然后选择具有最佳配置文件的循环。

这些优化通常在IR上执行,但也可以在汇编代码上执行。还有其他与汇编紧密相关的优化,例如指令重新排序。此优化包括更改指令顺序,以便获得更好的指令流水线或并行支持。有时,如果指令之间存在依赖关系,并且如果重新排序会导致性能大幅提高,则该依赖关系将被破坏,并对指令进行重新排序。 >
   //Ununrolled version
   for (int i = 0; i < N; i++)
       r += t[i];

   //Unrolled version handling any value of N 
   for (int i = 0; i < (N & ~3); i += 4)
     {
        r += t[i]; 
        r += t[i + 1]; 
        r += t[i + 2]; 
        r += t[i + 3]; 
     }

    //Handling the rest of the array elements 
    for (int i = (N & ~3); i < N; i++)
        r += t[i];


很明显,有序和重新排序的代码执行相同的操作,但是重新排序的版本比有序的代码消耗更少的周期,因为add,sub和mul指令,属于同一类,不会在管道中互相阻塞。您还可以注意到,存储操作位于代码的末端。这种模式通常比混合指令的模式具有更好的性能,尤其是当此类代码位于循环或经常执行的基本块中时。

有很多有趣的优化:自动执行向量化,函数内联,内存对齐等,但是很遗憾,此页面的大小不足以让我涵盖所有内容。您可以查看q4312079q的源代码和手册以获取更多信息。
我在上面和下面指出的参考文献很有帮助。如果您能够消化他们提供的大多数东西,我相信您会找到您想要的东西。 org / optimize /


http://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-optimization- manual.html


http://ukcatalogue.oup.com/product/9780198066644.do



#2 楼

根据gcc手册,您在注释中提到的-O1意味着打开以下标志:

-fauto-inc-dec -fcprop-registers -fdce -fdefer-pop 
-fdelayed-branch -fdse -fguess-branch-probability 
-fif-conversion2 -fif-conversion -finline-small-functions 
-fipa-pure-const -fipa-reference -fmerge-constants 
-fsplit-wide-types -ftree-builtin-call-dce -ftree-ccp 
-ftree-ch -ftree-copyrename -ftree-dce -ftree-dominator-opts 
-ftree-dse -ftree-fre -ftree-sra -ftree-ter -funit-at-a-time


您可以在gcc手册页上了解有关这些标志的更多信息。 >我还建议(如果您还没有这样做的话)阅读Aho的Dragon书。