多线程向量求和的可扩展性

Posted

技术标签:

【中文标题】多线程向量求和的可扩展性【英文标题】:Scalability of multi-threaded vector sum 【发布时间】:2018-06-18 15:49:57 【问题描述】:

这是一段用于多线程向量求和的 C++11 代码。

#include <thread>

template<typename ITER>
void sum_partial(ITER a, ITER b, double & result) 
  result = std::accumulate(a, b, 0.0);


template<typename ITER>
double sum(ITER begin, ITER end, unsigned int nb_threads) 
  size_t len = std::distance(begin, end);
  size_t size = len/nb_threads;

  std::vector<std::thread> thr(nb_threads-1);
  std::vector<double> r(nb_threads);
  size_t be = 0;
  for(size_t i = 0; i < nb_threads-1; i++) 
    size_t en = be + size;
    thr[i] = std::thread(sum_partial<ITER>, begin + be, begin + en, std::ref(r[i]));
    be = en;
  
  sum_partial(begin + be, begin + len, r[nb_threads-1]);
  for(size_t i = 0; i < nb_threads-1; i++)
    thr[i].join();
  return std::accumulate(r.begin(), r.end(), 0.0);

典型的用法是 sum(x.begin(), x.end(), n)x 一个双精度向量。

这是一个图表,显示计算时间作为线程数的函数(求和 10⁷ 值的平均时间,在没有其他任何运行的 8 核计算机上 - 我在 32 核计算机上尝试过,行为非常类似)。

为什么可扩展性这么差?可以改进吗?

我的(非常有限的)理解是,为了具有良好的可扩展性,线程应该避免在同一个缓存行中写入。这里所有线程都写入r 一次,在它们计算的最后,我不希望它成为限制因素。是内存带宽问题吗?

【问题讨论】:

需要 y 轴上的单位。 @UKMonkey 只需几秒钟,但我认为这并不重要...... 好吧,如果需要 Milliseconds to create thread: 0.015625 看起来线程创建可能是您的瓶颈 - 如果您的单位错误 @Elvis 这就是单位很重要的原因。 @Elvis 是的,你错了。 std::accumulate 被定义为左折叠,矢量化会破坏这一点,因为浮点加法 is not associative。 (根据 fp 严格性设置,库/编译器可能会这样做。)我肯定会反对使用 std::accumulate 以获得最佳性能。 【参考方案1】:

accumulate 在 cpu 算术单元上的利用率很低,但缓存和内存吞吐量很可能是瓶颈,尤其是对于 10^7 double 或 1000 万 double = 80MB 数据,这远远超过 CPU 缓存大小.


要克服缓存和内存吞吐量瓶颈,您可能需要enable prefetch 和-fprefetch-loop-arrays,甚至手动进行一些组装。

【讨论】:

以上是关于多线程向量求和的可扩展性的主要内容,如果未能解决你的问题,请参考以下文章

将向量传递给C++中的线程函数

手动编写多线程循环 - 次优可扩展性

扩展作为内存传递的向量的大小

为啥多线程应用程序通常会扩展不好?

PHP开启多线程扩展

在多核机器上扩展多线程应用程序