为 Python 程序员优化编译器思维
Optimising compilers mindset for a Python programmer
主要来自 Python 背景,我现在正在学习 C 和 x86-64 汇编。我以前通过 Cython 间接使用 C,但现在除了汇编之外,我正在学习 C。
我的基本问题是,在优化编译器时我应该给自己什么样的心态。我是否应该让编译器完成它的工作,但是,一旦我足够精通汇编,就开始检查并确认汇编输出?这就是想要编写高性能代码的负责任的 C 程序员所做的吗?
问题被触发是因为我想检查 gcc 7.5.0
将优化下面的代码。特别是,我 运行 objdump
了解如何在不同级别优化在同一索引处访问数组两次。
- 在
-O3
上有些说明我还没有学会,例如movaps XMMWORD PTR [rsp+0x10],xmm0
- 级别
-O2
和-O1
比较清楚,但我还是没有完全理解
- 在级别
-O0
我相信我可以看到一个相当简单的代码 t运行 我认为 messages[idx]
确实被访问了两次的代码
我的问题不是什么时候应该使用这些级别。我只是问更有经验的程序员,如果这就是你所做的,运行 高度优化的代码并检查汇编输出以确保一切都符合预期?对于想要真正了解编译器生成的机器代码的人来说,这是自然的工作流程吗?
我知道下面的例子是一种微不足道的优化机会,但你是否刚刚了解到某些优化肯定会发生而你不再考虑它们?关于可以发生什么样的 t运行sformations 和优化的信息并不多,更不用说编译器没有留下注释或消息让程序员了解优化的内容和原因,所以我不能想象一下除了在实践中简单地学习这一切之外的任何其他方式。谢谢。
#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;
}
My basic question is in what sort of a mindset should I put myself when it comes to optimising compilers. Should I just let a compiler do its job but, once I am sufficiently proficient in assembly, start to check and confirm the assembly output?
大部分没有
不同的代码片段对性能的影响程度不同 - 一段在初始化过程中只使用一次的代码不会对性能产生太大影响,而一段在循环中间频繁执行的代码可能会有对性能的极端影响。通过组装成本优化开发人员时间和可移植性;通常,这些额外成本不能通过不经常执行的代码的微不足道的性能改进来证明。
出于这个原因,主要策略是使用分析器来确定最重要的(性能)代码段在哪里;并仅研究这些部分的性能改进。
然而,“研究性能改进”并不一定意味着直接进行组装。你考虑改进算法,改进数据结构和缓存局部性,改进并行性(“更多线程!”)等
完成所有这些之后,您可能会查看编译器生成的程序集,看看是否可以找到手动 improve/optimize 的方法。你也可能不会。
您仍然可能不会使用汇编语言的原因是不同的 CPU 是不同的。您可以优化一个 CPU(无论您的计算机有什么)并使软件在其他 CPU 上显着变慢(无论 运行 您的软件的最终用户有什么);或者您可以依赖可能不存在的功能(例如 AVX512)。当然,这也意味着您从分析中获得的结果并不像您想象的那么有用(对于粗略估计来说足够好,永远不能用作适用于所有 CPU 的准确表示)。
为了解决这个问题,您可能需要针对不同 CPU 的多个不同版本的汇编语言 - 一个用于“64 位 Intel with AVX-512”,一个用于“64 位 Intel with AVX2” ,一个用于“没有任何 AVX 的 64 位英特尔”,另外 2 个版本用于 AMD,因为您发现一些指令在 AMD 上花费的时间更长,而其他一些指令在 AMD 上更快;然后是 64 位 ARM 不同版本的另一个集合,然后是 PowerPC,然后是 ...
基本上;很少在汇编中进行优化。对于“重磅”库(例如 MPEG 解码器、大数字库……),它可能很有意义,并且对于大型程序的一些性能关键部分,它可能是合理的;但除此之外,您的时间可能还有更重要的事情要做。
很少单独看反汇编。大多数情况下,我使用 Ghidra 反编译函数以查看优化器发生了什么。然后你会得到一个更大更好的画面。在更熟悉的语言中,您仍然可以在其中看到生成的程序集。
主要来自 Python 背景,我现在正在学习 C 和 x86-64 汇编。我以前通过 Cython 间接使用 C,但现在除了汇编之外,我正在学习 C。
我的基本问题是,在优化编译器时我应该给自己什么样的心态。我是否应该让编译器完成它的工作,但是,一旦我足够精通汇编,就开始检查并确认汇编输出?这就是想要编写高性能代码的负责任的 C 程序员所做的吗?
问题被触发是因为我想检查 gcc 7.5.0
将优化下面的代码。特别是,我 运行 objdump
了解如何在不同级别优化在同一索引处访问数组两次。
- 在
-O3
上有些说明我还没有学会,例如movaps XMMWORD PTR [rsp+0x10],xmm0
- 级别
-O2
和-O1
比较清楚,但我还是没有完全理解 - 在级别
-O0
我相信我可以看到一个相当简单的代码 t运行 我认为messages[idx]
确实被访问了两次的代码
我的问题不是什么时候应该使用这些级别。我只是问更有经验的程序员,如果这就是你所做的,运行 高度优化的代码并检查汇编输出以确保一切都符合预期?对于想要真正了解编译器生成的机器代码的人来说,这是自然的工作流程吗?
我知道下面的例子是一种微不足道的优化机会,但你是否刚刚了解到某些优化肯定会发生而你不再考虑它们?关于可以发生什么样的 t运行sformations 和优化的信息并不多,更不用说编译器没有留下注释或消息让程序员了解优化的内容和原因,所以我不能想象一下除了在实践中简单地学习这一切之外的任何其他方式。谢谢。
#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;
}
My basic question is in what sort of a mindset should I put myself when it comes to optimising compilers. Should I just let a compiler do its job but, once I am sufficiently proficient in assembly, start to check and confirm the assembly output?
大部分没有
不同的代码片段对性能的影响程度不同 - 一段在初始化过程中只使用一次的代码不会对性能产生太大影响,而一段在循环中间频繁执行的代码可能会有对性能的极端影响。通过组装成本优化开发人员时间和可移植性;通常,这些额外成本不能通过不经常执行的代码的微不足道的性能改进来证明。
出于这个原因,主要策略是使用分析器来确定最重要的(性能)代码段在哪里;并仅研究这些部分的性能改进。
然而,“研究性能改进”并不一定意味着直接进行组装。你考虑改进算法,改进数据结构和缓存局部性,改进并行性(“更多线程!”)等
完成所有这些之后,您可能会查看编译器生成的程序集,看看是否可以找到手动 improve/optimize 的方法。你也可能不会。
您仍然可能不会使用汇编语言的原因是不同的 CPU 是不同的。您可以优化一个 CPU(无论您的计算机有什么)并使软件在其他 CPU 上显着变慢(无论 运行 您的软件的最终用户有什么);或者您可以依赖可能不存在的功能(例如 AVX512)。当然,这也意味着您从分析中获得的结果并不像您想象的那么有用(对于粗略估计来说足够好,永远不能用作适用于所有 CPU 的准确表示)。
为了解决这个问题,您可能需要针对不同 CPU 的多个不同版本的汇编语言 - 一个用于“64 位 Intel with AVX-512”,一个用于“64 位 Intel with AVX2” ,一个用于“没有任何 AVX 的 64 位英特尔”,另外 2 个版本用于 AMD,因为您发现一些指令在 AMD 上花费的时间更长,而其他一些指令在 AMD 上更快;然后是 64 位 ARM 不同版本的另一个集合,然后是 PowerPC,然后是 ...
基本上;很少在汇编中进行优化。对于“重磅”库(例如 MPEG 解码器、大数字库……),它可能很有意义,并且对于大型程序的一些性能关键部分,它可能是合理的;但除此之外,您的时间可能还有更重要的事情要做。
很少单独看反汇编。大多数情况下,我使用 Ghidra 反编译函数以查看优化器发生了什么。然后你会得到一个更大更好的画面。在更熟悉的语言中,您仍然可以在其中看到生成的程序集。