繁体   English   中英

c ++线程开销

[英]c++ thread overhead

我正在使用C ++中的线程,特别是使用它们来并行化地图操作。

这是代码:

#include <thread>
#include <iostream>
#include <cstdlib>
#include <vector>
#include <math.h>
#include <stdio.h>

double multByTwo(double x){
  return x*2;
}

double doJunk(double x){
  return cos(pow(sin(x*2),3));
}

template <typename T>
void map(T* data, int n, T (*ptr)(T)){
  for (int i=0; i<n; i++)
    data[i] = (*ptr)(data[i]);
}

template <typename T>
void parallelMap(T* data, int n, T (*ptr)(T)){
  int NUMCORES = 3;
  std::vector<std::thread> threads;
  for (int i=0; i<NUMCORES; i++)
    threads.push_back(std::thread(&map<T>, data + i*n/NUMCORES, n/NUMCORES, ptr));
  for (std::thread& t : threads)
    t.join();
}

int main()
{
  int n = 1000000000;
  double* nums = new double[n];
  for (int i=0; i<n; i++)
    nums[i] = i;

  std::cout<<"go"<<std::endl;

  clock_t c1 = clock();

  struct timespec start, finish;
  double elapsed;

  clock_gettime(CLOCK_MONOTONIC, &start);

  // also try with &doJunk
  //parallelMap(nums, n, &multByTwo);
  map(nums, n, &doJunk);

  std::cout << nums[342] << std::endl;

  clock_gettime(CLOCK_MONOTONIC, &finish);

  printf("CPU elapsed time is %f seconds\n", double(clock()-c1)/CLOCKS_PER_SEC);

  elapsed = (finish.tv_sec - start.tv_sec);
  elapsed += (finish.tv_nsec - start.tv_nsec) / 1000000000.0;

  printf("Actual elapsed time is %f seconds\n", elapsed);
}

使用multByTwo ,并行版本实际上稍慢(1.01秒而不是.95实时),而使用doJunk则更快(51与136实时)。 这对我意味着

  1. 并行化正在发挥作用
  2. 声明新线程的开销非常大。 有关为什么开销如此之大,以及如何避免它的任何想法?

只是一个猜测:您可能会看到的是multByTwo代码如此之快以至于您实现了内存饱和。 无论你向它投入多少处理器能力,代码都不会运行得更快,因为它的速度已经达到了可以从RAM获取的速度。

您没有指定测试程序的硬件,也没有指定编译器版本和操作系统。 我确实在64位Scientific Linux下的四插槽Intel Xeon系统上尝试了您的代码,并从源代码编译了g++ 4.7。

首先在较旧的Xeon X7350系统上,我得到以下时间:

multByTwomap

CPU elapsed time is 6.690000 seconds
Actual elapsed time is 6.691940 seconds

multByTwo在3核上有parallelMap

CPU elapsed time is 7.330000 seconds
Actual elapsed time is 2.480294 seconds

并行加速是2.7倍。

doJunkmap

CPU elapsed time is 209.250000 seconds
Actual elapsed time is 209.289025 seconds

doJunk with parallelMap在3个核心上

CPU elapsed time is 220.770000 seconds
Actual elapsed time is 73.900960 seconds

并行加速是2.83x。

请注意,X7350来自相当古老的前Nehalem“Tigerton”系列,带有FSB总线和位于北桥的共享内存控制器。 这是一个没有NUMA效果的纯SMP系统。

然后我在四插槽Intel X7550上运行你的代码。 这些是Nehalem(“Beckton”)Xeons,内存控制器集成在CPU中,因此是一个4节点NUMA系统。 在一个套接字上运行并访问位于另一个套接字上的内存的线程运行速度稍慢。 对于可能通过某些愚蠢的调度程序决策迁移到另一个套接字的串行进程也是如此。 从时间上看,绑定在这样的系统中是非常重要的:

multByTwomap

CPU elapsed time is 4.270000 seconds
Actual elapsed time is 4.264875 seconds

multByTwomap绑定到NUMA节点0

CPU elapsed time is 4.160000 seconds
Actual elapsed time is 4.160180 seconds

multByTwomap绑定到NUMA节点0和CPU插槽1

CPU elapsed time is 5.910000 seconds
Actual elapsed time is 5.912319 seconds

mutlByTwo在3个核心上使用parallelMap

CPU elapsed time is 7.530000 seconds
Actual elapsed time is 3.696616 seconds

并行加速仅为1.13x(相对于最快的节点绑定串行执行)。 现在绑定:

multByTwo与3个核心上的parallelMap绑定到NUMA节点0

CPU elapsed time is 4.630000 seconds
Actual elapsed time is 1.548102 seconds

并行加速是2.69倍 - 与Tigerton CPU一样多。

multByTwo与3个核心上的parallelMap绑定到NUMA节点0和CPU插槽1

CPU elapsed time is 5.190000 seconds
Actual elapsed time is 1.760623 seconds

并行加速比前一种情况的2.36倍 - 88%。

(我太doJunk等待doJunk代码在相对较慢的Nehalems上完成,但我希望在Tigerton案例中有更好的表现)

但是有一个关于NUMA绑定的警告。 如果强制例如使用numactl --cpubind=0 --membind=0 ./program绑定到NUMA节点0,这将限制仅限于此节点的内存分配,并且在您的特定系统上,连接到CPU 0的内存可能不够,并且很可能会发生运行时故障。

正如您所看到的,除了创建线程的开销之外,还有一些因素可能会显着影响您的代码执行时间。 同样在非常快的系统上,与每个线程完成的计算工作相比,开销可能太高。 这就是为什么在提出有关并行性能的问题时,应该始终包含尽可能详细的硬件和用于衡量性能的环境的细节。

多线程只能在更少的时间内在多核机器上完成更多的工作。

其他明智的他们只是轮流轮流时尚。

根据平台的不同,产生新线程可能是一项昂贵的操作。 避免这种开销的最简单方法是在程序启动时生成一些线程并具有某种作业队列。 我相信std :: async会为你做这件事。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM