我有以下示例代码分配1D数组:
#define C 3
int main() {
int *a;
long long N = 1000000000, i;
a = (int*)malloc(sizeof(int) * N * C);
for (i = 0; i < N * C; i++)
a[i] = i / 2;
printf("%d\n", a[N*C - 1]);
return 0;
}
上面的代码在内存中占用了12 GB的数据
请注意sizeof(int) == 4
和sizeof(int*) == 8
。
现在,如果我使用以下代码实现动态2D数组:
#define C 3
int main() {
int **a;
long long N = 1000000000, i;
a = (int**)malloc(sizeof(int*) * N);
for (i = 0; i < N; i++)
a[i] = (int*)malloc(sizeof(int) * C);
for (i = 0; i < N; i++)
for (j = 0; j < C; j++)
a[i][j] = i;
printf("%d\n", a[N-1][C-1]);
return 0;
}
上面的代码奇怪地占用了大约38 GB的内存(虽然它应该占用12GB + 8GB(指针数组)= 20 GB。
奇怪的是,在第二个示例代码中,如果我将C
的值增加到4,5,6,消耗的内存完全相同(38 GB),而对于C=7
和C=8
消耗的内存为54 GB,而C=16
消耗的内存为86 GB。这不符合我能想到的任何数学。任何人都可以帮我解决这个问题吗?
答案 0 :(得分:4)
你的2D数组实际上是一个指向3 int
数组的指针数组。所需的额外空间来自3 int
的所有小数组中的开销:每个数组使用12个字节加上可能的4到12个字节的填充和估计的至少8个字节的开销。总大小可达32GB + 8GB = 40GB,由top
报告为38GiB。根据{{1}}的实际实现,开销可以从更少到更多。 malloc
返回的内存保证适合最大对齐要求。在intel 64位架构上,这意味着16个字节。如果分配器非常保守,每个小数组占用16个字节,如果不是,它可能占用32个字节或更多。
您可以通过这种方式分配一个真正的2D数组,而不需要任何开销:
malloc
编辑试图解释您的尺寸观察结果:
#define C 3
int main(void) {
long long N = 1000000000, i;
int (*a)[C] = malloc(sizeof(*a) * N);
for (i = 0; i < N; i++) {
for (j = 0; j < C; j++)
a[i][j] = i;
}
printf("%d\n", a[N-1][C-1]);
return 0;
}
C=3 to C=6 -> 38GiB
C=7, C=8 -> 54 GiB
C=16 -> 86 GiB
以GiB显示内存大小,单位为1024x1024x1024字节,比GB小约8%。
指针数组正好使用8GB(80亿字节),开销可以忽略不计。
下表总结了指针数组与top
分配给int
的大小malloc
的各个数组之间的细分:
C
我的解释是:
由 C used actual arrays pointers total binary
--- ---- ------ ------ -------- ----- ------
3 12 32 32GB 8GB 40GB 37.3GiB
4 16 32 32GB 8GB 40GB 37.3GiB
5 20 32 32GB 8GB 40GB 37,3GiB
6 24 32 32GB 8GB 40GB 37.3GiB
7 28 48 48GB 8GB 56GB 52.2GiB
8 32 48 48GB 8GB 56GB 52.2GiB
16 64 80 80GB 8GB 88GB 82.0GiB
分配的小内存块被四舍五入为16加8的大小倍数,加上竞技场簿记信息的额外8字节开销。 malloc
返回的地址在16字节边界上对齐,8字节开销位于块之前,块大小是16减8字节的倍数,以允许下一个块对齐。
这可以解释C = 7时从32跳到48字节的跳转。
你应该验证C = 11有类似的跳跃。
您还可以测量C = 2的情况,以查看块大小为
的最小块大小是8还是24字节