如何编写高效的程序?

编写高效的程序需要做到以下几点:
第一,我们必须选择一组适当的算法和数据结构。
第二,我们必须编写出编译器能够有效优化以转换成高效可执行代码的源代码。对于第二点,理解优化编译器的能力和局限性是很重要的。编写长须方式中看上去只是一点小小的变动,都会引起编译器优化方式很大的变化。有些编程语言比其他语言容易优化。C语言的有些特性,例如执行指针运算和强制类型转换的能力,使得编译器很难对它进行优化。程序员经常能够以一种使编译器更容易产生高效代码的方式来编写他们的程序。
第三,针对处理运算量特别大的计算将一个任务分成多个部分,这些部分可以在多核和多处理器的某种组合上并行地计算。

从过程调用开销上,优化代码有几方面(OP代表某个运算,加、减、乘、除,INDENT为宏变量):
1.消除低效率的循环
如何编写高效的程序? - 中山爷爷 - 凝聚 的博客
上述代码中,每次循环都需要对结构v取长度,但是v的长度是固定的,可以使用一下代码取代
  如何编写高效的程序? - 中山爷爷 - 凝聚 的博客

 
2.减少过程调用
在函数中,尽量减少过程的调用,这与模块化的思想违背。
3.消除不必要的内存引用
在机器语言中,读取寄存器的值比读取内存中的数据要快很多,在代码中,尽可能的减少内存的引用,如下图所示,使用即时数acc取代*dest的迭代引用
如何编写高效的程序? - 中山爷爷 - 凝聚 的博客

 
以上的不少原则与模块化的思想相违背,这是一个矛盾的存在!

除了上述的措施外,还可以从影响现代处理器性能的因素上进行优化,具体的步骤如下:
1.循环展开
循环展开是一种程序变换,通过增加每次迭代计算的元素的数量,减少循环的迭代次数。
如何编写高效的程序? - 中山爷爷 - 凝聚 的博客
上图是一个典型的循环展开的例子,其中每次迭代计算前置和的两个元素,因而将需要的迭代次数减半。循环展开能够从两个方面改进程序的性能。首先,它减少了不直接有助于程序结果的操作的数量,例如循环索引计算和条件分支。第二,它提供了一些方法,可以进一步变化代码,减少整个计算中关键路径上的操作数量。
把这个思想归纳为对一个循环按任意因子k进行展开,由此产生k*1循环展开;因此,需要确保几个条件:
1.第一次循环不会超出数组界限;
2.循环索引 i 自増时,没有超出数组界限;
为此,上限设为n-k+1,在循环内对元素i到i+k-1应用合并运算.
在机器代码的实现上,循环展开后,每次循环中对数据的关键路径中迭代次数减去1/k,因此循环展开能够带来更好的性能,但是也并不是k越大越好!!因为会导致寄存器溢出。
2.提高并行性
程序的性能是受运算单元的延迟限制的(造成延迟的因素,在某些文章中有提及,另外不同运算单元也分为是否能够进行各类的运算,例如,运算单元A只能运行加、减法,运算单元A只能执行加、减、乘,运算单元C只能执行浮点运算除法等等)。为了解决延迟限制,我们需要提高并行性,具体的方法如下:
1.使用累计变量储存
对于一个可结合和可交换的合并运算来说,比如说整数加法或乘法,我们可以通过将一组合并运算分割成两个或更多的部分,并在最后合并结果来提高性能。
如何编写高效的程序? - 中山爷爷 - 凝聚 的博客

 图中是计算数组V中所有元素的乘积,我们通过把单个累积变量通过循环展开,变成多个累积变量的再乘积。上述的变换不仅仅带有循环展开的好处,也增加了多个累积变量,不仅仅减少了循环的迭代次数也提高了运算的并行性。
(但是,上述的多个累积变量的方法对于加法运算提供的效率不明显,但是对于乘法是几乎提高了一倍的效率,因为,执行整数加法的运算单元是完全流水线化的,不存在延迟)
2.重新结合变换
上述我们提及了循环展开的优化方法,但是循环展开本身是没有改变数组元素和或者乘积的操作。但是,可以对代码做小小的合并执行的操作,就能极大地提高程序的性能。
如何编写高效的程序? - 中山爷爷 - 凝聚 的博客

 上述的代码针对 如何编写高效的程序? - 中山爷爷 - 凝聚 的博客这句代码,有极大的性能提升,因为在同一句指令中,从内存中取出两次变量,而不是分开两句指令取出变量。因为分开两个连续的取内存值的指令会造成延迟。使运算性能下降。
上面提及的两种方式都能把原来的计算提高一倍的效率!

限制性能优化的因素
除了上面提及的寄存器溢出(当添加临时变量时,寄存器数量不够时,会在内存中开辟空间,从内存中取值的时间是相对比较慢的)的问题外,分支预测造成预测错误处罚也会大大地降低运算的性能(分支预测在我的某篇文章中有提到)。这就要求我们书写更合适的代码。如下面两图:
如何编写高效的程序? - 中山爷爷 - 凝聚 的博客
1)
 
如何编写高效的程序? - 中山爷爷 - 凝聚 的博客
2).

 为了实现流水线,现代处理器会对条件分支进行分支预测,处理对大部分的条件分支都拥有很高的预测正确的方法,但是对于数组的逻辑运算比较,其预测的精确度就比较低。

注意,评价程序的执行效率不单单是从执行时间上看的!

猜你喜欢

转载自blog.csdn.net/amesteur/article/details/80272048