与全局数组相比,为什么在堆中为局部数组分配内存更快?
Why is it faster to allocate memory in the heap for a local array compared to a global array?
下面是我决定 运行 的一个简单实验。 test1()
是一个为全局数组 g
分配内存的函数,然后有一个 for 循环更新这个数组的所有元素。最后,它释放为 g
分配的内存。 test2
中发生了完全相同的事情,但现在我们使用本地数组而不是称为 l
.
#include <iostream>
#define n 1000000000
using namespace std;
int *g;
void test1(){
g = new int[n];
int i;
for(i=0;i<n;i++) g[i] = i;
delete[] g;
}
void test2(){
int *l = new int[n];
int i;
for(i=0;i<n;i++) l[i] = i;
delete[] l;
}
int main()
{
timespec cpu_time_s;
timespec cpu_time_e;
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &cpu_time_s);
test1();
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &cpu_time_e);
long long int ns = (cpu_time_e.tv_sec * 1000000000
+ cpu_time_e.tv_nsec - (cpu_time_s.tv_sec * 1000000000
+ cpu_time_s.tv_nsec));
cout<<ns<<" ns"<<endl;
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &cpu_time_s);
test2();
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &cpu_time_e);
ns = (cpu_time_e.tv_sec * 1000000000
+ cpu_time_e.tv_nsec - (cpu_time_s.tv_sec * 1000000000
+ cpu_time_s.tv_nsec));
cout<<ns<<" ns"<<endl;
return 0;
}
我 运行 进行了一些测试,我绕过 2709066246 ns
用于 test1()
和 2459390299 ns
用于 test2
由于第一个测试 运行 较慢,无论它是哪个,那么 OS 可能需要更长的时间来分配 4GB,然后在第一次通过时将其映射到可写内存。在极端情况下,它可能需要将其他内容保存到交换文件中以使 RAM 第一次可用,但第二次则不然。在轻负载下,您希望这不是必需的,但 OS 仍然可能需要做一些事情,例如删除磁盘缓存并重新使用它之前占用的内存。这是快速但不是即时的。
在第二遍中,您刚刚释放了 4GB 内存,所有这些内存最近都已映射,因此要做的工作可能会少一些。释放的 4GB 甚至可能仍然与您的进程相关联,因此 OS 根本没有任何关系,尽管您不希望有那么大的块。
您可以通过 运行ning test2
两次进行检查,看看第一次是否始终较慢。如果大体上是这样的话,那就是分配和写入数组,而不是你如何做的细节。
如果全局无论顺序如何都变慢,那么我能想到的最明显的可能原因是编译器可能不会将 g
保存在寄存器中,而是可能会重复将其从寄存器中加载出来全局区域。您可以通过查看它发出的代码来检查。
这个现象和我在this post的回答中描述的一样。
TL;DR 当您访问之前分配的内存时,OS 需要分配页面。当您释放并重新分配时,您最终会进入相同的内存区域(或几乎相同),并且页面已经由先前的访问分配。
请注意,页面分配完全独立于内存分配。分配 4GB 不会使用 4GB 或物理内存,直到您访问它。
要对此进行测试,请尝试在第一个循环中只访问一半的内存,在第二个循环中尝试访问另一半。结果应该是相同的。您也可以尝试只访问奇数页,然后访问偶数页(页面长 4096 字节,1024 整数)。
下面是我决定 运行 的一个简单实验。 test1()
是一个为全局数组 g
分配内存的函数,然后有一个 for 循环更新这个数组的所有元素。最后,它释放为 g
分配的内存。 test2
中发生了完全相同的事情,但现在我们使用本地数组而不是称为 l
.
#include <iostream>
#define n 1000000000
using namespace std;
int *g;
void test1(){
g = new int[n];
int i;
for(i=0;i<n;i++) g[i] = i;
delete[] g;
}
void test2(){
int *l = new int[n];
int i;
for(i=0;i<n;i++) l[i] = i;
delete[] l;
}
int main()
{
timespec cpu_time_s;
timespec cpu_time_e;
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &cpu_time_s);
test1();
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &cpu_time_e);
long long int ns = (cpu_time_e.tv_sec * 1000000000
+ cpu_time_e.tv_nsec - (cpu_time_s.tv_sec * 1000000000
+ cpu_time_s.tv_nsec));
cout<<ns<<" ns"<<endl;
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &cpu_time_s);
test2();
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &cpu_time_e);
ns = (cpu_time_e.tv_sec * 1000000000
+ cpu_time_e.tv_nsec - (cpu_time_s.tv_sec * 1000000000
+ cpu_time_s.tv_nsec));
cout<<ns<<" ns"<<endl;
return 0;
}
我 运行 进行了一些测试,我绕过 2709066246 ns
用于 test1()
和 2459390299 ns
用于 test2
由于第一个测试 运行 较慢,无论它是哪个,那么 OS 可能需要更长的时间来分配 4GB,然后在第一次通过时将其映射到可写内存。在极端情况下,它可能需要将其他内容保存到交换文件中以使 RAM 第一次可用,但第二次则不然。在轻负载下,您希望这不是必需的,但 OS 仍然可能需要做一些事情,例如删除磁盘缓存并重新使用它之前占用的内存。这是快速但不是即时的。
在第二遍中,您刚刚释放了 4GB 内存,所有这些内存最近都已映射,因此要做的工作可能会少一些。释放的 4GB 甚至可能仍然与您的进程相关联,因此 OS 根本没有任何关系,尽管您不希望有那么大的块。
您可以通过 运行ning test2
两次进行检查,看看第一次是否始终较慢。如果大体上是这样的话,那就是分配和写入数组,而不是你如何做的细节。
如果全局无论顺序如何都变慢,那么我能想到的最明显的可能原因是编译器可能不会将 g
保存在寄存器中,而是可能会重复将其从寄存器中加载出来全局区域。您可以通过查看它发出的代码来检查。
这个现象和我在this post的回答中描述的一样。
TL;DR 当您访问之前分配的内存时,OS 需要分配页面。当您释放并重新分配时,您最终会进入相同的内存区域(或几乎相同),并且页面已经由先前的访问分配。
请注意,页面分配完全独立于内存分配。分配 4GB 不会使用 4GB 或物理内存,直到您访问它。
要对此进行测试,请尝试在第一个循环中只访问一半的内存,在第二个循环中尝试访问另一半。结果应该是相同的。您也可以尝试只访问奇数页,然后访问偶数页(页面长 4096 字节,1024 整数)。