我有一个巨大的数组,我已经分配给堆,因为它会导致错误,如果留在堆栈上。现在,据我所知,有两种方法可以将它发送到堆中。
# 1
int i;
int x=10000, int y=10000;
double** array=(double**)malloc(sizeof(double*)*x);
if (image) {
for (i=0; i<x; i++) {
array[i] =(double*)malloc(sizeof(double)*y);
}
}
# 2
double *array[x][y]=(double*)malloc(sizeof(double)*x*y);
现在我想知道哪种方法是上级?我认为#1要求在堆中有x个长度为y的块,它们不需要彼此相邻。其中#2是在堆中请求一个y*x块。#2是请求一个巨大的x*y块,而#1是请求不需要连接的块。因此,既然可以拆分,那么第1号就可以成为监督者。假设堆无法处理长度为x*y的巨大条带,可以处理x数量的y条带数据。
首先这是真的吗?这两种方法我都遗漏了什么吗?我的论点实际吗?或者,即使是真的,也不太可能出现?有更高级的方法吗?
您是正确的,第一个方法可能更灵活,因为它不需要找到总大小的连续空闲内存,而第二个方法则需要。这样做的一个可能的不利影响是,如果分配的slab不是连续的,那么这本身可能会导致更多的堆碎片。每个板之间会有空间区域,未来的分配需要在其中找到空间。
然而,第二种选择可以利用空间和时间局部性。基本上,由于更多的数据彼此紧挨着,您需要的数据在CPU缓存中的可能性就会增加,因此,在此内存上操作将会快得多。
这取决于您使用的内存分配器和x和y的值。
内存分配器通常在用户空间中缓存小的内存块,在用户空间中处理小的分配,而将较大的分配请求通过mmap
传递给内核。
大多数内存分配器是这样工作的:
void* malloc(size_t size)
if (size > THRESHOLD) {
return large_alloc(size) // forward to mmap
}
retry:
void* ret = small_alloc(size); // handled in user space
if (ret == NULL) { // no small blocks left
enlarge_heap(); // map more memory from kernel
goto retry;
}
return ret;
}
在你的例子中y == 10000,所以你要求一个80000字节的内存块。在glibc的默认内存分配器中,mmap阈值为128kB。因此,如果分配器已经缓存了足够的内存,则倾向于在用户空间中处理此请求。但是#2将调用mmap调用,因为它大于128kB。
但是,在您的示例中x == 10000。因此,您讨论的是单个mmap系统调用调用和用户空间中的10000个分配。相信我。#2更快:
在现代x86机器上,高度优化的分配器实现中的分配总是需要超过70个周期。10000个分配将消耗超过700000个周期。但是典型的mmap调用延迟应该不超过100000个周期。所以第二条更好。对于其他的分配器,比如TCMalloc,它有一点不同。TCMalloc没有这样的阈值,并且总是尝试在其Span
结构中处理用户空间中的大分配请求。所以#2肯定要好得多,因为它只需要一次分配。
我同意#1更灵活,因为#2要求分配器找到一个大的连续内存块。但是请记住,它只在虚拟内存中是连续的,当您第一次接触它时,物理页是按需映射的。这意味着它不需要在物理内存中连续。并且通常很容易在虚拟内存中找到8 * 10000 * 10000字节的连续内存区域。