内存分配不仅是编程的基本任务,也是在多核编程时影响效率的一大挑战。在C++里我们可以用自定义内存分配器代替std:: allocator,Threading Building Blocks就提供了一个与std::allocator兼容的可扩展内存分配器。 copyright 52bc.net
每个内存分配器都有自己的特点。TBB的可扩展分配器致力于可扩展性和速度。在某些情况下,这是有代价的:浪费虚拟空间。具体来说,当 分配9k到12k的块可它会浪费较大的空间。 52bc.net
在多线程程序中,普通的内存分配将成为严重的性能瓶颈。这是因为普通的内存分配器用一个全局锁从一个单一的全局堆中分配和释放内存块, 每个线程分配和释放内存时就会引发竞争。正是由于这种竞争,频繁内存分配的程序可能降低多核带来的优势。使用C++标准库(STL)的程序可能会更为严 重,因为它们的内存分配往往隐藏在背后。
#include <iostream> #include <tbb/parallel_for.h> #include <tbb/tick_count.h> #include <vector>
using namespace tbb; void alloctask( int ) { //vector构造和析构时会申请和释放空间 std::vector<int> data(100); } int main() { tick_count t1 = tick_count::now(); //用于记录花费的时间 parallel_for(0,100000,1,alloctask); //十万次执行alloctask(并发) tick_count t2 = tick_count::now(); std::cout << (t2-t1).seconds() << std::endl; return 0; } 你可以运行这段代码查看所花费的时间,我们可以很容易地把上面的代码修改成使用TBB可扩展内存分配器,这样速度会加快将近一倍(在我双核CPU上,预计更多内核的CPU会有更好的表现)。 内容来自52bc.net
注:如果之前看过本站关于TBB循环的 文章的朋友可能会奇怪,这里没有task_scheduler_init,而且parallel_for的参数也不一样。其实这段代码是基于最新的TBB2.2版 本的,这个版本已经不再强制要求task_scheduler_init。parallel_for也多了几个重载以方便使用。 copyright 52bc.net
“假共享”是并发程序中另一个严重问题,它常发生于当多个线程使用的内存块紧靠在一起时。在处理器内核中有一个称为“cache lines”的高速缓存区,它只能由同一个线程存取同一个缓存区,否则就会引发缓存切换,这可以轻易地造成上百时钟周期的浪费。 内容来自52bc.net
为了说明为什么假共享有这样的性能损失,我们可以看看当两个线程访问紧靠在一起的内存时引起的额外的开销。假设这个缓存区有64字节, 两个线程共享这个缓存。 52bc.net
首先,程序定义了两个数组,包含1000个float(4字节): copyright 52bc.net
float A_array [1000]; float B_array [1000];内容来自52bc.net
由于编译器的顺序分配,这两个数组很可能是紧靠在一起的。考虑下面的动作: 内容来自52bc.net
看,即使线程A和线程B使用各自的内存还是会造成极大的开销。解决方法假共享的办法是把数组按缓存边界对齐。 copyright 52bc.net
TBB的可扩展内存分配器可以用来解决上面所述的问题,TBB提供了两个分配器:scalable_allocator和cache_aligned_allocator,它们分别定义于tbb/scalable_allocator.h和 tbb/cache_aligned_allocator.h里。
本文来自52bc.net
scalable_allocator和cache_aligned_allocator与std::allocator是兼容的,我们可以和使用 std::allocator一样使用它们。下面的例子演示了使用cache_aligned_allocator作为std::vector的分配器。
copyright 52bc.net
std::vector< int, cache_aligned_allocator<int> >;copyright 52bc.net
现在我们可以把前面的代码修改一下了: 本文来自52bc.net
void alloctask( int )
{
//vector构造和析构时会申请和释放空间
std::vector<int, scalable_allocator<int> > data(100);
}
运行结果:
未使用TBB的可扩展内存分配器:0.405s
使用scalable_allocator:0.1843s
使用cache_aligned_allocator :0.187084s