我可以通过使用多个线程来更快地分配内存吗?

Posted

技术标签:

【中文标题】我可以通过使用多个线程来更快地分配内存吗?【英文标题】:Can i allocate memory faster by using multiple threads? 【发布时间】:2011-08-21 10:42:30 【问题描述】:

如果我创建一个保留 1kb 整数数组 int[1024] 的循环,并且我希望它分配 10000 个数组,我可以通过从多个线程运行内存分配来使其更快吗?

我希望它们在堆中。

假设我有一个多核处理器来完成这项工作。

我已经尝试过了,但它降低了性能。我只是想知道,我是否只是编写了糟糕的代码,还是我对内存分配不了解?

答案是否取决于操作系统?如果是这样,请告诉我它在不同平台上的工作原理。

编辑:

整数数组分配循环只是一个简化的例子。不要费心告诉我如何改进它。

【问题讨论】:

考虑启动线程的开销... 分配内存不是仅仅放弃内存的过程。您需要跟踪和组织它(尤其是当它被释放时)。所以保存这些信息的数据结构对错误非常敏感。因此,当它们被修改时,您需要确保作用于该数据的多个线程不会造成混乱。这基本上意味着对结构的访问需要同步,这将产生开销(大部分时间)。 @Martin:您正在描述一种设计,其中有一个内存数据池,所有线程都从中分配内存。一个好的多线程分配器不会。它们具有不需要同步的每个线程池。 @MSalters:我对标准 C++ lib 内存分配器持悲观态度。 @Martin:没有标准的分配器。接口只有一个标准,甚至不承认线程。每个供应商都有自己的实施方式,而且差异很大。 【参考方案1】:

这取决于很多事情,但主要是:

操作系统 你正在使用的malloc的实现

操作系统负责分配您的进程可以访问的“虚拟内存”,并构建一个转换表,将虚拟内存映射回实际内存地址。

现在,malloc 的默认实现通常是保守的,并且会简单地围绕所有这些设置一个巨大的锁。这意味着请求是串行处理的,从多个线程而不是一个线程分配的唯一作用就是减慢整个过程。

还有更聪明的分配方案,通常基于池,它们可以在其他malloc 实现中找到:tcmalloc(来自 Google)和jemalloc(由 Facebook 使用)是两个这样的实现,专为高- 在多线程应用程序中的性能。

虽然没有灵丹妙药,操作系统必须在某一时刻执行需要某种形式的锁定的虚拟 实际转换。

您最好的选择是按竞技场分配:

一次性分配大块(竞技场) 将它们分成适当大小的数组

不需要并行化 arena 分配,最好尽可能要求最大的 arena(请记住,分配请求过多可能会失败),然后您可以并行化拆分.

tcmallocjemalloc 可能会有所帮助,但是它们不是为 big 分配而设计的(这很不寻常),我不知道是否可以配置他们要求的竞技场。

【讨论】:

【参考方案2】:

答案取决于内存分配例程,它是 C++ 库层 operator new 的组合,可能包裹在 libC malloc() 周围,而后者又偶尔调用诸如 sbreak() 之类的 OS 函数。所有这些的实现和性能特征都是未指定的,并且可能因编译器版本、编译器标志、不同的操作系统版本、不同的操作系统等而有所不同。如果分析显示它更慢,那么这就是底线。您可以尝试改变线程的数量,但可能发生的情况是所有线程都试图获得相同的锁以修改堆......说“好的,线程 X 下一个继续”所涉及的开销和“这里的线程 X,我完成了”只是在浪费时间。另一个 C++ 环境最终可能会使用原子操作来避免锁定,这可能会或可能不会被证明更快……没有一般规则。

如果您想更快地完成,请考虑分配一个 10000*1024 整数数组,然后使用它的不同部分(例如[0]..[1023][1024]..[2047]...)。

【讨论】:

【参考方案3】:

我认为也许你需要调整你对多线程的期望。

多线程的主要优点是您可以异步执行任务,即在parallel 中。在您的情况下,当您的主线程需要更多内存时,它是否由另一个线程分配并不重要 - 您仍然需要停止并等待分配完成,所以这里有no parallelism。此外,还有一个线程在完成时发出信号而另一个等待完成的开销,这只会降低性能。此外,如果您每次需要分配时都启动一个线程,这是一个huge 开销。如果没有,则需要某种机制在线程之间传递分配请求和响应,这是一种任务队列,又是一种没有收益的开销。

另一种方法可能是分配线程提前运行,pre-allocates 分配您 will 需要的内存。这可以给你一个真正的收获,但如果你正在做预分配,你还不如在主线程中做,这样会更简单。例如。一次性分配 10M(或 10 倍 1M,或尽可能多的连续内存),并有一个由 10,000 个指针组成的数组,在 1024 个偏移处指向它,代表您的数组。如果您不需要彼此独立地释放它们,这似乎比使用多线程更简单,甚至可能更有效。

【讨论】:

【参考方案4】:

至于 glibc,它有 arena(参见 here),每个 arena 都有锁。

您还可以考虑 google 的 tcmalloc(代表 Thread-Caching malloc),它显示线程应用程序的性能提高了 30%。我们在项目中使用它。在调试模式下,它甚至可以发现一些不正确的内存使用(例如新/空闲不匹配)

【讨论】:

【参考方案5】:

据我所知,所有操作系统在动态分配系统调用(malloc...)中都有隐式互斥锁。如果您考虑一下,如果您不锁定此操作,您可能会遇到可怕的问题。

您可以使用多线程 API 线程构建块 http://threadingbuildingblocks.org/ 它具有多线程友好的可扩展分配器。

但我认为一个更好的主意应该是一次性分配整个内存(应该工作得非常快)然后自行拆分。我认为 tbb 分配器做了类似的事情。

做类似的事情

new int[1024*10000] 然后将 1024ints 的部分分配给您的指针数组或您使用的任何东西。

你明白吗?

【讨论】:

【参考方案6】:

因为堆是每个进程共享的,所以每次分配都会锁定堆,因此只能由每个线程串行访问。这可以解释当您像现在这样从多个线程进行分配时性能下降的原因。

【讨论】:

这假设了一个非常简单的实现,整个堆只有一个锁。【参考方案7】:

如果数组属于一起并且只会作为一个整体被释放,您可以分配一个 10000*1024 个整数的数组,然后让您的各个数组指向它。请记住,您不能delete 小数组,只能是整体。

int *all_arrays = new int[1024 * 10000];
int *small_array123 = all_arrays + 1024 * 123;

像这样,当您将 123 替换为 0 到 9999 之间的数字时,您会得到较小的数组。

【讨论】:

那我不明白你真正想做什么。解释不那么简单的问题,也许我们可以帮助你。 我试图更快地复制对象树,因此我尝试将复制操作拆分到不同的线程。但是,我已经让它足够快了。所以现在我只是想更好地理解内存分配。【参考方案8】:

答案取决于所使用的操作系统和运行时,但在大多数情况下,您不能。

通常,您将有两个版本的运行时:多线程版本和单线程版本。

单线程版本不是线程安全的。两个线程同时进行的分配可能会炸毁您的应用程序。

多线程版本是线程安全的。然而,就大多数常见实现中的分配而言,这仅意味着对malloc 的调用被包装在互斥体中。在任何给定时间,malloc 函数中只能有一个线程,因此尝试使用多个线程加速分配只会导致锁护卫队。

可能有一些操作系统可以安全地处理同一进程中的并行分配,使用最少的锁定,这将允许您减少分配所花费的时间。不幸的是,我不知道。

【讨论】:

以上是关于我可以通过使用多个线程来更快地分配内存吗?的主要内容,如果未能解决你的问题,请参考以下文章

当多个线程调用相同的函数时,Java/C#如何分配内存

巨大的内存分配:堆栈与堆

动态内存分配的线程争用

如何用Java编写一段代码引发内存泄露

两个或多个线程如何在它们分配的堆上共享内存?

我们可以使用 realloc 来释放动态分配的内存吗?