对齐和未对齐的内存访问?

时间:2009-06-30 13:53:04

标签: c++ c memory memory-alignment omap

对齐和未对齐的内存访问有什么区别?

我在TMS320C64x DSP上工作,我想使用内部函数(汇编指令的C函数),它有

ushort & _amem2(void *ptr);
ushort & _mem2(void *ptr);

其中_amem2执行2字节的对齐访问,而_mem2执行未对齐访问。

我什么时候应该使用哪个?

6 个答案:

答案 0 :(得分:16)

许多计算机体系结构以每个几个字节的“字”存储内存。例如,Intel 32位架构存储32位字,每个字节为4个字节。但是,存储器以单字节级进行寻址;因此,地址可以“对齐”,意味着它从单词边界开始,或“未对齐”,这意味着它不会。

在某些体系结构上,某些内存操作可能会更慢,甚至完全不允许在未对齐的地址上使用。

因此,如果您知道您的地址在正确的地址上对齐,则可以使用_amem2()来提高速度。否则,您应该使用_mem2()。

答案 1 :(得分:16)

对齐的内存访问意味着指针(作为整数)是称为对齐的特定于类型的值的倍数。对齐是自然地址倍数,其中类型必须是或应该在CPU上存储(例如出于性能原因)。例如,CPU可能要求所有双字节加载或存储都通过2的倍数地址完成。对于小的基本类型(小于4个字节),对齐几乎总是类型的大小。对于结构,对齐通常是任何成员的最大对齐。

C编译器始终将您声明的变量放在满足“正确”对齐的地址处。因此,如果ptr指向例如一个uint16_t变量,它将被对齐,你可以使用_amem2。只有在访问时才需要使用_mem2。通过I / O接收的压缩字节数组,或字符串中间的字节。

答案 2 :(得分:3)

_mem2更为通用。如果ptr对齐或不对齐,它将起作用。 _amem2更严格:它要求ptr对齐(虽然可能稍微更高效)。所以使用_mem2,除非你能保证ptr始终是对齐的。

答案 3 :(得分:3)

许多处理器对内存访问都有对齐限制。未对齐访问会产生异常中断(例如ARM),或者只是较慢(例如x86)。

_mem2可能实现为获取两个字节并使用移位和/或按位操作从它们中产生16位ushort。

_amem2可能只是从指定的ptr中读取16位的ushort。

我不知道具体的TMS320C64x,但我猜它需要16位对齐才能进行16位内存访问。因此,您可以始终使用_mem2但性能损失,_amem2,当您可以保证ptr是偶数地址时。

答案 4 :(得分:3)

对齐地址是有问题的访问大小的倍数。

  • 将对齐4个字节的4字节字对齐
  • 从地址(例如)3访问4个字节将是未对齐访问

_mem2 函数很可能也适用于未对齐的访问,以便在其代码中使用正确的对齐方式。这意味着 _mem2 功能可能比其 _amem2 版本更昂贵。

因此,当您需要性能时(特别是当您知道访问延迟很高时),确定何时可以使用对齐访问是明智的。 _amem2 就是出于这个目的而存在的 - 当您知道访问权限已经对齐时为您提供性能。

对于2字节访问,识别对齐操作非常简单 如果操作的所有访问地址都是“偶数”(即,它们的LSB为零),则您具有2字节对齐。这可以通过

轻松检查
if (address & 1) // is true
    /* we have an odd address; not aligned */
else
    /* we have an even address; its aligned to 2-bytes */

答案 5 :(得分:3)

我知道这是一个带有选定答案的旧问题,但没有看到有人解释对齐和未对齐内存访问之间的区别是什么......

是戏剧性的,还是sram或flash或其他。以sram作为一个简单的例子,它是由比特构建的,一个特定的sram将由固定数量的位宽和固定数量的行构建。比方说32位宽,几行/多行深。

如果我在此sram中对地址0x0000进行32位写操作,则此sram周围的内存控制器只需对第0行执行单个写周期。

如果我在此sram中对地址0x0001进行32位写操作,假设允许,则控制器需要读取第0行,修改三个字节,保留一个,然后将其写入第0行,然后读取第1行修改一个字节,留下其他三个找到并写回。哪些字节被修改或不与系统的字节序有关。

前者是对齐的,后者是不对齐的,显然性能差异加上需要额外的逻辑才能完成四个存储周期并合并字节通道。

如果我从地址0x0000读取32位,则完成第0行的单次读取。但是从0x0001读取并且我必须做两次读取row0和row1,并且根据系统设计,将这些64位发送回处理器可能是两个总线时钟而不是一个。或者存储器控制器具有额外的逻辑,以便32位在一个总线周期内在数据总线上对齐。

16位读取稍好一些,从0x0000读取,0x0001和0x0002只能从row0读取,并且可以基于系统/处理器设计将这32位发送回去,处理器将它们提取或移位内存控制器使它们落在特定的字节通道上,因此处理器不必旋转。如果不是两者,则必须有一个或另一个。从0x0003读取虽然如上所述,您必须读取第0行和第1行,因为每个字节中的一个都在其中,然后再发送64位以供处理器提取,或者内存控制器将这些位组合成一个32位总线响应(假设处理器和内存控制器之间的总线对于这些示例是32位宽。)

16位写入总是以这个示例sram中的至少一个读取 - 修改 - 写入结束,地址0x0000,0x0001和0x0002读取row0修改两个字节并写回。地址0x0003读取两行,每次修改一个字节并写回。

8位你只需要读取包含该字节的一行,写入虽然是一行的读 - 修改 - 写。

armv4不喜欢未对齐虽然你可以禁用陷阱但结果不像你想象的那样,不重要,当前的手臂允许不对齐并给你上述行为你可以改变控制寄存器中的一点然后它将中止未对齐的转账。 mips曾经不允许,不知道他们现在做了什么。允许x86,68K等,内存控制器可能必须完成大部分工作。

不允许它的设计显然是为了性能和较少的逻辑,有些人会说是程序员的负担,其他人可能会说它不是程序员的额外工作或程序员更容易。对齐与否你也可以看到为什么最好不要尝试通过制作8位变量来保存任何内存,而是继续刻录32位字或任何寄存器或总线的自然大小。它可以以一些字节的小成本帮助您的性能。更不用说编译器需要添加的额外代码,使得32位寄存器可以模仿8位变量,屏蔽并有时签名扩展。在使用寄存器本机大小的情况下,不需要这些附加指令您还可以将多个内容打包到总线/内存大的位置,并执行一个内存周期来收集或写入它们,然后使用一些额外的指令来操作寄存器之间的操作,而不会损坏内存和可能的指令数量。

我不同意编译器将始终为目标对齐数据,有办法打破这一点。如果目标不支持未对齐,您将遇到错误。如果编译器总是基于您可能提出的任何合法代码做正确的话,程序员就永远不需要谈论这个问题,除非是为了性能,否则没有理由提出这个问题。如果您不控制void ptr地址是否对齐,那么您必须始终使用mem2()未对齐访问,或者您必须根据ptr的值在代码中执行if-then-else作为nik指出。通过声明为void,C编译器现在无法正确处理您的对齐,并且无法保证。如果您使用char * prt并将其提供给这些函数,则所有投注都会在编译器上完成,而不会在mem2()函数中或在这两个函数之外添加额外的代码。所以写在你的问题中mem2()是唯一正确的答案。

在您的台式机/笔记本电脑中使用的DRAM往往是64或72(带有ecc)位宽,并且每次访问它们都是对齐的。即使记忆棒实际上由8位宽或16或32位宽的芯片组成。 (这可能因手机/平板电脑因各种原因而改变)内存控制器和理想情况下至少有一个缓存位于此dram前面,以便处理小于总线宽度读取 - 修改 - 写入的未对齐或甚至对齐的访问在缓存sram中,速度更快,并且dram访问都是对齐的完整总线宽度访问。如果你在dram前没有缓存并且控制器是为全宽访问而设计的,那么这是性能最差的,如果设计用于单独点亮字节通道(假设8位宽的芯片)那么你就没有读取 - 修改 - 写一个更复杂的控制器。如果典型的用例是带缓存(如果设计中有一个)那么在控制器中为每个字节通道进行额外的工作可能没有意义,但让它只知道如何进行完整的总线宽度传输或。的倍数。

相关问题