C++ 大内存块的快速内存分配

C++ 大内存块的快速内存分配,c++,c,linux,performance,memory-management,C++,C,Linux,Performance,Memory Management,我一直在测量连续大块内存的不同C/C++分配(和初始化)技术的性能。 为此,我尝试分配(并写入)100个随机选择的大小,使用均匀分布,范围为20到4096 MB,并使用std::chrono high_resolution_clock测量时间。 每个测量都是通过一个程序的单独执行来完成的,也就是说,不应该有内存重用(至少在进程内) madvise ON是指使用madvu HUGEPAGE标志调用madvise,即启用透明的大页面(在我的系统中为2MB) 使用一个时钟速度为2400 MT/s、数据

我一直在测量连续大块内存的不同C/C++分配(和初始化)技术的性能。 为此,我尝试分配(并写入)100个随机选择的大小,使用均匀分布,范围为20到4096 MB,并使用
std::chrono high_resolution_clock
测量时间。 每个测量都是通过一个程序的单独执行来完成的,也就是说,不应该有内存重用(至少在进程内)

madvise ON是指使用
madvu HUGEPAGE
标志调用
madvise
,即启用透明的大页面(在我的系统中为2MB)

使用一个时钟速度为2400 MT/s、数据宽度为64位的16 GB DDR4模块,我得到了17.8 GB/s的理论最大速度

在Ubuntu18.04.05 LTS(4.15.0-118-generic)上,已经分配的内存块的
memset
接近理论极限,但是页面对齐的分配+
memset
稍慢一些,正如预期的那样
new
速度非常慢,可能是由于其内部开销(以GB/s为单位的值):

方法制造中值标准
memset madvise关闭17.3 0.32
11.4 0.21上的页面对齐+memset madvise
mmap+memset madvise于11.3 0.23
2006年3月2日新[]号()madvise
使用两个模块时,由于双通道(至少在写操作中是这样),我预计性能将接近两倍(比如35 GB/s):

方法制造中值标准
memset madvise关闭28.0 0.23
mmap+memset madvise于14.5 0.18
14.4 0.17上的页面对齐+memset madvise
如您所见,
memset()
仅达到理论速度的80%。内存分配+写入速度仅增加3 GB/s,仅达到内存理论速度的40%

为了确保我没有把操作系统搞砸(我已经用了几年了),我安装了新的Ubuntu 20.04(双启动)并重复了这个实验。最快的行动是:

方法制造中值标准
memset madvise OFF 29.1 0.86
10.5 0.27上的页面对齐+memset madvise
mmap+memset madvise于10.5 0.31
如您所见,
memset
的结果相当相似,但实际上分配+写入操作的结果更糟

您是否知道一种更快的分配(和初始化)大块内存的方法?为了记录在案,我测试了
malloc
new
float/double数组、
\u calloc
operator new
mmap
和page\u的组合,以及
memset
和写入循环,以及
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);});
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(“mmap+memset”+使用madvise\u str+csepator+bytes\u str,[&]{
p=mmap(0,字节,保护读写,映射私有,映射匿名,-1,0);
if(use_madvise)madvise(p,bytes,MADV_HUGEPAGE);
memset(p,std::numeric_limits::max(),bytes);
});
新[]()

reportTimeUs(“new[]()”+使用_madvise_str+csepator+bytes_str,[&]{
p=新的双精度[计数双精度]();
if(use_madvise)madvise(p,bytes,MADV_HUGEPAGE);
});
当你“建议大页面”时,这并不保证你会得到大页面。这是内核的最大努力。此外,如何配置透明巨大页面(THP):启用/sys/kernel/mm/Transparent_hugepage/的内容

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

  • )

为了确保所有的度量都基于巨大的页面,最好禁用THP并从基准程序中显式分配巨大的页面,如所述。

双通道内存模式不会使性能提高一倍。(例如,您增加了延迟时间和一些开销。)仅此而已。了解硬件规格。PS这是一个与硬件相关的问题,与编程无关。@paladin这个问题对我来说似乎是个话题。内存分配的详细信息,在C/C++中甚至更多,听起来非常特定于编程。我完全同意评论的第一部分。我不完全理解你的实验:“memset”实验是测量memset不同大小块的性能。。?“mmap+memset”包括测量中每个memset的mmap?PS一些硬件架构具有一些特殊能力,可以将整个内存区域标记为设置为零。虽然这对于8086兼容的CPU来说非常少见,但现代AMD GPU有这个功能,称为HyperZ。nVidia可能也有类似的功能。如何分配大页面,而不是依赖透明的大页面?谢谢,我会在周末正确阅读这些内容。不幸的是,如果我需要更改内核以支持HP(并关闭THP),那么这样的解决方案对我来说是不可接受的:(.THP可以在每次测试之前通过将“从不”写入“/sys/kernel/mm/transparent\u hugepage/enabled”来禁用)。对于来自用户空间的巨大页面,请检查内核是否使用CONFIG_HUGETLBFS编译。如果是,则您可以从程序中使用HP。
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);
});