对齐和未对齐的内存访问有什么区别?
我在 TMS320C64x DSP 上工作,我想使用内部函数(汇编指令的 C 函数),它有
ushort & _amem2(void *ptr);
ushort & _mem2(void *ptr);
在哪里_amem2
进行 2 个字节的对齐访问和_mem2
未对齐访问。
我什么时候应该使用哪个?
对齐和未对齐的内存访问有什么区别?
我在 TMS320C64x DSP 上工作,我想使用内部函数(汇编指令的 C 函数),它有
ushort & _amem2(void *ptr);
ushort & _mem2(void *ptr);
在哪里_amem2
进行 2 个字节的对齐访问和_mem2
未对齐访问。
我什么时候应该使用哪个?
许多计算机体系结构将内存存储在每个数字节的“字”中。例如,英特尔 32 位体系结构存储 32 位字,每个字 4 个字节。但是,内存是在单字节级别寻址的;因此,地址可以“对齐”,即从字边界开始,或“未对齐”,即不对齐。
在某些架构上,某些内存操作可能会更慢,甚至在未对齐的地址上完全不允许。
因此,如果您知道您的地址与正确的地址对齐,则可以使用 _amem2() 来提高速度。否则,您应该使用 _mem2()。
对齐的内存访问意味着指针(作为整数)是称为对齐的特定类型值的倍数。对齐是类型必须是或应该存储(例如出于性能原因)在 CPU 上的自然地址倍数。例如,CPU 可能要求所有两字节的加载或存储都通过是 2 的倍数的地址来完成。对于小的基本类型(小于 4 个字节),对齐几乎总是类型的大小。对于结构,对齐通常是任何成员的最大对齐。
C 编译器总是将您声明的变量放在满足“正确”对齐的地址处。因此,如果 ptr 指向例如 uint16_t 变量,它将被对齐并且您可以使用 _amem2。只有在访问例如通过 I/O 接收的打包字节数组或字符串中间的字节时,才需要使用 _mem2。
我知道这是一个带有选定答案的老问题,但没有看到有人解释对齐和未对齐内存访问之间有什么区别的答案......
无论是 DRAM 还是 SRAM 或闪存或其他。以 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 位宽)。
但是,在此示例 sram 中,16 位写入始终以至少一次读取-修改-写入结束,地址 0x0000、0x0001 和 0x0002 读取行 0 修改两个字节并写回。地址 0x0003 读取两行,每行修改一个字节并写回。
8位您只需要读取包含该字节的一行,尽管写入是一行的读取-修改-写入。
armv4 不喜欢未对齐,尽管您可以禁用陷阱,但结果与您在上面所期望的不同,这并不重要,当前武器允许未对齐并为您提供上述行为,您可以在控制寄存器中更改一点,然后它将中止未对齐转移。mips 以前不允许,现在不知道他们在做什么。x86、68K 等是允许的,内存控制器可能不得不做最多的工作。
显然不允许它的设计是为了性能和更少的逻辑,有些人会说这是程序员的负担,其他人可能会说这对程序员来说没有额外的工作或更容易。对齐与否,您还可以看到为什么最好不要尝试通过创建 8 位变量来节省任何内存,而是继续烧写 32 位字或寄存器或总线的自然大小。它可以以一些字节的小成本帮助您的性能。更不用说编译器需要添加的额外代码来使 32 位寄存器模仿 8 位变量、屏蔽和有时符号扩展。在使用寄存器本机大小的情况下,不需要这些额外的指令。
我不同意编译器总是为目标对齐数据,有办法打破它。如果目标不支持未对齐,您将遇到错误。如果编译器总是根据你能想到的任何合法代码正确地做到这一点,程序员就永远不需要谈论这个问题,除非是为了性能,否则没有理由提出这个问题。如果您不控制 void ptr 地址是否对齐,那么您必须始终使用 mem2() 未对齐访问,或者您必须根据 ptr 作为 nik 的值在代码中执行 if-then-else指出。通过声明为 void,C 编译器现在无法正确处理您的对齐方式,并且无法保证。如果您采用 char *prt 并将其提供给这些函数,那么所有的赌注都在于编译器是否正确,而无需在 mem2() 函数中或这两个函数之外添加额外的代码。所以写在你的问题 mem2() 是唯一正确的答案。
说在您的台式机/笔记本电脑中使用的 DRAM 往往是 64 或 72(带 ecc)位宽,并且对它们的每次访问都是对齐的。即使记忆棒实际上是由 8 位宽或 16 或 32 位宽的芯片组成的。(由于各种原因,这可能会随着手机/平板电脑而改变)内存控制器和理想情况下至少有一个缓存位于该 DRAM 前面,以便处理小于总线宽度的未对齐甚至对齐访问读取-修改-写入在缓存 sram 中速度更快,并且 DRAM 访问都是对齐的全总线宽度访问。如果您在 DRAM 前面没有缓存并且控制器是为全宽度访问而设计的,那么这是最差的性能,如果设计用于单独点亮字节通道(假设 8 位宽芯片),那么您没有读取-修改-写入,而是一个更复杂的控制器。如果典型用例是缓存(如果设计中有缓存),那么在控制器中为每个字节通道进行额外的工作可能没有意义,但让它知道如何进行全总线宽度大小的传输或倍数。
对齐地址是所讨论的访问大小的倍数。
也可用于未对齐访问的_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 */
_mem2 更通用。如果 ptr 是否对齐,它将起作用。_amem2 更严格:它要求 ptr 对齐(尽管可能效率更高一些)。所以使用 _mem2 除非你能保证 ptr 总是对齐的。
许多处理器对内存访问有对齐限制。非对齐访问要么产生异常中断(例如 ARM),要么只是速度较慢(例如 x86)。
_mem2
可能实现为获取两个字节并使用移位和/或按位运算来制作 16 位 ushort。
_amem2
可能只是从指定的 ptr 读取 16 位 ushort。
我不具体了解 TMS320C64x,但我猜它需要 16 位对齐才能进行 16 位内存访问。因此,您可以使用_mem2
always 但会降低性能,并且_amem2
可以保证 ptr 是偶数地址。