TBB在Matlab Mex文件中表现得很奇怪

yfe*_*eng 5 matlab multithreading initialization tbb mex

编辑:< Matlab限制TBB但不限制OpenMP >我的问题与上面的问题不同,虽然使用相同的示例代码进行说明,但它并没有重复.在我的情况下,我在tbb初始化中指定了多个线程,而不是使用"deferred".另外我在谈论cx中TBB与TBM中TBB之间的奇怪行为.该问题的答案仅演示了在C++中运行TBB时的线程初始化,而不是在MEX中.


我正在尝试提升Matlab mex文件以提高性能.在mex中使用TBB时遇到的奇怪之处是TBB初始化不能按预期工作.

这个C++程序执行100%的cpu使用,并且在单独执行时有15个TBB线程:

main.cpp中

#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<10000000000L;++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(15);  // 15 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>());

}

int main()
{
    mexFunction();
}
Run Code Online (Sandbox Code Playgroud)

然后我修改了一些代码来为matlab制作一个MEX:

BuildMEX.mexw64

#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<10000000000L;++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(15);  // 15 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>());

}
Run Code Online (Sandbox Code Playgroud)

最终在Matlab中调用BuildMEX.mexw64.我将以下代码片段编译(mcc)到Matlab二进制文件"MEXtest.exe"并使用vTune来分析其性能(在MCR中运行).进程中的TBB只初始化了4个tbb线程,而二进制只占用了约50%的cpu使用率.为什么MEX会降低整体性能和TBB?如何为mex获取更多cpu使用?

MEXtest.exe

function MEXtest()

BuildMEX();

end
Run Code Online (Sandbox Code Playgroud)

Amr*_*mro 2

根据调度程序类描述

该类允许在一定程度上自定义TBB任务池的属性。例如,它可以限制给定线程启动的并行工作的并发级别。它还可用于指定 TBB 工作线程的堆栈大小,但如果线程池已创建,则此设置无效

构造函数调用的initialize()方法对此进行了进一步解释:

如果当前存在任何其他 task_scheduler_init,则忽略 number_of_threads。一个线程可以构造多个task_scheduler_init。这样做不会有什么坏处,因为底层调度程序是引用计数的。

(突出显示的部分是我添加的)

我相信 MATLAB 已经在内部使用了 Intel TBB,并且在执行 MEX 函数之前它必须在顶层初始化一个线程池。因此,代码中的所有任务调度程序都将使用 MATLAB 内部部分指定的线程数,而忽略您在代码中指定的值。

默认情况下,MATLAB 必须初始化线程池,其大小等于物理处理器(而非逻辑处理器)的数量,在我的四核超线程机器上,我得到的事实表明了这一点:

>> maxNumCompThreads
Warning: maxNumCompThreads will be removed in a future release [...]
ans =
     4
Run Code Online (Sandbox Code Playgroud)

另一方面,OpenMP 没有调度程序,我们可以通过调用以下函数来控制运行时的线程数量:

>> maxNumCompThreads
Warning: maxNumCompThreads will be removed in a future release [...]
ans =
     4
Run Code Online (Sandbox Code Playgroud)

或者通过设置环境变量:

>> setenv('OMP_NUM_THREADS', '8')
Run Code Online (Sandbox Code Playgroud)

为了测试这个建议的解释,这是我使用的代码:

测试_tbb.cpp

#include <omp.h>
.. 
omp_set_dynamic(1);
omp_set_num_threads(omp_get_num_procs());
Run Code Online (Sandbox Code Playgroud)

以下是一个简单帮助程序类的代码,用于通过跟踪从线程池调用了多少工作线程来分析并发性。您始终可以使用英特尔 VTune或任何其他分析工具来获取相同类型的信息:

tbb_helpers.hxx

>> setenv('OMP_NUM_THREADS', '8')
Run Code Online (Sandbox Code Playgroud)

tbb_helpers.cxx

#ifdef MATLAB_MEX_FILE
#include "mex.h"
#endif

#include <cstdlib>
#include <cstdio>
#include <vector>

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#include "tbb/task_scheduler_init.h"
#include "tbb/parallel_for_each.h"
#include "tbb/spin_mutex.h"

#include "tbb_helpers.hxx"

#define NTASKS 100
#define NLOOPS 400000L

tbb::spin_mutex print_mutex;

struct mytask {
    mytask(size_t n) :_n(n) {}
    void operator()()
    {
        // track maximum number of parallel workers run
        ConcurrencyProfiler prof;

        // burn some CPU cycles!
        double x = 1.0 / _n;
        for (long i=0; i<NLOOPS; ++i) {
            x = sin(x) * 10.0;
            while((double) rand() / RAND_MAX < 0.9);
        }
        {
            tbb::spin_mutex::scoped_lock s(print_mutex);
            fprintf(stderr, "%f\n", x);
        }
    }
    size_t _n;
};

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

void run()
{
    // use all 8 logical cores
    SetProcessAffinityMask(GetCurrentProcess(), 0xFF);

    printf("numTasks = %d\n", NTASKS);
    for (int t = tbb::task_scheduler_init::automatic;
         t <= 512; t = (t>0) ? t*2 : 1)
    {
        tbb::task_scheduler_init init(t);

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

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

        printf("pool_init(%d) -> %d worker threads\n", t,
            ConcurrencyProfiler::GetMaxNumThreads());
    }
}

#ifdef MATLAB_MEX_FILE
void mexFunction(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[])
{
    run();
}
#else
int main()
{
    run();
    return 0;
}
#endif
Run Code Online (Sandbox Code Playgroud)

首先,我将代码编译为本机可执行文件(我使用的是 Intel C++ Composer XE 2013 SP1,带有 VS2012 Update 4):

C:\> vcvarsall.bat amd64
C:\> iclvars.bat intel64 vs2012
C:\> icl /MD test_tbb.cpp tbb_helpers.cxx tbb.lib
Run Code Online (Sandbox Code Playgroud)

我在系统 shell (Windows 8.1) 中运行该程序。CPU 利用率达到 100%,我得到以下输出:

C:\> test_tbb.exe 2> nul
numTasks = 100
pool_init(-1) -> 8 worker threads          // task_scheduler_init::automatic
pool_init(1) -> 1 worker threads
pool_init(2) -> 2 worker threads
pool_init(4) -> 4 worker threads
pool_init(8) -> 8 worker threads
pool_init(16) -> 16 worker threads
pool_init(32) -> 32 worker threads
pool_init(64) -> 64 worker threads
pool_init(128) -> 98 worker threads
pool_init(256) -> 100 worker threads
pool_init(512) -> 98 worker threads
Run Code Online (Sandbox Code Playgroud)

正如预期的那样,线程池被初始化为我们要求的大小,并且充分利用受到我们创建的任务数量的限制(在最后一种情况下,我们只有 512 个线程用于 100 个并行任务!)。

接下来我将代码编译为 MEX 文件:

>> mex -I"C:\Program Files (x86)\Intel\Composer XE\tbb\include" ...
   -largeArrayDims test_tbb.cpp tbb_helpers.cxx ...
   -L"C:\Program Files (x86)\Intel\Composer XE\tbb\lib\intel64\vc11" tbb.lib
Run Code Online (Sandbox Code Playgroud)

以下是在 MATLAB 中运行 MEX 函数时得到的输出:

>> test_tbb()
numTasks = 100
pool_init(-1) -> 4 worker threads
pool_init(1) -> 4 worker threads
pool_init(2) -> 4 worker threads
pool_init(4) -> 4 worker threads
pool_init(8) -> 4 worker threads
pool_init(16) -> 4 worker threads
pool_init(32) -> 4 worker threads
pool_init(64) -> 4 worker threads
pool_init(128) -> 4 worker threads
pool_init(256) -> 4 worker threads
pool_init(512) -> 4 worker threads
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,无论我们指定什么池大小,调度程序始终最多旋转 4 个线程来执行并行任务(4即我的四核计算机上的物理处理器的数量)。这证实了我在帖子开头所说的内容。

请注意,我明确设置了处理器关联掩码以使用所有 8 个核心,但由于只有 4 个正在运行的线程,因此在这种情况下 CPU 使用率保持在大约 50%。

希望这有助于回答这个问题,对于这么长的帖子表示歉意:)