【发布时间】:2021-03-09 04:24:03
【问题描述】:
我一直在测量不同 C/C++ 分配(和初始化)技术对连续大块内存的性能。
为此,我尝试分配(并写入)100 个随机选择的大小,使用均匀分布和 20 到 4096 MB 的范围,并使用std::chrono high_resolution_clock 测量时间。
每次测量都是通过单独执行程序来完成的,即不应该有内存重用(至少在进程内)。
madvise ON 是指使用MADV_HUGEPAGE 标志调用madvise,即启用透明大页面(我的系统为2MB)。
使用单个 16GB DDR4 模块,时钟速度为 2400 MT/s,数据宽度为 64 位,我的理论最大速度为 17.8 GB/s。
在 Ubuntu 18.04.05 LTS (4.15.0-118-generic) 上,已分配内存块的 memset 接近理论限制,但 page_aligned 分配 + memset 有点慢,正如预期的那样。 new 非常慢,可能是由于其内部开销(值以 GB/s 为单位):
method madvise median std
memset madvise OFF 17.3 0.32
page_aligned+memset madvise ON 11.4 0.21
mmap+memset madvise ON 11.3 0.23
new<double>[]() madvise ON 3.2 0.06
使用两个模块,由于双通道,我预计性能几乎翻倍(比如 35 GB/s),至少对于写入操作而言:
method madvise median std
memset madvise OFF 28.0 0.23
mmap+memset madvise ON 14.5 0.18
page_aligned+memset madvise ON 14.4 0.17
如您所见,memset() 仅达到理论速度的 80%。内存分配+写入速度仅提升3GB/s,仅达到内存理论速度的40%。
为了确保我没有弄乱操作系统中的某些东西(我已经使用了几年了),我安装了新的 Ubuntu 20.04(双启动)并重复了实验。最快的操作是:
method madvise median std
memset madvise OFF 29.1 0.86
page_aligned+memset madvise ON 10.5 0.27
mmap+memset madvise ON 10.5 0.31
如您所见,memset 的结果相当相似,但实际上分配 + 写入操作的结果更差。
您知道分配(和初始化)大块内存的更快方法吗?作为记录,我已经测试了malloc、new float/double 数组、_calloc、operator new、mmap 和 page_aligned 的组合用于分配,memset 和 for 循环用于写入,以及madvise 标志。
完整的基准测试位于此处:https://github.com/DStrelak/memory_allocation_bench。以下是上面提到的方法。
内存集:
void *p = malloc(bytes);
memset(p, std::numeric_limits<unsigned char>::max(), bytes); // write there, so we know it's allocated
reportTimeUs("memset", [&]{memset(p, 0, bytes);});
page_aligned+memset
reportTimeUs("page_aligned" + use_madvise_str + cSeparator + bytes_str, [&]{
p = aligned_alloc(PAGE_SIZE, bytes);
if (use_madvise) madvise(p, bytes, MADV_HUGEPAGE);
});
mmap+memset:
reportTimeUs("mmap+memset" + use_madvise_str + cSeparator + bytes_str, [&]{
p = mmap(0, bytes, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
if (use_madvise) madvise(p, bytes, MADV_HUGEPAGE);
memset(p, std::numeric_limits<unsigned char>::max(), bytes);
});
新[]()
reportTimeUs("new<double>[]()" + use_madvise_str + cSeparator + bytes_str, [&]{
p = new double[count_double]();
if (use_madvise) madvise(p, bytes, MADV_HUGEPAGE);
});
【问题讨论】:
-
双通道内存模式不会让您的性能翻倍。 (例如,您增加了延迟时间和一些开销。)仅此而已。阅读有关硬件规格的信息。 PS这是一个硬件相关的问题,与编程无关。
-
@paladin 这个问题对我来说似乎很重要。内存分配细节,甚至 C/C++ 中的更多细节,听起来非常特定于编程。我绝对同意评论的第一部分。
-
我不完全理解你的实验:“memset”实验正在测量 memsetting 不同大小块的性能..? “mmap+memset”是否包含测量中每个 memset 的 mmap?
-
PS 一些硬件架构有一些特殊的能力可以将整个内存区域标记为 zeroes。虽然这对于 8086 兼容的 CPU 来说非常少见,但现代 AMD GPU 具有此功能,称为 HyperZ。 nVidia 可能有类似的东西。
-
如何分配大页面而不是依赖透明大页面?
标签: c++ c linux performance memory-management