如何解决如果距开始位置 <128 字节,则更快地访问结构成员?
从Anger Fog's C++ optimization manual,我读到:
访问数据成员的代码更紧凑,如果偏移量 该成员相对于结构或类的开头较少 大于 128,因为偏移量可以表示为 8 位有符号 数字。如果相对于结构开头的偏移量或 类是 128 字节或更多,则偏移量必须表示为 32 位数字(指令集在 8 位和 32 位之间没有任何内容 位偏移)。示例:
// 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;}
};
这里b的偏移量为400。通过 a 访问 b 的任何代码 指针或成员函数(如 ReadB)需要将偏移量编码为 一个 32 位的数字。如果 a 和 b 交换,那么两者都可以访问 一个被编码为 8 位有符号数的偏移量,或者没有偏移量 全部。这使得代码更紧凑,以便使用代码缓存 更有效率。因此建议大阵列和 其他大对象在结构或类声明中排在最后,并且 最常用的数据成员排在第一位。如果不可能 包含前 128 个字节内的所有数据成员,然后放入最多的 前 128 个字节中经常使用的成员。
我已经试过了,我看不出这个测试程序的汇编输出有什么不同,如图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
用于这两种情况。
- 有人能解释一下这是为什么吗?
- 有人能解释一下“指令集在 8 位和 32 位偏移量之间没有任何内容”是什么意思(我是 ASM 的新手)以及这个陈述表明我应该 在 ASM 中看到吗?
解决方法
您应该查看 ReadB
的 asm,而不是 main
;但是由于它们是内联定义的,除非您调用它们(然后它将与调用函数的代码混合在一起),否则不会生成 asm。让我们将它们移出线外以使其更容易。
class S2 {
public:
int a[100];
int b;
int ReadB();
};
int S2::ReadB() { return b; }
等等。
此外,仅查看汇编代码不会显示指令的大小。您想查看实际的机器代码字节。在 Godbolt 中检查“输出:编译为二进制”会做到这一点;在真机上,您可以编译为目标文件并使用 objdump --disassemble
或显示机器代码的类似反汇编工具进行转储。
有关更新版本,请参阅 https://godbolt.org/z/bf7KjK。
这些函数中的每一个都需要一个 this
中的 rdi
指针,并且需要将 this->b
移动到 eax
中。所以它需要从内存中加载一个 dword,地址是 rdi 给出的地址加上相关类中 b
的偏移量。现在你可以看到:
-
当
b
在a
之后时,8b 87 90 01 00 00
获得mov eax,DWORD PTR [rdi+0x190]
(6 个字节) -
当
b
在课程的最开始时,您将获得8b 07
(2 个字节)的mov eax,DWORD PTR [rdi]
-
当
b
在a
之前但在新的int
成员other
之后时,您得到8b 47 04
mov eax,DWORD PTR [rdi+0x4]
。
这里使用了三种不同的addressing modes,可以通过三种方式指定要加载的地址:
-
作为寄存器(指令需要两个字节),
-
作为一个寄存器加上一个有符号的 8 位位移(占用 1 个额外字节),
-
作为一个寄存器加上一个有符号的 32 位位移(占用 4 个额外的字节)。
如果必要的位移不为零但适合 8 位,则可以使用第二种形式。如果不是,那么您将陷入第三种形式,使您的代码大 3 个字节。 (正如 prl 指出的那样,这不会必然让它变慢,但它往往会变慢,因为它会消耗更多宝贵的缓存。)
“Nothing between”是指您可能希望有一种形式,例如,具有 16 位位移的形式,这对于位移 400
来说足够大,但仅使用两个额外的字节。但没有。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。