编译器如何重新排列变量来优化代码?
How compilers rearranging variables optimizes the code?
所以一般来说,如果我在 c 中声明一些变量然后打印出它们的内存地址,有时它们会与声明不符。我的讲师似乎暗示这样做是为了优化代码,但我看不出这有什么帮助。
我怀疑优化来自于将数据与尽可能接近的词对齐,但在这里我看到两个问题。
- 重新排列不应该有助于对齐,因为无论您如何重新排列它们,仍然会有相同数量的变量未对齐。如果对齐确实是他们想要的,应该使用填充。
- 如果 RAM 根据定义是“随机访问”,为什么对齐甚至很重要?如果我在 4 字节字设备中从字节 3 读取到字节 7,它的性能如何?
我将从你的第二个问题开始:内存真的是“随机访问”吗?现代硬件中的内存访问非常复杂,因此任何简单的答案都必然(过度)简化,但剥离其本质,它是这样的:内存是随机访问,因为任何内存单元都可以读取或写入任何时候,但一次只能有一个单元。这里的“单位”不止一个字节;它可能不仅仅是一个“单词”,但重要的是它们是 non-overlapping。所以你可以读取任何单元,但你不能读取任何字节序列,除非它们都在同一个单元中。如果字节序列的一部分在一个单元中,其余部分在下一个单元中,则必须读取两个单元。而且由于一次一个,这比阅读一个单元需要更长的时间。
但这仍然是随机访问。它不像一个旋转的磁盘,你必须等待你想要的数据到达读头。或者磁带驱动器,在获得所需数据之前,您可能需要将磁带缠绕或倒带很长一段距离。
曾几何时,CPU 到 assemble 字节序列的能力非常有限。例如,它可以取 four-byte 字中的前两个字节,或最后两个字节,但不包括中间两个字节。如果你需要中间的两个字节,你必须读取所有四个字节,然后移位和屏蔽以获得你感兴趣的位。现在,我们知道如何在芯片上获得更多的电路,并复制来自内存单元的任何连续字节通常都有效。单位本身也变得更宽了。所以对齐不像以前那么重要了。但这仍然很重要,因为读取存在于两个连续单元中的序列仍然需要访问两个,一个接一个。
此外,并非所有 CPU 都如此灵活。在某些 CPUs 上,内存访问一个字的中间是不可能的,所以对于那些芯片对齐仍然非常有用。
现在,对齐是一个非常抽象的词;具体实现可能对不同的数据大小有不同的对齐方式。 Abd 这就是模型编译器通常设计的目的。因此,如果 two-byte 数据可以位于 four-byte 单元的开头或结尾,那么它的地址必须是偶数。但是 four-byte 单元必须有一个可以被四整除的地址。所以顺序确实有所作为。考虑由两个单独的字节组成的数据,一个 two-byte “half-word”,一个 four-byte 字。如果这些排列为字节、半字、字节、字,那么将占用 12 个字节:一个字节的数据、一个字节的填充、一个半字(偶数对齐)、另一个字节、三个字节的填充和字。如果我们将它们排列为字节、字节、半、字,则没有填充并且数据适合八个字节。
所以一般来说,如果我在 c 中声明一些变量然后打印出它们的内存地址,有时它们会与声明不符。我的讲师似乎暗示这样做是为了优化代码,但我看不出这有什么帮助。
我怀疑优化来自于将数据与尽可能接近的词对齐,但在这里我看到两个问题。
- 重新排列不应该有助于对齐,因为无论您如何重新排列它们,仍然会有相同数量的变量未对齐。如果对齐确实是他们想要的,应该使用填充。
- 如果 RAM 根据定义是“随机访问”,为什么对齐甚至很重要?如果我在 4 字节字设备中从字节 3 读取到字节 7,它的性能如何?
我将从你的第二个问题开始:内存真的是“随机访问”吗?现代硬件中的内存访问非常复杂,因此任何简单的答案都必然(过度)简化,但剥离其本质,它是这样的:内存是随机访问,因为任何内存单元都可以读取或写入任何时候,但一次只能有一个单元。这里的“单位”不止一个字节;它可能不仅仅是一个“单词”,但重要的是它们是 non-overlapping。所以你可以读取任何单元,但你不能读取任何字节序列,除非它们都在同一个单元中。如果字节序列的一部分在一个单元中,其余部分在下一个单元中,则必须读取两个单元。而且由于一次一个,这比阅读一个单元需要更长的时间。
但这仍然是随机访问。它不像一个旋转的磁盘,你必须等待你想要的数据到达读头。或者磁带驱动器,在获得所需数据之前,您可能需要将磁带缠绕或倒带很长一段距离。
曾几何时,CPU 到 assemble 字节序列的能力非常有限。例如,它可以取 four-byte 字中的前两个字节,或最后两个字节,但不包括中间两个字节。如果你需要中间的两个字节,你必须读取所有四个字节,然后移位和屏蔽以获得你感兴趣的位。现在,我们知道如何在芯片上获得更多的电路,并复制来自内存单元的任何连续字节通常都有效。单位本身也变得更宽了。所以对齐不像以前那么重要了。但这仍然很重要,因为读取存在于两个连续单元中的序列仍然需要访问两个,一个接一个。
此外,并非所有 CPU 都如此灵活。在某些 CPUs 上,内存访问一个字的中间是不可能的,所以对于那些芯片对齐仍然非常有用。
现在,对齐是一个非常抽象的词;具体实现可能对不同的数据大小有不同的对齐方式。 Abd 这就是模型编译器通常设计的目的。因此,如果 two-byte 数据可以位于 four-byte 单元的开头或结尾,那么它的地址必须是偶数。但是 four-byte 单元必须有一个可以被四整除的地址。所以顺序确实有所作为。考虑由两个单独的字节组成的数据,一个 two-byte “half-word”,一个 four-byte 字。如果这些排列为字节、半字、字节、字,那么将占用 12 个字节:一个字节的数据、一个字节的填充、一个半字(偶数对齐)、另一个字节、三个字节的填充和字。如果我们将它们排列为字节、字节、半、字,则没有填充并且数据适合八个字节。