C++内存分配机制性能比较(tcmalloc vs. jemalloc)

31

我有一个应用程序需要分配大量内存,考虑使用比malloc更好的内存分配机制。

我的主要选择是:jemalloc和tcmalloc。是否有使用它们中的任何一个带来的好处?

http://locklessinc.com/benchmarks.shtml 中有一个很好的比较(包括作者专有的机制--lockless),并提到了它们各自的优缺点。

考虑到这两种机制都是活跃的,并不断改进。有没有人对这两种机制的相对性能有任何见解或经验?


5
你为什么在C++中使用malloc - John Dibling
2
@JohnDibling 性能 - Shayan Pooya
4
下一个自然的问题应该是,为什么你要使用C++? - John Dibling
13
我注意到常见的new实现方式都依赖于malloc来获取内存空间。 - Matthieu M.
2
你也可以通过减少分配内存来提高性能。对象池在这里非常有用。虽然编程可能会变得有些棘手,但如果分配方案导致性能问题,那么就应该考虑使用对象池了。 - edA-qa mort-ora-y
显示剩余3条评论
6个回答

47

如果我没记错的话,两个库的主要区别在于多线程项目。

这两个库都试图通过让线程从不同的缓存中选择内存来解除竞争,但它们有不同的策略:

  • jemalloc(Facebook使用)为每个线程维护一个缓存
  • tcmalloc(Google的)维护一组缓存,并且线程会对某个缓存产生“自然”的亲和力,但可能会更改

如果我没记错,这导致了在线程管理方面的重要差异。

  • jemalloc在静态线程下更快,例如使用池
  • tcmalloc在创建/销毁线程时更快

此外,由于jemalloc旋转新缓存以适应新的线程ID,因此突然增加线程数会导致在后续的平静阶段留下大量空缓存的问题。

因此,我建议在一般情况下使用tcmalloc,并将jemalloc保留给非常特定的用途(在应用程序的生命周期中线程数量变化很少的情况下使用)。


15

我最近在考虑在工作中使用tcmalloc。以下是我观察到的情况:

  • 在多线程环境下,对于频繁使用malloc的情况性能大幅提升。我在工作中使用了它,并且性能提高了将近两倍。原因是,在这个工具中有一些线程在关键循环中执行小对象的分配。如果使用glibc时,由于不同线程之间的malloc/free调用存在锁争用,性能会受到影响。

  • 不幸的是,tcmalloc增加了内存占用。我提到的那个工具将消耗两到三倍的内存(最大驻留集大小为度量标准)。这种内存占用增加对我们来说行不通,因为我们实际上正在寻找减少内存占用的方法。

最终,我决定不使用tcmalloc,而是直接优化应用程序代码:这意味着将分配从内部循环中移除以避免malloc/free锁争用。(对于好奇心强的人,使用一种压缩形式而不是使用内存池。)

对你而言的教训是,你应该仔细测量具有代表性的工作负载的应用程序。如果你能承受额外的内存使用,tcmalloc可能非常适合你。如果不能,tcmalloc仍然有用之处,可以看到避免跨线程频繁调用内存分配所带来的好处。


纯猜测,但这可能是由于库过度分配而使分配快速,如果有一个“shrink”函数可用于所有这些微小的分配之后,它应该有助于减少内存。 - Iyad Ahmed

5

请注意,根据“nedmalloc”主页的说法,现代操作系统的分配器现在实际上非常快:

“Windows 7、Linux 3.x、FreeBSD 8、Mac OS X 10.6都包含最先进的分配器,在实际应用中,没有第三方分配器可能会显著提高它们的性能。”

http://www.nedprod.com/programs/portable/nedmalloc

所以你可能只需要建议用户升级或类似的操作就可以了 :)


1
这也是我的观察结果,CRT开发人员的观察结果,因为标准的 malloc 在win32上只是 VirtualAlloc 的包装器,在linux上则是 mmap - v.oddou
1
@v.oddou:所有的malloc实现最终都会调用mmap,因为这是从系统获取内存的唯一方式(几乎如此)。mmap的问题在于它无论如何都很慢(它是一个涉及上下文切换的系统调用),并且它只能以页面粒度(4K或更多)分配内存。malloc实现的目标是使用mmap从系统获取大块内存,然后在用户进程中从这些内存区域中分配较小的对象。 - Yakov Galka
1
@rogerdpack:注意 jemalloc ** 是 ** FreeBSD 的分配器。 - Yakov Galka
@YakovGalka 如果没有进行明确的研究以确保,我会发出不确定性的免责声明。但根据我的当前知识,您的信息是错误或过时的。一些旧的malloc使用srbk从系统获取块。对于大型独立块,mmap是后备方案。今天它被直接称为,而不是“在某个时候”,malloc实现现在是空的。它们只是简单地用没有装饰的mmap包装。此外,系统调用具有快速陷阱。 - v.oddou
1
@v.oddou 我真诚地建议您查看任何这些“空现代malloc实现”的源代码,并弄清楚为什么包装“mmap”需要成千上万行代码。 - Yakov Galka
显示剩余3条评论

2
您也可以考虑使用Boehm保守垃圾回收器。基本上,您需要将源代码中的每个malloc替换为GC_malloc(等等...),并且不需要再调用free。 Boehm的GC不会比malloc更快地分配内存(大约相同,或者可能慢30%),但它有自动处理无用内存区域的优点,这可能会改善您的程序(并且肯定会简化编码,因为您不再关心释放)。而且Boehm的GC还可以作为C++分配器使用
如果你真的认为malloc太慢了(但你应该进行基准测试;大多数malloc只需要不到微秒的时间),并且如果你充分理解了程序的分配行为,你可以用你自己的特殊分配器替换一些malloc(例如使用mmap从内核中获取大块内存,并自己管理内存)。但我相信这样做很痛苦。在C++中,你有allocator概念和std::allocator_traits,大多数标准容器模板都接受这样的分配器(也可以参见std::allocator),例如作为std::vector的可选第二个模板参数等等...
如其他人所建议的那样,如果您认为malloc是瓶颈,您可以分块(或使用arena)分配数据,或者只需在数组中分配。
有时,实现一个专门的拷贝垃圾回收器(针对某些数据)可能会有所帮助。考虑可能使用MPS
但不要忘记,过早优化是万恶之源,请对您的应用程序进行基准测试和性能分析,以了解时间到底花在哪里。

我认为迁移到垃圾收集器并不像只是将malloc更改为gc_malloc那么简单。您还需要将指针的类型更改为某种不透明句柄类型。 - v.oddou
1
不使用Boehm的GC。它的“gc_malloc”旨在替代“malloc”,并且还提供了“void *”。这是一种保守的GC。 - Basile Starynkevitch

1

谢谢。我会阅读它。但是正如我所说,我认为现在它已经过时了。 - Shayan Pooya
又一个线程 https://www.reddit.com/r/programming/comments/7o8d9/tcmalloca_faster_malloc_than_glibcs_open_sourced/ - Scott Stensland

1

您的帖子没有提到线程,但在考虑混合使用C和C++分配方法之前,我建议先研究一下内存池的概念。BOOST有一个很好的内存池。


谢谢。首先,这看起来不错。我会看一下的。其次,我正在进行配置文件和优化阶段,并优化瓶颈(这里是内存分配)。第三,混合使用C/C++分配方法是否有问题?(除了使代码变得混乱/非标准之外) - Shayan Pooya

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接