根据
调度器类描述:
这个类允许在一定程度上自定义TBB任务池的属性。例如,它可以限制由给定线程启动的并行工作的并发级别。它还可以用于指定TBB工作线程的堆栈大小,尽管如果线程池已经被创建,则此设置无效。
这在
initialize()
方法中进一步解释,该方法由构造函数调用:
如果存在任何其他task_scheduler_inits,则忽略number_of_threads。一个线程可以构造多个task_scheduler_inits。这样做不会有害,因为底层的调度器是引用计数的。
(我添加了突出部分)
我相信MATLAB已经在内部使用Intel TBB,并且必须在MEX函数执行之前在顶层初始化线程池。因此,您代码中的所有任务调度程序都将使用MATLAB内部部分指定的线程数,而忽略您在代码中指定的值。
默认情况下,MATLAB必须使用与物理处理器数量相等的线程池大小(而不是逻辑处理器),这表明在我的四核超线程机器上我得到:
>> maxNumCompThreads
Warning: maxNumCompThreads will be removed in a future release [...]
ans =
4
另一方面,OpenMP没有调度程序,我们可以通过调用以下函数在运行时控制线程数量:
#include <omp.h>
..
omp_set_dynamic(1);
omp_set_num_threads(omp_get_num_procs());
或者通过设置环境变量:
>> setenv('OMP_NUM_THREADS', '8')
为了测试这个提出的解释,这里是我使用的代码:
test_tbb.cpp
#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()()
{
ConcurrencyProfiler prof;
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()
{
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
这是一个简单的辅助类代码,用于通过跟踪从线程池调用了多少个工作线程来分析并发性能。您始终可以使用Intel VTune或任何其他分析工具来获取相同类型的信息。
tbb_helpers.hxx
#ifndef HELPERS_H
#define HELPERS_H
#include "tbb/atomic.h"
class ConcurrencyProfiler
{
public:
ConcurrencyProfiler();
~ConcurrencyProfiler();
static void Reset();
static size_t GetMaxNumThreads();
private:
static void RecordMax();
static tbb::atomic<size_t> cur_count;
static tbb::atomic<size_t> max_count;
};
#endif
tbb_helpers.cxx
#include "tbb_helpers.hxx"
tbb::atomic<size_t> ConcurrencyProfiler::cur_count;
tbb::atomic<size_t> ConcurrencyProfiler::max_count;
ConcurrencyProfiler::ConcurrencyProfiler()
{
++cur_count;
RecordMax();
}
ConcurrencyProfiler::~ConcurrencyProfiler()
{
--cur_count;
}
void ConcurrencyProfiler::Reset()
{
cur_count = max_count = 0;
}
size_t ConcurrencyProfiler::GetMaxNumThreads()
{
return static_cast<size_t>(max_count);
}
void ConcurrencyProfiler::RecordMax()
{
size_t o;
do {
o = max_count;
if (o >= cur_count) break;
} while(max_count.compare_and_swap(cur_count,o) != o);
}
首先,我将代码编译为本地可执行文件(我使用的是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
我在系统Shell(Windows 8.1)中运行程序。它占用了100%的CPU利用率,我得到了以下输出:
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
正如我们所期望的那样,线程池被初始化成我们所要求的大小,并且被完全利用,受限于我们创建的任务数(在上一个案例中,我们为只有100个并行任务分配了512个线程!)。
接下来,我将代码编译为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
当我在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
正如你所看到的,无论我们指定什么样的池大小,调度程序始终最多旋转4个线程来执行并行任务(
4
是我的四核机器上物理处理器的数量)。这证实了我在帖子开头所说的话。
请注意,我明确设置了处理器亲和力掩码以使用所有8个核心,但由于只有4个运行线程,在这种情况下CPU使用率大约保持在50%左右。
希望这有助于回答问题,并为长篇帖子感到抱歉 :)
mcc
)生成一个调用MEX函数的独立程序(因此在MCR上下文中运行)?还是将MEX函数与外部编译并且与MATLAB无关的普通C++程序进行比较?以下给出的答案似乎暗示了前者。 - Amro