我是TBB
的新手并尝试做一个简单的表达。
我的功能数据是:
int n = 9000000;
int *data = new int[n];
我创建了一个函数,第一个没有使用TBB
:
void _array(int* &data, int n) {
for (int i = 0; i < n; i++) {
data[i] = busyfunc(data[i])*123;
}
}
It takes 0.456635 seconds.
还创建了一个to功能,第一个使用TBB
:
void parallel_change_array(int* &data,int list_count) {
//Instructional example - parallel version
parallel_for(blocked_range<int>(0, list_count),
[=](const blocked_range<int>& r) {
for (int i = r.begin(); i < r.end(); i++) {
data[i] = busyfunc(data[i])*123;
}
});
}
我需要0.584889 seconds.
至于busyfunc(int m):
int busyfunc(int m)
{
m *= 32;
return m;
}
您能否告诉我,为什么不使用TBB
的功能花费的时间少于TBB
的功能?
我认为,问题在于功能很简单,而且在不使用TBB
的情况下很容易计算。
答案 0 :(得分:3)
首先,busyfunc()
似乎不那么繁忙,因为9M元素只在半秒内计算出来,这使得这个例子相当于内存限制(未缓存的内存操作比算术运算多了几个数量级)。内存绑定计算的扩展程度不如计算限制,例如:普通内存复制通常可以扩展到不超过4倍,甚至可以在更大数量的内核/处理器上运行。
此外,内存绑定程序对NUMA效果更敏感,并且由于您使用标准C ++将此数组分配为连续内存,因此默认情况下它将完全分配在初始化发生的同一内存节点上。可以通过numactl -i all --
运行来更改此默认值。
最后一个,但最重要的是TBB懒得且很慢地初始化线程。我想你不打算编写一个在并行计算花费0.5秒后退出的应用程序。因此,公平的基准应该考虑到实际应用中预期的所有预热效果。至少,它必须等到所有线程都启动并运行才能开始测量。 This answer提出了一种方法。
[update]请参阅Alexey的答案,了解潜在的编译器优化差异的另一个可能原因。
答案 1 :(得分:2)
除了Anton的asnwer之外,我还建议检查编译器是否能够等效地优化代码。
首先,检查由单个线程执行的TBB版本的性能,没有真正的并行性。您可以使用tbb::global_control
或tbb::task_scheduler_init
将线程数限制为1,例如
tbb::global_control ctl(tbb::global_control::max_allowed_parallelism, 1);
当一个线程执行所有代码时,线程创建的开销以及缓存局部性或NUMA效应不应起作用。因此,您应该看到与无TBB版本大致相同的性能。如果你这样做,那么你就有可扩展性问题,Anton解释了可能的原因。
但是,如果您发现性能下降很多,那么这是一个串行优化问题。一个已知的原因是某些编译器不能优化blocked_range
上的循环,因为它们优化了原始循环;并且还观察到将r.end()
存储到局部变量可能会有所帮助:
int rend = r.end();
for (int i = r.begin(); i < rend; i++) {
data[i] = busyfunc(data[i])*123;
}