【问题标题】:Fast memory allocation of big memory chunk大内存块的快速内存分配
【发布时间】: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 的结果相当相似,但实际上分配 + 写入操作的结果更差。

您知道分配(和初始化)大块内存的更快方法吗?作为记录,我已经测试了mallocnew float/double 数组、_callocoperator newmmap 和 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 具有此功能,称为 HyperZnVidia 可能有类似的东西。
  • 如何分配大页面而不是依赖透明大页面?

标签: c++ c linux performance memory-management


【解决方案1】:

当您“建议大页面”时,这并不能保证您会获得大页面。这是内核的最大努力。此外,如何配置透明大页面(THP):/sys/kernel/mm/transparent_hugepage/enabled的内容?

THP 可能会引入开销,因为名为 khugepaged 的底层“垃圾收集器”内核守护进程负责合并物理页面以生成大页面。关于 THP 的性能评估/问题存在一些有趣的论文:

为确保所有措施是否基于大页面,最好禁用 THP 并从基准程序中显式分配大页面,例如 here 中的说明。

【讨论】:

  • 谢谢,我会在周末好好阅读这些内容。不幸的是,如果我需要更改内核以支持 HP(并关闭 THP),那么这样的解决方案对我来说是不可接受的 :(.
  • 可以在每次测试前通过将“never”写入“/sys/kernel/mm/transparent_hugepage/enabled”来禁用 THP。对于来自用户空间的 Huge Pages,检查内核是否使用 CONFIG_HUGETLBFS 编译。如果是,那么您就可以从您的程序中使用 HP。
猜你喜欢
  • 1970-01-01
  • 2013-07-27
  • 2011-01-10
  • 2016-02-13
  • 1970-01-01
  • 2010-10-10
  • 1970-01-01
  • 2012-12-26
  • 1970-01-01
相关资源
最近更新 更多