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

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

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

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

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

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

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

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

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

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

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

_mem2更为通用。 如果ptr对齐或不对齐,它将起作用。 _amem2更严格:它要求ptr对齐(虽然可能稍微有点效率)。 因此,除非可以保证ptr始终对齐,否则请使用_mem2。

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

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

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

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

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

  • 将对齐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 */ 

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

无论是戏剧,还是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不喜欢未对齐,虽然你可以禁用陷阱,结果不像你想象的那样,不重要,当前的arm允许不对齐并给你上述行为你可以在控制寄存器中改变一点然后它会中止不对齐传输。 mips曾经不允许,不知道他们现在做了什么。 允许x86,68K等,内存控制器可能必须完成大部分工作。

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

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

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