我发现结果令人费解,所以我进一步调查了一下。首先,我通过使用 chrono 并添加一个通过指针访问局部变量(而不是堆上的内存)的测试来增强示例 prog。这确保了时间差异不是由对象的位置引起的,而是由访问方法引起的。
其次,我在结构中添加了一个虚拟成员,因为我注意到直接成员目标使用了堆栈指针的偏移量,我怀疑这可能是罪魁祸首;指针版本通过没有偏移的寄存器访问内存。假人在那里平整了场地。但这并没有什么不同。
通过指针访问堆和本地对象的速度明显更快。这是来源:
#include<chrono>
#include<iostream>
using namespace std;
using namespace std::chrono;
struct MyStruct { /* offset for i */ int dummy; int i; };
int main()
{
MyStruct *heapPtr = new MyStruct;
MyStruct localObj;
MyStruct *localPtr = &localObj;
///////////// ptr to heap /////////////////////
auto t1 = high_resolution_clock::now();
for (int i = 0; i < 100000000; ++i)
{
heapPtr->i = i;
}
auto t2 = high_resolution_clock::now();
cout << "heap ptr: "
<< duration_cast<milliseconds>(t2-t1).count()
<< " ms" << endl;
////////////////// local obj ///////////////////////
t1 = high_resolution_clock::now();
for (int i = 0; i < 100000000; ++i)
{
localObj.i = i;
}
t2 = high_resolution_clock::now();
cout << "local: "
<< duration_cast<milliseconds>(t2-t1).count()
<< " ms" << endl;
////////////// ptr to local /////////////////
t1 = high_resolution_clock::now();
for (int i = 0; i < 100000000; ++i)
{
localPtr->i = i;
}
t2 = high_resolution_clock::now();
cout << "ptr to local: "
<< duration_cast<milliseconds>(t2-t1).count()
<< " ms" << endl;
/////////// have a side effect ///////////////
return heapPtr->i + localObj.i;
}
这是一个典型的运行。 heap 和 local ptr 之间的差异在两个方向上都是随机的。
heap ptr: 217 ms
local: 236 ms
ptr to local: 206 ms
这里是指针的反汇编和直接访问。我假设 heapPtr 的堆栈偏移量为 0x38,因此第一个 mov 将其内容(即它指向的堆上对象的地址)移动到 %rax。这用作在第三次移动中将值移动到的地址(由于前面的虚拟成员而具有 4 个字节的偏移量)。
第二步将 i 的值(i 显然位于堆栈偏移 4C,如果您计算所有中间定义,则对齐)到 %edx(因为最后一个 mov 最多可以有一个内存操作数,即对象,所以 i 中的值必须进入寄存器)。
最后一个 mov 将 i 的值,在寄存器 %edx 中,放入对象的地址,现在在 %rax 中,加上 4 的偏移量,因为是 dummy。
heapPtr->i = i;
3e: 48 8b 45 38 mov 0x38(%rbp),%rax
42: 8b 55 4c mov 0x4c(%rbp),%edx
45: 89 50 04 mov %edx,0x4(%rax)
正如预期的那样,直接访问时间更短。变量的值(不同的本地 i,这次在堆栈偏移量 0x48 处)加载到寄存器 %eax 中,然后将其写入堆栈偏移量 -0x60 处的地址(我不知道为什么一些本地对象存储在正偏移量和其他在负数)。最重要的是,这是一条比指针访问短的指令;基本上,缺少指针访问的第一条指令,它将指针的值加载到地址寄存器中。这正是我们所期望的——这就是取消引用。 尽管如此,直接访问需要更多时间。我不知道为什么。由于我排除了大多数可能性,我必须假设使用 %rbp 比使用 %rax 慢(不太可能),或者负偏移量会减慢访问速度。是这样吗?
localObj.i = i;
d6: 8b 45 48 mov 0x48(%rbp),%eax
d9: 89 45 a0 mov %eax,-0x60(%rbp)
需要注意的是,gcc 在开启优化时会将赋值移出循环。因此,对于关心性能的人来说,这在某种程度上是一个幻影问题。此外,这些微小的差异将被循环中发生的任何“真实”事件所淹没。但还是出乎意料。