如果距离开始 <128 字节,结构成员的访问速度会更快吗?

Access of struct member faster if located <128 bytes from start?

来自Anger Fog's C++ optimization manual,我读到:

The code for accessing a data member is more compact if the offset of the member relative to the beginning of the structure or class is less than 128 because the offset can be expressed as an 8-bit signed number. If the offset relative to the beginning of the structure or class is 128 bytes or more then the offset has to be expressed as a 32-bit number (the instruction set has nothing between 8 bit and 32 bit offsets). Example:

// Example 7.40
class S2 {
public:
int a[100]; // 400 bytes. first byte at 0, last byte at 399
int b; // 4 bytes. first byte at 400, last byte at 403
int ReadB() {return b;}
};

The offset of b is 400 here. Any code that accesses b through a pointer or a member function such as ReadB needs to code the offset as a 32-bit number. If a and b are swapped then both can be accessed with an offset that is coded as an 8-bit signed number, or no offset at all. This makes the code more compact so that the code cache is used more efficiently. It is therefore recommended that big arrays and other big objects come last in a structure or class declaration and the most often used data members come first. If it is not possible to contain all data members within the first 128 bytes then put the most often used members in the first 128 bytes.

我试过了,我看不出这个测试程序的汇编输出有什么不同,如图所示here:

class S2 {
public:
    int a[100]; // 400 bytes. first byte at 0, last byte at 399
    int b; // 4 bytes. first byte at 400, last byte at 403
    int ReadB() { return b; }
};

// Changed order of variables a and b!
class S3 {
public:
    int b; // 4 bytes. first byte at 400, last byte at 403
    int a[100]; // 400 bytes. first byte at 0, last byte at 399
    int ReadB() { return b; }
};

int main()
{
    S3 s3; s3.b = 32;
    S2 s2; s2.b = 16;
}

输出为

push    rbp
mov     rbp, rsp
sub     rsp, 712
mov     DWORD PTR [rbp-416], 32
mov     DWORD PTR [rbp-432], 16
mov     eax, 0
leave
ret

显然,mov DWORD PTR 用于两种情况。

  1. 谁能解释这是为什么?
  2. 有人可以解释“指令集在 8 位和 32 位偏移量之间没有任何内容”(我是 ASM 的新手)是什么意思,以及这个陈述暗示的是什么我应该 在 ASM 中看到吗?

您应该查看 ReadB 的 asm,而不是 main;但由于它们是内联定义的,除非您调用它们(然后它会与调用函数的代码混合在一起),否则不会生成任何 asm。让我们将它们移出线外以使其更容易。

class S2 {
public:
    int a[100];
    int b;
    int ReadB();
};

int S2::ReadB() { return b; }

以此类推

此外,仅查看 asm 代码不会告诉您指令的大小。您想查看实际的机器代码字节。在 Godbolt 中检查“输出:编译为二进制文件”即可;在真实机器上,您可以编译为目标文件并使用 objdump --disassemble 或显示机器代码的类似反汇编工具转储。

有关更新版本,请参阅 https://godbolt.org/z/bf7KjK

这些函数中的每一个都在 rdi 中获取一个 this 指针,并且需要将 this->b 移动到 eax 中。所以它需要从内存中加载一个双字,地址为rdi给出的地址加上相关class中b的偏移量。现在你可以看到:

  • ba 之后时,mov eax, DWORD PTR [rdi+0x190]

    得到 8b 87 90 01 00 00(6 个字节)
  • b 位于 class 的开头时,mov eax, DWORD PTR [rdi][=35= 会得到 8b 07(2 个字节) ]

  • ba 之前但在新的 int 成员之后 other,你得到 8b 47 04 mov eax, DWORD PTR [rdi+0x4].

这里使用了三种不同的,可以通过三种方式指定要加载的地址:

  • 作为寄存器(指令需要两个字节),

  • 作为寄存器加上一个带符号的8位位移(额外占用1个字节),

  • 作为寄存器加上有符号的32位位移(额外占用4个字节)

如果必要的位移不为零但适合8位,则可以使用第二种形式。如果没有,那么您将陷入第三种形式,使您的代码增加 3 个字节。 (正如 prl 指出的那样,这并不 必然 使它变慢,但它往往会变慢,因为它会耗尽更多宝贵的缓存。)

“Nothing between”指的是您可能希望有一种形式,例如,16 位位移,这对于位移 400 来说足够大,但只使用两个额外的字节.但是没有。