Malloc / new锁和多线程

malloc / new lock and multithreading

本文关键字:多线程 new Malloc      更新时间:2023-10-16

如何在多线程环境中使用new ?

准确地说:我有一段代码,我用40个线程运行。每个线程调用new几次。我注意到性能下降,可能是因为线程锁定在new中(在__lll_lock_wait_parallel__lll_unlock_wait_parallel中花费了大量时间)。我可以使用的new/delete的最佳替代方案是什么?

相关:

    http://dsc.sun.com/solaris/articles/multiproc/multiproc.html
  • malloc线程安全吗?

即使您正在使用new操作符,它也在下面使用malloc来进行分配和释放。在这些情况下,重点应该放在分配器上,而不是用来访问它的API。

TCMalloc 是Google专门为多线程环境下的良好性能而创建的malloc。是google-perf-tools的一部分。

另一个malloc是Hoard。它的目标与TCMalloc大致相同。

我不知道什么是"最好的",但我会尝试一些事情:

  • 减少分配/释放的频率(可能很难)。如果可以提高性能,就浪费内存(但不要泄漏)

  • 滚动我自己的,每线程分配器,并始终使用mmap分配/释放同一线程的实际内存

滚动您自己的原语分配器:

  • 使用mmap从OS获取大块内存
  • 使用数据结构(链表,树等)来跟踪空闲和已使用的块
  • 永远不要释放其他线程分配的数据

我不认为这是微不足道的,但如果做得好,它可以提高性能。到目前为止,最棘手的部分是跟踪分配,防止碎片等。

在本书末尾的" C编程语言"中提供了一个简单的实现(但它使用brk IIRC)。

我认为你应该使用内存池。在项目开始时第一次分配所需的所有内存(如果size为Fix),并让数组从分配的第一个数组中获得所需的内存。

我倾向于在服务器和其他这样的应用程序中使用对象池,这些应用程序的特点是持续频繁地分配和释放大量的少数对象集(在服务器中-套接字,缓冲区和缓冲区收集类)。池是队列,在启动时创建,并推送适当数量的实例。我的服务器- 24000套接字,48000个集合和7个不同大小/计数的缓冲区池的数组。将对象实例从队列中弹出并将其推回到队列中比new/delete要快得多,即使池队列具有锁,因为它是跨线程共享的(锁跨度越小,争用的可能性越小)。我的池对象类(继承了所有套接字等)有一个私有的'myPool'成员(在启动时加载)和一个'release()'方法,没有参数&因此,任何缓冲区都可以轻松且正确地返回到自己的池中。有问题:

1)分配/释放时不调用Ctor和dtor;因此,分配的对象包含了它们最后一次使用时剩下的所有资源。这有时是有用的。可重用的套接字对象),但通常意味着需要注意,例如,布尔值的初始状态,int值等。

2)每个线程的池具有最大的性能改进潜力-不需要锁定,但在每个线程的加载是间歇性的系统中,这可能是一个对象浪费。我似乎从来没有能够摆脱这一点,主要是因为我使用池对象进行线程间通信,所以release()无论如何都必须是线程安全的。

3)消除共享池上的"虚假共享"可能会很尴尬-每个实例最初都应该是"新"的,以便独占地使用一个整数的缓存页面。至少这只需要在启动时做一次。

4)如果系统弹性在池耗尽,要么需要分配更多的对象可以在需要的时候添加到池中,(然后爬池大小),或生产者消费者队列可以这样使用线程块池在对象被释放之前,(pci队列更慢,因为condvar/信号/无论等待线程块,还死锁线程分配释放之前空池)。

5)需要在开发期间监视池的水平,以便可以检测到对象泄漏和双重释放。可以将代码/数据添加到对象/池中,以便在发生此类错误时进行检测,但这会影响性能。

第一,你真的要"new"那个东西吗?为什么不使用局部变量或每个线程的堆对象呢?

第二,看看http://en.wikipedia.org/wiki/Thread-local_storage,如果你的开发环境支持它…

既然没有人提到它,我也可能建议尝试使用Boehm的保守垃圾收集器;这意味着使用new(gc)代替new, GC_malloc代替malloc,不要为free -ing或delete -ing内存对象而烦恼。几年前,我测量了GC_mallocmalloc,它有点慢(GC_malloc可能是25µs,而malloc系统是22µs)。

我不知道Boehm的GC在多线程使用中的性能(但我知道它可以在多线程应用程序中使用)。

Boehm的GC的优点是您不应该关心数据的free