一、Task Scheduler默认执行
在TBB中,一般情况下,默认会允许任务调度器使用所有的可用计算资源 。这种情况当然是为了更大化的利用计算机的资源,但这也有可能在一些场合下,导致与其它任务的资源竞争。所以,TBB也允许对任务调度器进行控制,从而只使用其中的一部分资源。
但需要提醒的是,在对任务调度器进行配置控制时,可能导致一些问题。需要加以认真对待。
二、TBB配置任务调度器
TBB中,提供了task_arena接口作为控制任务管理器的引导。task_arena其实就是一个并行资源的管理器或者说并行的环境管理器,它允许创建一个隔离的线程池,同时它还支持嵌套的使用。
task_arena 应用的主要目的是:
1、控制并行度:指定 task_arena 中的线程数,控制并行任务的执行。
2、资源隔离:不同的 task_arena 可以运行在不同的 CPU 核心上,实现资源的隔离和优化。
3、优先级调度:不同的任务集分配可分配不同的 task_arena,实现优先级调度。
它可以通过下面两种方式来控制任务调度:
1、设置首选计算单元
2、限制部分计算单元
三、任务调度配置的内容
在知道任务管理器提供的接口task_arena后,就可以看看其如何应用 。这些任务的控制一般封装在task_arena::constraints中,它包括对下面几部分的控制:
1、首选NUMA节点
其实是就NUMA架构上,任务被随机调度到不同的节点上时,可能产生性能的损失,所以可以将任务分配到指定的NUMA节点,即可以在task_arena::constraints::numa_id域上进行标记节ID,见下面的代码:
std::vector<tbb::numa_node_id> numa_indexes = tbb::info::numa_nodes();
std::vector<tbb::task_arena> arenas(numa_indexes.size());
std::vector<tbb::task_group> task_groups(numa_indexes.size());for(unsigned j = 0; j < numa_indexes.size(); j++) {arenas[j].initialize(tbb::task_arena::constraints(numa_indexes[j]));arenas[j].execute([&task_groups, &j](){task_groups[j].run([](){/*some parallel stuff*/});});
}for(unsigned j = 0; j < numa_indexes.size(); j++) {arenas[j].execute([&task_groups, &j](){ task_groups[j].wait(); });
}
2、首选核心类型
这个可能是与CPU的核心类型有关,如果是同构的简单的处理器,可能就没有意义了。但如果在异构的有多种核心类型的CPU,可能就可以充分利用某个类型的核心,如下面的代码:
std::vector<tbb::core_type_id> core_types = tbb::info::core_types();
tbb::task_arena arena(tbb::task_arena::constraints{}.set_core_type(core_types.back())
);arena.execute( [] {/*the most performant core type is defined as preferred.*/
});
3、每个核心同时调度的最大逻辑线程数
这个就比较简单了,主要是针对Intel CPU的超线程技术,可以通过 task_arena::constraints::max_threads_per_core来限制每个核心同时运行的最大线程数,其实就是变相提高指定的目标的运行效率,有点线程绑定的味道,但又有很大不同。下面的代码表示一个核心只能运行一个线程:
tbb::task_arena no_ht_arena( tbb::task_arena::constraints{}.set_max_threads_per_core(1) );
no_ht_arena.execute( [] {/*parallel work*/
});
4、task_arena并发级别
这个其实和上面的目的相同,只是处理起来更简单,见下面的代码:
int no_ht_concurrency = tbb::info::default_concurrency(tbb::task_arena::constraints{}.set_max_threads_per_core(1)
);
tbb::task_arena arena( no_ht_concurrency );
arena.execute( [] {/*parallel work*/
});
同样的线程资源可以通过一种简单的约束控制起来,而不是去直接的控制,更方便控制。
四、总结
任务调度在哪儿都是一种非常让人纠结的事情。就像一个大厨,有一两个人时,饭菜就容易为所有人所表扬,可当面对上百人时,可能就有不少人给差评了。所以一个好的框架,不但要提供默认的管理调度手段,也要提供针对个性化的定制场景的接口。既要面对主流,也要取悦小众。难点,在于一个平衡!
做为开发者,要从学习代码的最初的功能到学习其架构,在到其整体的设计思想,这才是最终极的目标。