4

我只是要求这个试图了解我花了 24 小时试图解决的问题。

我的系统:Ubuntu 12.04.2,Matlab R2011a,它们都是 64 位,基于 Nehalem 的 Intel Xeon 处理器。

问题很简单,Matlab 允许基于 OpenMP 的程序利用启用了超线程的所有 CPU 内核,但不允许 TBB 使用相同的 CPU 内核。

运行 TBB 时,我只能启动 4 个线程,即使我将 maxNumCompThreads 更改为 8。而使用 OpenMP,我可以使用我想要的所有线程。如果没有超线程,TBB 和 OpenMP 当然会使用所有 4 个内核。

我了解超线程,并且它是虚拟的,但 matlab 的限制实际上确实会导致性能下降(额外的参考)。

我使用 2 个程序测试了这个问题,一个简单的 for 循环

#pragma omp parallel for

以及另一个基于 tbb 示例代码的非常简单的循环。

tbb::task_scheduler_init init(tbb::task_scheduler_init::deferred);
tbb::parallel_for_each(tasks.begin(),tasks.end(),invoker<mytask>());

并用 matlab mexFunction 将它们都包裹起来。

有人对此有解释吗?允许matlab节流TBB但不允许OpenMP节流的线程创建方法或结构是否存在固有差异?

参考代码:

开放式MP:

#include "mex.h"

void mexFunction( int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[] ){
    threadCount = 100000;
#pragma omp parallel for
    for(int globalId = 0; globalId < threadCount ; globalId++)
    {
        for(long i=0;i<1000000000L;++i) {} // Deliberately run slow
    }
}

待定:

#include "tbb/parallel_for_each.h"
#include "tbb/task_scheduler_init.h"
#include <iostream>
#include <vector>
#include "mex.h"

struct mytask {
  mytask(size_t n)
    :_n(n)
  {}
  void operator()() {
    for (long i=0;i<1000000000L;++i) {}  // Deliberately run slow
    std::cerr << "[" << _n << "]";
  }
  size_t _n;
};

template <typename T> struct invoker {
  void operator()(T& it) const {it();}
};

void mexFunction(int nlhs, mxArray* plhs[], int nrhs, const
mxArray* prhs[]) {

  tbb::task_scheduler_init init(tbb::task_scheduler_init::deferred);  // Automatic number of threads

  std::vector<mytask> tasks;
  for (int i=0;i<10000;++i)
    tasks.push_back(mytask(i));

  tbb::parallel_for_each(tasks.begin(),tasks.end(),invoker<mytask>());

}
4

1 回答 1

2

抱歉这么久才回答。指定deferred只是阻止任务调度程序创建线程池,直到第一个并行构造开始。默认情况下,线程数是automatic,它对应于内核数(代码设置在 中src/tbb/tbb_misc_ex.cpp,并且还取决于 CPU 亲和力等。请参阅initialize_hardware_concurrency_info()

我稍微修改了你的代码:

#include "tbb/parallel_for_each.h"
#include "tbb/task_scheduler_init.h"
#include "tbb/atomic.h"
#include "tbb/spin_mutex.h"
#include <iostream>
#include <vector>

// If LOW_THREAD == 0, run with task_scheduler_init(automatic), which is the number
// of cores available.  If 1, start with 1 thread.

#ifndef NTASKS
#define NTASKS 50
#endif
#ifndef MAXWORK
#define MAXWORK 400000000L
#endif
#ifndef LOW_THREAD
#define LOW_THREAD 0  // 0 == automatic
#endif

tbb::atomic<size_t> cur_par;
tbb::atomic<size_t> max_par;

#if PRINT_OUTPUT
tbb::spin_mutex print_mutex;
#endif

struct mytask {
  mytask(size_t n) :_n(n) {}
  void operator()() {
      size_t my_par = ++cur_par;
      size_t my_old = max_par;
      while( my_old < cur_par) { my_old = max_par.compare_and_swap(my_par, my_old); }

      for (long i=0;i<MAXWORK;++i) {}  // Deliberately run slow
#if PRINT_OUTPUT
      {
          tbb::spin_mutex::scoped_lock s(print_mutex);
          std::cerr << "[" << _n << "]";
      }
#endif
      --cur_par;
  }
  size_t _n;
};

template <typename T> struct invoker {
  void operator()(T& it) const {it();}
};

void mexFunction(/*int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[]*/) {

    for( size_t thr = LOW_THREAD; thr <= 128; thr = thr ? thr * 2: 1) {
        cur_par = max_par = 0;
        tbb::task_scheduler_init init(thr == 0 ? (unsigned int)tbb::task_scheduler_init::automatic : thr);

        std::vector<mytask> tasks;
        for (int i=0;i<NTASKS;++i) tasks.push_back(mytask(i));

        tbb::parallel_for_each(tasks.begin(),tasks.end(),invoker<mytask>());
        std::cout << " for thr == ";
        if(thr) std::cout << thr; else std::cout << "automatic";
        std::cout << ", maximum parallelism == " << (size_t)max_par << std::endl;
    }
}

int main() {
    mexFunction();
}

我在这里的 16 核系统上运行它:

for thr == automatic, maximum parallelism == 16
for thr == 1, maximum parallelism == 1
for thr == 2, maximum parallelism == 2
for thr == 4, maximum parallelism == 4
for thr == 8, maximum parallelism == 8
for thr == 16, maximum parallelism == 16
for thr == 32, maximum parallelism == 32
for thr == 64, maximum parallelism == 50
for thr == 128, maximum parallelism == 50

50 的限制是程序创建的任务总数。

TBB 创建的线程由程序启动的并行构造共享,所以如果你有两个并行的 for_each 同时运行,最大线程数不会改变;每个 for_each 都会运行得更慢。TBB 库不控制 OpenMP 构造中使用的线程数,因此 OpenMP parallel_for 和 TBB parallel_for_each 通常会超额订阅机器。

于 2014-01-21T19:42:53.137 回答