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

时间:2011-05-09 06:04:03

标签: c++ dynamic-memory-allocation

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

我希望他们在堆里。

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

我已经尝试过这个,但它降低了性能。我只是想知道,我只是制作了错误的代码,还是有一些我不了解内存分配的东西?

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

编辑:

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

8 个答案:

答案 0 :(得分:4)

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

  • 操作系统
  • 您正在使用的malloc的实施

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

现在,malloc的默认实现通常是保守的,并且只是围绕这一切进行了巨大的锁定。这意味着请求是按顺序处理的,并且从多个线程而不是一个线程分配的唯一事情就是减慢整个事情的速度。

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

虽然没有银弹,但OS必须在某一时刻执行虚拟< =>实际翻译需要某种形式的锁定。

你最好的选择是通过竞技场分配:

  • 立刻分配大块(竞技场)
  • 将它们拆分为适当大小的数组

没有必要并行化竞技场分配,你最好还是要求最大的竞技场(请记住,太大的分配请求可能会失败),然后你可以并行分割

tcmallocjemalloc可能会有所帮助,但它们不适用于分配(这是不常见的),我不知道是否可以配置他们要求的竞技场的大小。

答案 1 :(得分:3)

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

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

答案 2 :(得分:3)

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

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

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

答案 3 :(得分:1)

至于glibc,它有竞技场(参见here),每个竞技场都有锁定。

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

答案 4 :(得分:0)

据我所知,所有操作系统都在动态分配系统调用(malloc ...)中隐含了互斥锁。如果你想到这一点,如果你不锁定这个动作,你可能会遇到可怕的问题。

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

但我认为更好的想法应该是将整个内存分配一次(应该工作得非常快)并将其自行拆分。我认为tbb分配器做了类似的事情。

执行类似

的操作

new int [1024 * 10000]并将1024个部分分配给指针数组或者你使用的是什么。

你明白吗?

答案 5 :(得分:0)

由于堆是按进程共享的,因此每个分配都会锁定堆,因此每个线程只能对它进行串行访问。这可以解释当您从多个线程执行alloc时的性能下降。

答案 6 :(得分:0)

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

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

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

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

有可能操作系统可以使用最小锁定在同一进程内安全地处理并行分配,这样可以减少分配时间。不幸的是,我不知道。

答案 7 :(得分:0)

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

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

像这样,当你用0到9999之间的数字替换123时,你有小数组。