测试内存分配器:ptmalloc2 vs tcmalloc vs hoard vs jemalloc,同时尝试模拟真实世界的负载

本文主要是介绍测试内存分配器:ptmalloc2 vs tcmalloc vs hoard vs jemalloc,同时尝试模拟真实世界的负载,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

原文

当我们为参与者开发自己的分配器时,我们需要对其进行测试,更重要的是,需要将其与现有的分配器进行基准测试。显然,虽然有相当多的分配器测试程序,但这些测试中的大多数并不能代表真实世界的负载,因此,只能提供关于真实程序中分配器性能的非常粗略的想法。
例如,相当流行的t-test1.c(参见,例如,t-test1.c)经常被用作单元测试,因此它必须测试诸如calloc()realloc() 之类的东西;另一方面,这些功能在现代C++程序中很少使用,因此对它们进行基准测试实际上会对测试的真实世界条件(至少对于C++)跑偏。此外,均匀随机分布(分配的空间大小和创建/销毁项目的相对频率)虽然最容易模拟,但不能代表真实世界的情况(事实上,已知在大块内存上均匀随机分布是有效禁用缓存的最佳方式,但幸运的是,这在现实世界中很少发生。)最后但并非最不重要的一点是,只测试分配/取消分配,而不访问分配的内存,这是不现实的。

目标和要求

因此,我们决定开发自己的测试,尝试解决上面列出的所有问题。我们的目标是开发一个分配器的测试程序。一方面它将尽可能接近真实世界的C++程序,但是另一方面,它将允许测量存在的性能差异(没有退化成“所有分配器性能都是一样的,因为我们的测试在非分配代码中花费了太多的时间”)。当尝试进一步深入实际时,它产生了以下一组要求:

  • 我们希望我们的测试只使用new/delete(或malloc/free)。其他的东西在C++中很少使用,对它们做基准测试与“接近真实世界的C++程序”的目标不太一致。
    • 或者从另一个角度来看也是一样:如果你的程序真的需要很多realloc()调用,那么要为你的应用找到最佳的分配器,你需要一个不同的基准——很可能是一个不同的分配器。
  • 我们希望我们的测试使用一些真实场景的统计分布——既用于分配条目的大小的分布,也用于“分配条目的使用周期”的分布。
    • 我们确实意识到这必然是一个非常粗略的近似,但对于绝大多数程序来说,任何诚实的尝试都会比使用耗尽缓存(cache-killing)的一致分布更好。
  • 我们希望我们的测试能够访问所有分配的内存:我们应该至少写一次分配的内存,并且至少读回一次。
    • 我们承认,可以有不止一次的写/读操作,但我们认为至少一次是绝对最低限度的,目的是至少接近现实场景。

基本思想

从30000英尺的高度看,我们测试的一个非常基本的想法与t-test1.c非常相似:

  • 我们有一堆“槽(slot)”,每个“槽”代表一个潜在的分配项目。
  • 在每次迭代中,我们都会在槽上随机选择:
    • 如果它是空的(empty)–我们正在分配一个项目并将其放入槽中
    • 如果是释放的(free),我们将释放该项目,并将槽清空
  • 如果有多个线程,则每个线程都沿着上面的线独立工作。

差不多就是这样。

使分布更现实

在这个模型中,有两种不同的分布:(a)分配项目的大小分布,和(b)槽(slot)内随机选择的分布(这转化为分配项目的相对寿命分布)。
对于分配大小的分布,我们认为

p ~ 1/size

(其中“~”表示“成比例”且 size <= max_size)足以表示某种或多或少现实的场景。换句话说,分配 30字节的概率是分配 60字节概率的两倍,而分配60字节的概率又是分配 120字节概率的两倍,依此类推。在实践中,为了确保计算足够快,我们必须使用分段线性函数的近似值,但我们希望不要偏离1/size的目标太多。

对于选择用于操纵的槽(slot)分布,我们进行了大量实验,以确保一方面,访问频率较低的槽的概率快速降低(特别是,大大快于1/x,这太慢了,无法代表我们职业生涯中看到的真实世界分布),但另一方面,它们不会太快地达到虚拟零。最后,我们不得不求助于所谓的帕累托分布(在其经典版本中,“20%的人喝80%的啤酒”规则)。除了对于许多不同的非计算机相关的真实世界分布是一个非常好的近似值外,帕累托分布确实提供了我们认为相当接近真实世界的访问频率近似值;另外,我们观察到,在某些参数(槽数量和max_size)下,它与访问L1的次数 ~= 访问L2的次数 ~= 访问L3的次数 ~= 访问主RAM的次数没有太大的偏差,我们认为这是一个好迹象。

与分配的大小一样,我们确实需要使用分段线性函数,以便在测试时使事情变得合理快速,但我们确实希望这不是太糟糕。

访问分配的内存

如上所述,我们确实希望访问分配的内存;在我们的测试中,我们在分配之后编写整个块(使用memset())——并在释放之前读取它。这是我们在现实世界中所能想象的最低访问量(嘿,如果我们真的想分配,也许我们真的想在那里写点什么?)。

补偿机制

我们所做的所有工作(特别是关于分布区间)都在CPU方面取得了巨大的成功(尽管我们尽可能地避免了间接操作,但寄存器内(in-register)计算仍然非常重要。此外,我们确实想减去memset()和内存读取的理想情况成本,以便更清楚地看到不同分配器之间的差异。
为此,我们总是运行两个测试:(a)被测分配器,(b)虚拟“void”分配器(它完全不做任何事情),然后我们从(a)中花费的时间中减去(b)中花费的时间,以获得被测分配器的性能,而无需测试本身的成本。

测试

测试程序。我们运行我们的测试应用程序,按照上面讨论的思路设计,并在Github.alloc test上提供。
系统。我们在一个典型的2槽高的服务器上运行我们的所有测试,有两个E5645,每个E5645都有6个内核,具有超线程(Hyperthreading)(~=“2路SMT”),12M的三级缓存,能够以2.4GHz(涡轮模式下为2.67Ghz)的频率运行。这个主机有32G的RAM。OS:Debian 9“Stretch”(截至本文撰写之时,它是当前Debian“稳定的”版本)。非常简单:这是一个非常典型的(即使有点过时)现实世界的“主力”服务器。
正在测试的分配器。我们的第一组测试是在4个流行的Linux分配器上运行的:

  • 内置glibc分配器(一般认为它是经过大量修改的ptmalloc2)
  • [hoard]. 下载https://github.com/emeryberger/Hoard并编译
  • tcmalloc. apt-get install google-perftools
  • jemalloc. apt-get install libjemalloc-dev, apt-get install libjemalloc1

对于所有的分配器,我们没有使用LD_PRELOAD;相反,我们(对于除内置分配器外的所有分配器):

  • 编译我们的测试应用程序时使用了-fno-builtin-malloc-fno-builtin-calloc-fno-builtin-realloc-fno-builtin-free标志(如perftools.README中所建议的那样);在没有这些标志的情况下,我们至少在tcmalloc中遇到了问题。
  • 将我们的测试应用程序链接到-lhoard-ltcmalloc-ljemalloc

测试设置。为了运行我们的测试,我们必须选择某些参数,最重要的是——我们要分配的RAM数量。为了使我们的测试尽可能接近真实世界,我们决定使用大约1G的分配RAM进行测试;事实上,由于我们尽可能使用2的幂,我们最终得到了1.3G的分配RAM。需要注意的是,在处理多个线程时,我们决定在所有线程上拆分相同数量的RAM(这很重要,因为我们确实希望排除由于与CPU缓存相比使用的RAM大小不同而产生的差异)。
另一个参数(因测试而异)是线程数。由于我们的机器支持24个线程(2个CPU6个内核/CPU2个线程/内核=24个线程),我们使用1-23个线程运行测试(保留最后一个硬件线程以满足操作系统自身的需要,因此中断和后台活动不会对我们的测试产生太大影响)。

测量什么。对于第一组测试,我们只测试了两个参数:

  • 测试运行时花费的时间。然后,我们使用这些数据来推导“每个malloc()/free() 对的CPU时钟周期”指标。
  • 程序的内存使用情况(以RSS=“Resident Set Size”的最大值来衡量);在没有交换的情况下,它是程序从操作系统分配的RAM量的一个合理的良好指标。我们使用这些数据来计算“内存开销”(作为“从操作系统分配的RAM数量”与“应用程序级别通过malloc()请求的RAM数量”的比率)。

测试结果

现在,根据测试条件的描述(如果有什么遗漏,请给我们一个提示,我们很乐意进一步阐述),我们可以进入本文最有趣的部分——测试结果。
以下是我们得到的数据:
malloc test result
这或多或少与之前的测试和我们的预期一致,但是:

  • 由于我们试图模拟真实世界的行为,现代最先进的 Malloc 库之间的性能差异并不像有时所说的那样明显。特别是,tcmalloc 和 ptmalloc2 之间观察到的最大性能差异是,tcmalloc 的性能比 ptmalloc2 好1.7倍,尽管它只发生在一个线程上,并且在线程数量较多的情况下,ptmalloc2 的性能比 tcmalloc 好(高达1.2倍)
  • 值得注意的是,那些使用较少线程( tcmalloc 和 hoard)表现较好的 malloc,在大约12个线程时开始表现逐渐较差,到了大约18-20个线程时,表现比 ptmalloc2 和 jemalloc 差。
    • 这是否与超线程(SMT)有关(在本框中,12个线程是SMT发挥作用的一个点),还是与其他任何东西有关,都有待进一步研究。特别值得一提的是,如果我们在不同的进程中运行我们的测试线程,那么找出这种影响是否持续是很有趣的。

malloc overhead
我们测量的第二个参数是“内存开销”。这对于整体性能非常重要,因为我们产生的RAM开销越少,缓存的效率就越高。为了粗略估计这种影响,我们可以考虑分配程序的开销为2倍,实际上浪费了每一级缓存的一半,因此,相对于12M的三级缓存,使用这样一个开销为2倍的分配程序,我们将认为“好像”只有6M。
正如我们所看到的,从这个角度来看,jemalloc 是一个明显的赢家,ptmalloc2 是第二好的,而 tcmalloc 在任何线程数 > 1 的情况下都远远落后。

结论

现在,是时候从这些数据中得出我们主观的初步结论了:

  • 在不指定具体应用程序的情况下,我们测试的现代 malloc 库彼此之间性能差距不大,更重要的是,在某些条件下,它们中的每一个都可以超越另一个。换句话说:如果你确实想通过改变 malloc 获得性能,请确保用你自己的应用程序测试它们。
  • 如果我们不知道确切的应用程序(比如为OS发行版选择默认malloc),那么我们的建议如下:
    • 考虑到奇怪的内存开销,我们应该远离 hoard
    • tcmalloc 和 ptmalloc2 非常相似(在这一点上,tcmalloc 对于台式机和 ptmalloc2——对于服务器来说似乎有优势,但现在做出任何明确的结论还为时过早)
    • 但在我们的拙见中,到目前为止,总冠军是 jemalloc。它的低内存开销有望改善缓存的处理方式,对于“普通应用程序”,我们希望它的性能至少不会比其他应用程序差。你的情况可能会有所不同,上面的测试不包括电池消耗,请参阅上面的“确保使用您自己的应用程序测试电池”部分。

这篇关于测试内存分配器:ptmalloc2 vs tcmalloc vs hoard vs jemalloc,同时尝试模拟真实世界的负载的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/723669

相关文章

C++高效内存池实现减少动态分配开销的解决方案

《C++高效内存池实现减少动态分配开销的解决方案》C++动态内存分配存在系统调用开销、碎片化和锁竞争等性能问题,内存池通过预分配、分块管理和缓存复用解决这些问题,下面就来了解一下... 目录一、C++内存分配的性能挑战二、内存池技术的核心原理三、主流内存池实现:TCMalloc与Jemalloc1. TCM

Redis过期删除机制与内存淘汰策略的解析指南

《Redis过期删除机制与内存淘汰策略的解析指南》在使用Redis构建缓存系统时,很多开发者只设置了EXPIRE但却忽略了背后Redis的过期删除机制与内存淘汰策略,下面小编就来和大家详细介绍一下... 目录1、简述2、Redis http://www.chinasem.cn的过期删除策略(Key Expir

VS配置好Qt环境之后但无法打开ui界面的问题解决

《VS配置好Qt环境之后但无法打开ui界面的问题解决》本文主要介绍了VS配置好Qt环境之后但无法打开ui界面的问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要... 目UKeLvb录找到Qt安装目录中designer.UKeLvBexe的路径找到vs中的解决方案资源

Python使用pynput模拟实现键盘自动输入工具

《Python使用pynput模拟实现键盘自动输入工具》在日常办公和软件开发中,我们经常需要处理大量重复的文本输入工作,所以本文就来和大家介绍一款使用Python的PyQt5库结合pynput键盘控制... 目录概述:当自动化遇上可视化功能全景图核心功能矩阵技术栈深度效果展示使用教程四步操作指南核心代码解析

python多线程并发测试过程

《python多线程并发测试过程》:本文主要介绍python多线程并发测试过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、并发与并行?二、同步与异步的概念?三、线程与进程的区别?需求1:多线程执行不同任务需求2:多线程执行相同任务总结一、并发与并行?1、

Python模拟串口通信的示例详解

《Python模拟串口通信的示例详解》pySerial是Python中用于操作串口的第三方模块,它支持Windows、Linux、OSX、BSD等多个平台,下面我们就来看看Python如何使用pySe... 目录1.win 下载虚www.chinasem.cn拟串口2、确定串口号3、配置串口4、串口通信示例5

Java内存区域与内存溢出异常的详细探讨

《Java内存区域与内存溢出异常的详细探讨》:本文主要介绍Java内存区域与内存溢出异常的相关资料,分析异常原因并提供解决策略,如参数调整、代码优化等,帮助开发者排查内存问题,需要的朋友可以参考下... 目录一、引言二、Java 运行时数据区域(一)程序计数器(二)Java 虚拟机栈(三)本地方法栈(四)J

Nginx使用Keepalived部署web集群(高可用高性能负载均衡)实战案例

《Nginx使用Keepalived部署web集群(高可用高性能负载均衡)实战案例》本文介绍Nginx+Keepalived实现Web集群高可用负载均衡的部署与测试,涵盖架构设计、环境配置、健康检查、... 目录前言一、架构设计二、环境准备三、案例部署配置 前端 Keepalived配置 前端 Nginx

java变量内存中存储的使用方式

《java变量内存中存储的使用方式》:本文主要介绍java变量内存中存储的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、介绍2、变量的定义3、 变量的类型4、 变量的作用域5、 内存中的存储方式总结1、介绍在 Java 中,变量是用于存储程序中数据

nginx负载均衡及详细配置方法

《nginx负载均衡及详细配置方法》Nginx作为一种高效的Web服务器和反向代理服务器,广泛应用于网站的负载均衡中,:本文主要介绍nginx负载均衡及详细配置,需要的朋友可以参考下... 目录一、 nginx负载均衡策略1.1 基本负载均衡策略1.2 第三方策略1.3 策略对比二、 nginx配置2.1