编辑:其中Matlab limits TBB but not OpenMP> 我的问题与上面的问题不同,虽然使用相同的示例代码进行说明,但它并没有重复。在我的情况下,我在tbb初始化中指定了多个线程,而不是使用“deferred”。另外我在谈论cx中TBB与TBM中TBB之间的奇怪行为。该问题的答案仅演示了在C ++中运行TBB时的线程初始化,而不是在MEX中。
我正在尝试提升Matlab mex文件以提高性能。在mex中使用TBB时遇到的奇怪之处是TBB初始化不能按预期工作。
此C ++程序在执行 100%cpu 时使用 15 TBB线程 :< / p>
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();
}
然后我修改了一些代码来为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>());
}
最终在Matlab中调用BuildMEX.mexw64。我将以下代码片段编译(mcc)到Matlab二进制文件“MEXtest.exe”并使用vTune来分析其性能(在MCR中运行)。进程中的TBB仅初始化 4 tbb线程 ,并且二进制文件仅占用 ~50%cpu 用法。为什么MEX会降低整体性能和TBB?如何为mex获取更多cpu使用?
MEXtest.exe
function MEXtest()
BuildMEX();
end
答案 0 :(得分:1)
假设您的计算机上有超过4个物理内核,MATLAB独立进程的关联掩码可能会限制可用的CPU。从实际MATLAB安装调用的函数应该使用所有CPU,但对于使用MATLAB编译器生成的独立MATLAB应用程序可能不是这种情况。再次尝试测试,直接从MATLAB运行MEX功能。在任何情况下,您都应该能够重置关联掩码以使TBB可以使用所有核心,但我认为这种方法不会让您强制TBB启动比您拥有物理核心更多的线程。
<强>背景强>
自TBB 3.0更新4以来,引用了处理器关联设置以确定可用内核的数量according to a developer blog:
因此,TBB应该做的唯一事情就是检索当前的进程关联掩码,计算其中的非零位数,而不是向系统询问它有多少CPU,而且,TBB不再使用工作者线程超过必要!这正是TBB 3.0 Update 4所做的。澄清我之前博客结尾处的陈述 TBB的方法
tbb::task_scheduler_init::default_num_threads()
和tbb::tbb_thread::hardware_concurrency()
不仅返回系统或当前处理器组中逻辑CPU的总数,而且还返回CPU的数量可根据其亲和力设置进行处理。
同样,the docs for tbb::default_num_threads
表示此更改:
在TBB 3.0 U4之前,此方法返回系统中逻辑CPU的数量。目前在Windows,Linux和FreeBSD上,它根据其关联掩码返回当前进程可用的逻辑CPU数。
The docs for tbb::task_scheduler_init::initialize
还表明线程数“受处理器关联掩码限制”。
解决强>
要检查您是否受到关联掩码的限制,可以使用Windows .NET功能:
numCoresInSystem = 16;
proc = System.Diagnostics.Process.GetCurrentProcess();
dec2bin(proc.ProcessorAffinity.ToInt32,numCoresInSystem)
输出字符串在任何表示真实(存在于系统中)核心的位置都不应该为零。
您可以在MATLAB或C中设置关联掩码,如Q&amp; A,Set processor affinity for MATLAB engine (Windows 7)中所述。 MATLAB方式:
proc = System.Diagnostics.Process.GetCurrentProcess();
proc.ProcessorAffinity = System.IntPtr(int32(2^numCoresInSystem-1));
proc.Refresh()
或者在mexFunction中使用Windows API,在调用task_scheduler_init
之前使用:
SetProcessAffinityMask(GetCurrentProcess(),(1 << N) - 1)
对于* nix,您可以拨打taskset
:
system(sprintf('taskset -p %d %d',2^N - 1,feature('getpid')))
答案 1 :(得分:1)
根据scheduler class description:
此类允许为某些人定制TBB任务池的属性 程度。例如,它可以限制并行工作的并发级别 由给定的线程发起。它也可以用于指定堆栈 TBB工作线程的大小,尽管此设置无效 如果已经创建了线程池。
在构造函数调用的initialize()
methods中进一步解释了这一点:
如果当前存在任何其他task_scheduler_inits,则忽略number_of_threads 。线程可以构造多个 task_scheduler_inits。这样做不会有害,因为潜在的 调度程序是引用计数。
(由我添加的突出显示的部分)
我相信MATLAB已在内部使用英特尔TBB,它必须在执行MEX功能之前在顶层初始化一个线程池。因此,代码中的所有任务调度程序将使用MATLAB内部部分指定的线程数,忽略您在代码中指定的值。
默认情况下,MATLAB必须初始化大小为equal to the number of physical processors(不是逻辑)的线程池,这可以通过我的四核超线程机器得到的事实表明:
>> 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')
为了测试这个建议的解释,这里是我使用的代码:
#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
以下是一个简单帮助程序类的代码,用于通过跟踪从线程池调用的工作程序数来分析并发性。您始终可以使用Intel VTune或任何其他分析工具来获取相同类型的信息:
#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
#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);
}
// Performs: max_count = max(max_count,cur_count)
// http://www.threadingbuildingblocks.org/
// docs/help/tbb_userguide/Design_Patterns/Compare_and_Swap_Loop.htm
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);
}
首先我将代码编译为本机可执行文件(我使用的是英特尔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)中运行该程序。它的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
正如预期的那样,线程池初始化的大小与我们要求的一样大,并且被完全利用受到我们创建的任务数量的限制(在最后一种情况下,我们只有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%。
希望这有助于回答这个问题,对于长篇文章感到抱歉:)