首先显而易见的是:对于特定用例,需要进行分析。然而,这个问题要求一个粗略的大致数量级估计。当我们不确定是否应该考虑一个问题时,我们会这样做。当我的数据被发送到阿姆斯特丹时,我需要担心它是否在缓存中吗?从问题的图片来看,答案是明确的“否”。是的,这可能是一个问题,但只有当你犯了很大的错误时才会出现。我们假设这种情况被排除了,而是以概率上的一般性来讨论问题。
或许有点讽刺的是,这个问题出现在我正在处理非常关注细节的程序中,其中几个百分点的性能差异就相当于数百万个CPU小时。分析表明malloc不是问题所在,但在完全排除之前,我想进行理性检查:malloc是否可能成为瓶颈?
正如在此问题的早期版本中反复建议的那样,不同的环境之间存在很大的差异。我尝试了各种机器(Intel:i7 8700K、i5 5670、一些笔记本电脑中的早期移动版i7;AMD:Ryzen 4300G、Ryzen 3900X)、各种操作系统(Windows 10、Debian、Ubuntu)和编译器(GCC、Clang-14、Cygwin-G++、MSVC;没有调试版本)。
我使用这个工具来了解特征,只使用1个线程:
#include <stddef.h>
#include <stdlib.h>
#include <time.h>
#include <stdio.h>
int main(int argc, char* argv[]) {
const size_t allocs = 10;
const size_t repeats = 10000;
printf("chunk\tms\tM1/s\tGB/s\tcheck\n");
for (size_t size = 16; size < 10 * 1000 * 1000; size *= 2) {
float t0 = (float)clock() / CLOCKS_PER_SEC;
size_t check = 0;
for (size_t repeat = 0; repeat < repeats; ++repeat) {
char* ps[allocs];
for (size_t i = 0; i < allocs; i++) {
ps[i] = malloc(size);
if (!ps[i]) {
exit(1);
}
for (size_t touch = 0; touch < size; touch += 512) {
ps[i][touch] = 1;
}
}
for (size_t i = 0; i < allocs; i++) {
check += ps[i][0];
free(ps[i]);
}
}
float dt = (float)clock() / CLOCKS_PER_SEC - t0;
printf ("%d\t%1.5f\t%7.3f\t%7.1f\t%d\n",
size,
dt / allocs / repeats * 1000,
allocs / dt * repeats / 1000 / 1000,
allocs / dt * repeats * size / 1024 / 1024 / 1024,
check);
}
}
方差很大,但是如预期的那样,这些值仍然属于同一范围。下表是代表性的,其他的偏差小于10倍。
chunk ms M1/s GB/s check
16 0.00003 38.052 0.6 100000
32 0.00003 37.736 1.1 100000
64 0.00003 37.651 2.2 100000
128 0.00004 24.931 3.0 100000
256 0.00004 26.991 6.4 100000
512 0.00004 26.427 12.6 100000
1024 0.00004 24.814 23.7 100000
2048 0.00007 15.256 29.1 100000
4096 0.00007 14.633 55.8 100000
8192 0.00008 12.940 98.7 100000
16384 0.00066 1.511 23.1 100000
32768 0.00271 0.369 11.3 100000
65536 0.00707 0.141 8.6 100000
131072 0.01594 0.063 7.7 100000
262144 0.04401 0.023 5.5 100000
524288 0.11226 0.009 4.3 100000
1048576 0.25546 0.004 3.8 100000
2097152 0.52395 0.002 3.7 100000
4194304 0.80179 0.001 4.9 100000
8388608 1.78242 0.001 4.4 100000
以下是cygwin-g++上3900X的测试结果。可以清晰地看到更大的CPU缓存,以及更高的内存吞吐量。
chunk ms M1/s GB/s check
16 0.00004 25.000 0.4 100000
32 0.00005 20.000 0.6 100000
64 0.00004 25.000 1.5 100000
128 0.00004 25.000 3.0 100000
256 0.00004 25.000 6.0 100000
512 0.00005 20.000 9.5 100000
1024 0.00004 25.000 23.8 100000
2048 0.00005 20.000 38.1 100000
4096 0.00005 20.000 76.3 100000
8192 0.00010 10.000 76.3 100000
16384 0.00015 6.667 101.7 100000
32768 0.00077 1.299 39.6 100000
65536 0.00039 2.564 156.5 100000
131072 0.00067 1.493 182.2 100000
262144 0.00093 1.075 262.5 100000
524288 0.02679 0.037 18.2 100000
1048576 0.14183 0.007 6.9 100000
2097152 0.26805 0.004 7.3 100000
4194304 0.51644 0.002 7.6 100000
8388608 1.01604 0.001 7.7 100000
那么问题来了?使用小的块大小,甚至在旧的普通硬件上每秒可以实现>= 1000万次调用。一旦大小超过CPU缓存,即1到100 MB左右,RAM访问将迅速占主导地位(我没有测试malloc而不实际使用块的情况)。根据您malloc的大小,其中一个将是(大致)限制。但是,对于每秒10k个分配,这可能是您可以暂时忽略的事情。
mimalloc
和jemalloc
。 - Warren Weckesser