我理解正确吗?
我试图深入探讨内存分配,寻址和我也遇到了堆栈对齐的概念,一般来说,内存对齐。 我想了解我是否正确理解了所有的概念。 我的问题都是现在提到的电脑和处理器,比如我们笔记本电脑上的那些。 我希望强调,我在stackoverflow上阅读了很多其他问题,并且我的大部分实际知识都来自他们。
我的第一个怀疑与记忆词的概念有关。 存储器字不仅定义了寄存器和总线大小,而且还定义了基本存储器单元(例如,64位拱形上的64位,32位拱形电路板上的32位)。 然而,据我所知,每个地址都会引用一个字节的内存,而不管内存字的大小如何。 所以我们可以说每个字节都有自己的地址。 但:
1) CPU无法访问单个字节,但访问包含该字节的整个字是否正确? 因此,如果一个特定的字节是请求(例如,我访问一个字符的直接地址),它访问整个单词并进行一些计算以删除另一部分并返回确切的字节?
2)那么CPU是否可以实际访问存储器单元单元并且每个存储器单词是从一个偶数地址开始的,是单元本身的倍数?
因此,例如,在64位体系结构中,存储器字是8字节,因此(示例)地址0x2710(以10为底的10000)将是存储器字的开始。 如果我尝试访问0x2711,CPU将从0x2710访问到0x2717,然后只提取单个字节。 正确?!
第二。 正如我之前所说,我遇到了内存对齐问题。 在开始时,它造成了我一些困惑。 请帮助我了解我是否正确。 这个问题基本上与性能有关,或者在某些情况下,与需要16字节对齐的SSE特定指令有关。 例如,在第一种情况下,如果(在64位arch上)一个8字节的数据(例如long int)跨2个存储字存储,则CPU需要2次访问而不是1次。
3)所以,举个例子:
0x2710 | .... |
0x2711 | .... |
0x2712 | .... |
0x2713 | .... |
0x2714 | data |
0x2715 | data |
0x2716 | data |
0x2717 | data |
0x2718 | data |
0x2719 | data |
0x2720 | data |
0x2721 | data |
0x2722 | .... |
0x2723 | .... |
0x2724 | .... |
0x2725 | .... |
在这种情况下,内存不对齐。 正确? 通过对齐,CPU将只存储从0x2710开始的数据,或者如果占用到0x2713,它将插入填充,然后从0x2718存储8个字节的数据。 对?
4)因此,内存对齐基本上包括从仅从所需字节单位的多个地址(通常,内存字本身,但也包括其他自定义单位 - 例如,在GCC上使用m优先级 - 堆栈边界)的地址开始讲述多字节数据。 。 我说“多字节”数据,因为如果数据只有一个字节,它总是只能放入一个单词中。 这一切是正确的吗?
5)内存对齐由编译器应用? 因此,它在二进制(汇编)代码中应用,还是在存储数据时由CPU以某种方式应用? 而且,这不是对内存的大量浪费吗? 我的意思是,如果总是应用每个多字节数据,也可能意味着填充! 我可以是一个巨大的内存空间浪费!
就这样! 谢谢,非常感谢提前!
数据对齐是硬件体系结构优化,特定于CPU。 在单次获取中从内存获取8个字节可以更快,更简单,然后丢弃额外的数据并在CPU内部移动数据。 它还可以通过忽略地址的低3位(0-7)来简化数据总线,从而节省CPU / MMU和存储器总线之间的三条信号线。 较少的数据总线线路意味着在PCB上布线信号更容易,RF噪声更少。
但是,如果CPU使用8字节对齐并将8字节值存储在未对齐的地址处,则访问该值现在需要2次提取 - 从而导致较差的执行性能。 如果程序员/编译器知道数据对齐,他/它可以安排数据以避免双重提取。 这可能会浪费一些内存来节省CPU周期,如果内存很便宜而且时间不够,这很好。 或者,如果内存不便宜,程序员可以使用#pragma pack(1)
覆盖默认的数据对齐方式,这会告诉编译器忽略数据对齐。
堆栈通常对齐,以便使用泛型指令更容易推送和弹出。 在这种情况下,它会以浪费少量内存为代价来使生活变得更简单。
3)CPU不决定在哪里存储数据,程序员/编译器(有时是操作系统)做出这个决定。 CPU完全能够从任何地址读取任何大小的数据,但不一定在一次操作中。 数据不一致将需要更多的提取和更多的时间。 某些CPU在错误操作(摩托罗拉68000和许多低成本微控制器)上会出现故障,但是大多数带有MMU的CPU将在内部处理它。
4)不完全。 重要的是多字节数据不能跨越对齐边界。 在8字节与2字节值对齐的情况下,该值可以被存储在地址0x1000,0x1001,0x1002,0x1003,0x1004,0x1005,0x1006而不需要多次读取。 由于CPU需要读取0x1000 [.. 0x1007]和0x1008 [.. 0x100F]才能读取整个值,因此仅将其存储在0x1007处会导致问题。
5)是的,一些内存可能会浪费但不是太多。 当你只需要1时,读取8个字节没有任何性能影响。如果你的代码有8个char
值,编译器会安排它们,使它们全部在同一个8字节字中。 这样不会浪费空间,也不会影响性能。
每个平台都有一套称为ABI或应用程序二进制接口的约定。 它们通常记录在平台开发人员提供的文档中。 这些约定涵盖了一些主题,对齐规则就是其中之一。 在给定的硬件架构上可能存在多个平台; 一个例子是x64,其中有两个主要的ABI,Microsoft ABI(在Windows上使用)和System V ABI(在Linux上使用)。
对齐规则通常由硬件支配。 例如,某些硬件体系结构显然不能在CPU内核和内存之间传输未对齐的数据。 一些硬件架构虽然能够这样做,但对于每一次这样的传输都会导致性能损失。
为了生成符合目标平台的ABI的程序,编译器工具链与操作系统配合使用。 例如,操作系统保证可执行文件部分应始终加载在满足ABI规定的最严格对齐要求的地址处。 链接器生成包含对齐对象的部分时,它依赖于该对象。 AC编译器应根据对齐要求在对象文件中注释片段,以便链接器可以在从多个编译单元中编写单个文件时相应地使用该信息进行布局。
当涉及到堆栈时,可能存在不同的策略。 在某些平台上,总是要求函数以最严格的对齐要求的倍数消耗堆栈。 如果编译器可能依赖它,它将相应地布局一个函数的栈帧。
但是,在某些平台上,堆栈对齐要求并不那么严格。 例如,一个SSE数据类型在32字节处对齐,但是人们认为,对于每个函数来说,要占用32个字节的倍数是非常耗费的:这种类型的使用相对较少。 这意味着,在编译将__m256
放入堆栈的函数时,编译器可能通常不会在函数启动时依赖足够的堆栈。 然后编译器会在prolog中插入一个代码来检查它是否是,并且如果不是,则另外增加堆栈。 显然,这是一个折衷:如果你需要更严格的对齐,你的程序开始浪费太多的堆栈空间,如果要求过于宽松,编译器将需要发布对齐代码,这会膨胀代码并妨碍性能。
上一篇: have I understood correctly?
下一篇: Should I align a character array before accessing it as 32