为Python程序员优化编译器思维方式

问题描述

来自大多数的Python背景,我现在正在学习C和x86-64汇编语言。以前我是通过Cython间接使用C的,但是现在我除了汇编语言之外,还在学习C的知识。

我的基本问题是,在优化编译器时应该摆什么样的心态。我是否应该让编译器来完成工作,但是一旦我足够精通汇编,就开始检查并确认汇编输出吗?是负责任的C程序员想要编写高性能代码吗?

触发该问题是因为我想检查gcc 7.5.0将如何优化下面的代码。特别是,我运行objdump来找出如何在相同级别上两次访问同一索引的数组。

  • -O3上有一些我尚未学习的说明,例如movaps XMMWORD PTR [rsp+0x10],xmm0
  • 级别-O2-O1较为清晰,但我还是不太了解
  • -O0级别上,我认为在messages[idx]确实被两次访问的地方,我可以看到代码的相当直接的翻译。

我的问题不是何时应该使用这些级别。我只是问更有经验的程序员,这是您的工作吗,请高度优化地运行代码,并检查程序集的输出以确保一切都按预期进行?对于想要真正了解编译器生成什么机器代码的人来说,这是自然的工作流程吗?

我知道以下示例是实现优化的微不足道的机会,但是您是否刚刚知道肯定会进行某些优化,而您不再考虑它们了?关于可以进行什么样的转换和优化的信息并不多,更不用说编译器没有留下注释或消息供程序员了解优化的内容和原因的事实了,所以我只是想像不到其他任何方式在实践中学习这一切。谢谢。

#include <stddef.h>
#include <stdio.h>

int main(int argc,char ** argv)
{
    size_t len_messages = 9;
    int messages[] = {1,2,3,4,5,6,7,8,9};

    for(size_t idx=0; idx < len_messages; idx++) {
        printf("Accessing here %d and there %d\n",messages[idx],messages[idx]);
    }

    return 0;
}

解决方法

我的基本问题是,在优化编译器时应该摆什么样的心态。我是否应该让编译器来完成工作,但是一旦我精通汇编,就开始检查并确认汇编输出?

大多数不是。

不同的代码段对性能的影响程度不同-在初始化期间仅使用一次的代码段不会对性能产生太大的影响,而频繁执行的循环中间的一段代码可能会产生极大的影响在性能上。通过组装进行优化会浪费开发人员的时间和便携性;通常,对那些不经常执行的代码进行微不足道的性能改进就无法证明这些额外的成本。

出于这个原因,主要策略是使用探查器来确定最重要的(对于性能而言)代码所在的位置;并仅研究这些部件的性能改进。

但是,“调查性能改进”仍然不一定意味着直接进行组装。您在考虑改进算法,改善数据结构和缓存局部性,改善并行性(“更多线程!”)等等。

所有这些之后,您可能会查看编译器生成的程序集,并查看是否可以找到手动改进/优化它的方法。您也可能不会。

您仍然不使用汇编语言的原因是不同的CPU不同。您可以优化一个CPU(无论您的计算机拥有什么),并使该软件在其他CPU上的运行速度明显变慢(无论运行您的软件的最终用户拥有什么)。或者您可以依靠可能不存在的功能(例如AVX512)。当然,这也意味着从性能分析中得到的结果并不像您想象的那样有用(足够好用于粗略的估计,并且永远无法用作适用于所有CPU的准确表示)。

要解决这一问题,您可能需要针对不同的CPU使用多种汇编语言版本-一个用于“带AVX-512的64位Intel”,一个用于“带AVX2的64位Intel”,一个用于“ 64位”英特尔,而没有任何AVX”,则为AMD提供了2个以上的版本,因为您发现AMD上的一些指令花费更长的时间,而AMD上的其他一些指令则更快。然后是针对64位ARM的另一个不同版本的集合,然后是PowerPC,然后...

基本上;进行装配优化的情况很少。对于“沉重负担”的库(例如MPEG解码器,大数库等),它可能具有很大的意义,对于大型程序的一些性能至关重要的部分,则可能是合理的;但是除此之外,您可能还需要花费很多时间。

,

我很少单独看拆卸。通常,我会使用Ghidra对该函数进行反编译,以查看优化程序的运行情况。这样您将获得更大,更好的画面。用更熟悉的语言,您仍然可以在其中看到生成的程序集。