c ++线程开销

时间:2012-06-22 15:43:51

标签: c++ multithreading performance c++11

我正在使用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. 声明有一个非常大的开销 新线程。关于为什么开销如此之大以及如何避免它的任何想法?

4 个答案:

答案 0 :(得分:7)

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

答案 1 :(得分:3)

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

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

带有multByTwo

map

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倍。

带有doJunk

map

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

doJunk,{3}核心parallelMap

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系统。在一个套接字上运行并访问位于另一个套接字上的内存的线程运行速度稍慢。对于可能通过某些愚蠢的调度程序决策迁移到另一个套接字的串行进程也是如此。从时间上看,绑定在这样的系统中是非常重要的:

带有multByTwo

map

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

multByTwo
带有map

CPU elapsed time is 5.910000 seconds Actual elapsed time is 5.912319 seconds 绑定到NUMA节点0和CPU套接字1

mutlByTwo

parallelMap,{3}核心CPU elapsed time is 7.530000 seconds Actual elapsed time is 3.696616 seconds

multByTwo

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

parallelMap,其中3个核心上的CPU elapsed time is 4.630000 seconds Actual elapsed time is 1.548102 seconds 绑定到NUMA节点0

multByTwo

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

带有parallelMap的{​​p> CPU elapsed time is 5.190000 seconds Actual elapsed time is 1.760623 seconds 绑定到NUMA节点0和CPU插槽1的3个核心

doJunk

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

(我太急于等待numactl --cpubind=0 --membind=0 ./program代码完成相对较慢的Nehalems,但我希望在Tigerton案例中表现更好一些)

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

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

答案 2 :(得分:2)

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

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

答案 3 :(得分:0)

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