TBB(Intel Threading Building Blocks)學習筆記
並行與併發是相對的,OS裏講的是併發而在
架構
方面更多的是說並行。並行是分多個層面的,我的認爲基本上能夠分爲這麼幾個層面:一、指令級的並行;即所謂的微程序、指令流水線等,如今
cp
u的一級緩存、二級緩存都很大,因此這個cache的效果仍是比較好的(基於局部性原理)二、線程級的並行;即同一個時刻多個函數在運行(如今的cpu好像都是多核的)三、服務級別的(好比一個遊戲服務器中有商店服務、也有戰鬥服務、聊天服務等 這裏的每一個服務可能對應多個邏輯線程)四、節點級別的;即所謂的分佈式系統,多個節點互相配合,使整個系統在邏輯上成爲一個單一的系統。(google、qq等這些海量訪問的服務通通是分佈式的)通常來講,第一個級別的並行直接作在硬件裏面,第二個級別的並行會有一些基礎的框架,第三和第四個級別的並行就是應用程序本身的架構的問題了。這裏面實際上有一個爭論:是在算法並行化上面花心思去研究仍是採用分佈式的框架來面對問題規模的增加?實際上2者各有利弊,前者能夠充分利用已有硬件,可是對程序員的要求較高,維護開發成本高,風險大;後者容易實現可是浪費硬件,在有些狀況下不是全部問題均可以用加個機器的方式能夠解決的(好比客戶端上的多媒體軟件,其計算量極大,總不能要求全部用戶都升級吧。)
Intel Threading Building Blocks,是爲了方便程序員使用多核處理器的C++庫,應該是對應上面提到的第二個級別的並行的。
1、TBB應該提供哪些東西?
用TBB就是爲了程序的並行化,那麼程序員須要什麼樣的支持呢?最理想的狀況是已有的代碼不做任何修改,換一個編譯器從新編譯一下就OK,如今看來這個還不太現實。要有更好的效率就須要有更多的啓發式信息,同時也就要求程序員要了解不少細節。整個程序邏輯沒辦法自動並行化,那就針對控制流進行並行化吧,因此TBB中提供了 parallel_for、parallel_while、 parallel_re
du
ce等;(這些是TBB給C++程序員的比較高層的接口)並行確定是多線程,這樣的話數據競爭問題就比較棘手,因此TBB提供併發容器;若是以爲
TBB提供的這些接口尚未辦法解決性能問題,那就能夠更深刻的研究使用mut
ex
、atomic、task等了;能夠看出,TBB從幾個層次上爲程序員提供了支持。
2、TBB提供的接口
由底層到高層,task_scheduler--------co
nc
urrent_container--------parallel_for---pipeline
簡單說,TBB幫咱們調度一個個task(比OS的調度要高效),實現高效的並行算法
3、細節
一、parallel_for 適用場合:多個數據或請求彼此沒有依賴關係,所要進行的操做是同樣的(典型SPMD)
例子:
// 典型的c++泛型編程 blocked_range 是要處理的多個數據,3個參數依次是開始的指針(迭代器)、結束指針、每一個任務分配的數據數
// parallel_forFibBody能夠簡單理解爲一個函數對象(c++裏是用運算符重載實現的,即()是通訊的接口)
parallel_for( blocked_range<int>( 1, my_n, 10 ), parallel_forFibBody(my_s
tr
eam) );
struct parallel_forFibBody {
QueueStream &my_stream;
//! fill functor arguments
parallel_forFibBody(QueueStream &s) : my_stream(s) { }
// 這裏是並行的代碼
vo
id
operator()( const blocked_range<int> &range ) const {
int i_end = range.end();
for( int i = range.begin(); i != i_end; ++i ) {
my_stream.Queue.push( Matrix1110 ); // push initial matrix
}
}
};
二、parallel_reduce 適合於須要彙總的狀況,即各個數據的結果須要彙總回來
例子:(注意分發下去和彙總回來的方法)
float ParallelSumFoo( const float a[], size_t n ) {
SumFoo sf(a);
parallel_reduce(blocked_range<size_t>(0,n,IdealGrainSize), sf );
return sf.
su
m;
}
class SumFoo {
float* my_a;
public:
float sum;
void operator()( const blocked_range<size_t>& r ) {
float *a = my_a;
for( size_t i=r.begin(); i!=r.end(); ++i )
sum += Foo(a[i]);
}
SumFoo( SumFoo& x,
split
) : my_a(x.my_a), sum(0) {} // 分發任務,注意這個構造器要求是線程安全的
void
join
( const SumFoo& y ) {sum+=y.sum;} // 收集彙總結果
SumFoo(float a[] ) :
my_a(a), sum(0)
{}
};
三、parallel_while 有時不知道循環什麼時候結束,即便用for的end未知,在這種狀況下可使用parallel_while
例子:注意pop_if_present、typedef Item* argument_type、operator()等部分的處理
// 串行版本
void SerialApplyFooToList( Item*root ) {
for( Item* ptr=root; ptr!=NULL; ptr=ptr->next )
Foo(pointer->data);
}
// 並行版本
class ItemStream {
Item* my_ptr;
public:
bool pop_if_present( Item*& item ) { // 用於提供下一個迭代器
if( my_ptr ) {
item = my_ptr;
my_ptr = my_ptr->next;
return true;
} e
ls
e {
return false;
}
};
ItemStream( Item* root ) : my_ptr(root) {}
}
class ApplyFoo {
public:
void operator()( Item* item ) const { // 要求必定是const的
Foo(item->data);
}
typedef Item* argument_type; // 此句是必須的
};
void ParallelApplyFooToList( Item*root ) {
// parallel_while是個class
parallel_while<ApplyFoo> w; // 先創建個對象
ItemStream stream;
ApplyFoo body;
// 第一個參數提供數據指針,第二個參數提供函數體
w.run( stream, body );
}
4、併發容器
大部分程序都有容器類,在多線程環境下就有數據污染的問題,爲了使併發的線程串行化,通常是使用加鎖的辦法,若是這個
容器由程序員本身來實現,難度仍是比較大的,這樣就須要有線程安全的容器類。
一、concurrent_hash_map
hash接口與stl相似
二、concurrent_vector
grow_by(n) 插入n個item(動態增加)
grow_to_at_least()設定容器的大小
size() 包括正在併發增加的部分 由於有可能會同時取,因此程序員須要本身維護本身的class的線程安全性
clear
() 不是線程安全的
三、concurrent_queue
pop_if_present(item) 非阻塞,
pop() 阻塞,
concurrent_queue::size() 負數時表示有多少個消費者在等待
set
_capacity()指定隊列大小,會使push操做被阻塞
在並行時,paralell_while pipeline 的效率要高於concurrent_queue
5、若是以爲TBB的加鎖效率不高,能夠本身控制鎖
最經常使用的是spin lock
6、整個TBB引擎的核心是 Task Scheduler(基於任務圖來實現)
提升效率的核心是threading stealing,保證cpu的效率
7、小結
要使用TBB進行並行化,首先程序員要知道哪些是能夠並行化;其次,要熟悉TBB並行化的框架(主要是泛型編程);再次,程序員要大概知道
並行算法的執行步驟;最後,利用TBB的組件,實現並行化的算法。整體上來講,仍是不太好用的
再分享一下我老師大神的人工智能教程吧。零基礎!通俗易懂!風趣幽默!還帶黃段子!但願你也加入到咱們人工智能的隊伍中來!https://blog.csdn.net/jiangjunshowhtml