据我了解,如果内存对齐,代码将执行得更快,因为处理器不必采取额外的步骤来恢复被切割的内存位。
这不一定是执行的事情,x86 具有可变长度的指令,从单个 8 位指令开始,最多可达几个字节,这都是关于未对齐的。但他们已采取措施在很大程度上消除这种情况。
如果我的处理器边缘有 64 位总线,这并不意味着芯片边缘,而是内核边缘。另一面是一个内存控制器,它知道总线协议,是地址开始被解码的第一个地方,事务开始将其他总线拆分到它们的目的地。
它非常特定于架构和总线设计,随着时间的推移,您可以拥有具有不同总线的架构或不同版本,例如,您可以获得具有 64 总线或 32 位总线的臂。但是假设我们有一个非典型的情况,即总线为 64 位宽,并且该总线上的所有事务都在 64 位边界上对齐。
如果我要对 0x1000 进行 64 位写入,这将是一个单一的总线事务,现在这是某种写入地址总线,带有一些 id x 和长度为 0 (n-1) 然后另一边确认我看到你想用 id x 进行写入,我准备好接收你的数据。然后处理器使用 id x 的数据总线发送数据,每 64 位一个时钟,这是一个 64 位,因此该总线上有一个时钟。可能会返回一个 ack,也可能不会。
但是,如果我想对 0x1004 进行 64 位写入,会发生什么变成两个事务,一个完整的 64 位地址/数据事务位于地址 0x1000,只有四个字节通道启用通道 4-7(表示地址 0x1004-0x1007)。然后在 0x1008 完成一个完整的事务,启用 4 字节通道,通道 0-3。因此,总线上的实际数据移动从一个时钟变为两个时钟,但也有两倍的握手开销才能到达这些数据周期。在那辆公共汽车上,您可能会感觉到整体系统设计如何,或者可能需要做很多事情才能感觉到,但这是非常明显的。但效率低下是存在的,不管是否埋没在噪音中。
我想我理解 64 位处理器读取 64 位 x 64 位内存。
根本不是一个好的假设。如今,32 位 ARM 具有 64 位总线,例如 ARMv6 和 ARMv7 都带有或可以。
现在,假设我有一个按顺序排列的结构(没有填充):char、short、char 和 int。为什么短线会错位?我们拥有块中的所有数据!为什么它必须在一个是 2 的倍数的地址上。整数和其他类型的问题相同?
unsigned char a 0x1000
unsigned short b 0x1001
unsigned char c 0x1003
unsigned int d 0x1004
您通常会在代码 something.a something.b something.c something.d 中使用结构项。当您访问 something.b 时,它是针对总线的 16 位事务。在 64 位系统中,您是正确的,如果按照我已经解决的方式对齐,那么当您执行 x = something.b 时正在读取整个结构,但是处理器将丢弃除字节通道 1 和 2 之外的所有通道(丢弃 0 和3-7),那么如果您访问 something.c,它将在 0x1000 处进行另一次总线事务并丢弃除通道 3 之外的所有内容。
当您使用 64 位总线写入 something.b 时,仅启用字节通道 1 和 2。现在更痛苦的地方是,如果有一个缓存,它很可能也由一个 64 位 ram 构造来与这个总线配合,不是必须的,但让我们假设它有。您想通过缓存写入 something.b,0x1000 处的写入事务,字节通道 1 和 2 启用 0、3-7 禁用。缓存最终会得到这个事务,它在内部必须执行读取修改写入,因为它不是一个完整的 64 位宽事务(启用所有通道),因此从性能角度来看,您也会受到读取修改写入的影响(上面未对齐的 64 位写入也是如此)。
short 是未对齐的,因为在打包时它的地址 lsbit 被设置,要对齐 8 位中的 16 位项目,字节世界需要为零,因为 32 位项目要对齐其低两位地址是零,64位,三个零等等。
根据系统的不同,您最终可能会使用 32 位或 16 位总线(这些天不是为了内存),因此您最终可以进行多次传输。
您的高效处理器(如 MIPS 和 ARM)采用了对齐指令的方法,并强制对齐事务,即使在 something.b 情况下也不会对 32 位或 64 位总线造成任何损失。该方法是性能优于内存消耗,因此指令在某种程度上浪费了它们的消耗,以提高它们的获取和执行效率。数据总线同样简单得多。当构造高级概念(如 C 中的结构)时,在填充以对齐结构中的每个项目以获得性能时会浪费内存。
unsigned char a 0x1000
unsigned short b 0x1002
unsigned char c 0x1004
unsigned int d 0x1008
举个例子
我还有第二个问题:对于我之前提到的结构,处理器如何知道它何时读取它的 64 位,前 8 位对应于一个字符,然后接下来的 16 位对应于一个短等等...... ?
unsigned char c 0x1003
编译器在地址 0x1003 处生成单字节大小的读取,这将转换为具有该地址的特定指令,并且处理器生成总线事务来执行此操作,然后处理器总线的另一端执行其工作,依此类推下线。
编译器通常不会将该结构的打包版本转换为提供所有项目的单个 64 位事务,而是为每个项目刻录一个 64 位总线事务。
有可能取决于指令集、预取器、缓存等,而不是在高层使用结构,而是创建一个 64 位整数并在代码中完成工作,那么您可能会或可能会没有获得性能。在大多数使用缓存等运行的架构上,预计这不会更好地执行,但是当您进入嵌入式系统时,您可能在 ram 上有一些等待状态,或者在闪存或任何代码存储上有一些等待状态您可以找到时间,而不是更少的指令和更多的数据事务,您想要更多的指令和更少的数据事务。代码是线性的 代码段,如读取、掩码和移位、掩码和移位等。指令存储可能具有用于线性事务的突发模式,但数据事务需要尽可能多的时钟。
中间立场是将所有内容都设为 32 位变量或 64 位,然后全部对齐并以使用更多内存为代价执行相对较好。
因为人们不懂对齐,被 x86 编程宠坏了,选择跨编译域使用结构(这是个坏主意),ARM 和其他人正在容忍未对齐的访问,你可以非常感受到性能的影响那些平台,因为如果一切都对齐,它们会非常高效,但是当你做一些不对齐的事情时,它只会产生更多的总线事务,从而使一切花费更长的时间。因此,较旧的臂默认会出现故障,arm7 可以禁用故障但会围绕字旋转数据(在一个字中交换 16 位值的好技巧)而不是溢出到下一个字,后来的架构默认不对齐错误或大多数人将它们设置为不对齐错误,并且他们按照人们希望/期望的方式读取/写入未对齐的传输。
对于您的计算机中的每个 x86 芯片,您在同一台计算机或挂在该计算机上的外围设备(鼠标、键盘、显示器等)中都有多个(如果不是少数)非 x86 处理器。其中很多是 8 位 8051s 和 z80s,但也有很多是基于 arm 的。因此,不仅所有手机和平板电脑的主处理器正在进行大量非 x86 开发。其他人希望低成本和低功耗,以便在总线性能方面提高编码效率,从而降低时钟速度,同时在整体上平衡代码/数据使用,以降低闪存/内存的成本。
在 x86 平台上强制执行这些对齐问题非常困难,要克服其架构问题需要大量开销。但是您可以在更高效的平台上看到这一点。就像火车对跑车,有人从火车上掉下来或跳上火车,有如此大的动力,以至于一点都没有注意到,但是改变跑车上的质量,你会感觉到它。所以尝试在 x86 上执行此操作,如果您甚至可以弄清楚如何执行此操作,您将不得不更加努力地工作。但在其他平台上更容易看到效果。除非你找到一个 8086 芯片并且我怀疑你能感觉到那里的差异,否则必须拿出我的手册来确认。
如果您有幸能够访问芯片源/模拟,那么您可以看到到处都在发生这种事情,并且可以真正开始手动调整您的程序(针对该平台)。同样,您可以看到各种形式的缓存、写缓冲、指令预取等对整体性能有何作用,有时会创建并行时间段,在这些时间段内其他效率不高的事务可以隐藏,或者故意创建备用周期,因此需要额外时间的事务可以有一个时间片。