在C++中使用Lambda函式提高程式碼效能

iDotNetSpace發表於2009-05-19

使編譯器以及操縱系統從正在建立的應用中榨取更高機能的樞紐在於提供充足的有關程式碼意圖的資訊。在充分了解這個程式碼意圖實現的功能等資訊的情況下, 就有可能將程式碼在編譯時和執行時的並行吞吐量最大化,令開發者可以將更多精力放在他們所關注的貿易領域的題目,將重量級的多核多處理器的任務計劃交託給編 譯器,執行時庫以及操縱系統中的基礎舉措措施程式碼來處理。


輪迴函式是很重要的一個環節,由於在所有可用的硬體資源中,被分離的輪迴中的各個部門在一般情況下能夠提供更高的應用機能。考慮這樣一個小情況:迭代選定組合中的全部元素以求得總和。最簡樸最直接的執行方法如下:


std::vector v;
v.push_back(1);
v.push_back(5);
int total = 0;
for (int ix = 0; ix < v.size(); ++ix){
total += v[ix];
}


以上的例子十分便於人工讀寫。對於認識C語言家族語法的開發者而言,這個輪迴的意圖也十分輕易理解。然而對於編譯器以及執行時庫的組合而言,要在多個執行緒之間計劃好這個輪迴,它還需要類似於OpenMP編譯指示一類的指示來告訴它哪裡有優化的空間:


std::vector v;
v.push_back(1);
v.push_back(5);
int total = 0;
#pragma omp for
for (int ix = 0; ix < v.size(); ++ix){
#pragma omp atomic
total += v[ix];
}


第一個OpenMP指示提出了多執行緒執行for輪迴的要求,而第二個omp atomic指示則被用來防止多執行緒同時向總數變數上寫入。對於OpenMP,在MSDN庫的參考文件中有關於所有指示的具體先容。


假如使用了宣告式輪迴技巧,那麼將並行方法應用在向量乞降上則更加乾淨簡樸。STL for_each函式是一個理想的替換品,以上的例子則被改寫如下:


class Adder{
private:
int _total;
public:
Adder() : _total(0) {}

void operator ( ) ( int& i )
{
  _total += i;
}

operator int ( )
{
  return  _total;
}
};

void VectorAdd()
{
std::vector v;
v.push_back(1);
v.push_back(5);
int total = std::for_each(v.begin(), v.end(), Adder());
}
這裡,詳細的for輪迴被捨棄,求向量和的程式碼變得乾淨了一些;但是因為需要使用一系列執行符來定義一個類,這使得這個解決方案被大大的複雜化了。 除非程式碼庫中還有大量類似的乞降宣告,否則一個開發者是不會僅僅為了STL for_each的那點好處而多花費功夫去定義一個新類的。


仔細檢查這個Adder類,可以很顯著的看出其大部門內容都僅僅是用來知足將例項用作函式物件的呼叫前提的。這個類中獨一起到計算作用的僅僅是那一 行_total += i。考慮到這一點,C++ 0x提供了一個被大大簡化了的、以lambda函式方式來實現的語法技巧。Lambda函式移除了對這些搭架子程式碼的需求,並答應在另外的一個宣告中定義 一個謂詞函式。由此,VectorAdd函式可以被改寫如下:


std::vector v;
v.push_back(1);
v.push_back(5);
int total = 0;
std::for_each(v.begin(), v.end(),
[&total](int x) {total += x;}
);


Lambda函式的語法相稱直截了當。方括號中的第一個lambda元素告訴編譯器,本地變數total通過引用被捕獲(這樣的情況下最好用引用捕 捉,由於你需要向量和的結果在for_each之後仍舊有效),而lambda的第二部門則是引數列表。Lambda的最後一部門是函式的主體,這個例子 中就是將引數x的值加到變數total中去。


假如在lambda函式中沒有需要捕獲的變數,或者只需要捕獲變數的一個副本,那麼函式開始的方括號可以留空:


std::for_each(v.begin(), v.end(), [](int x) {
std::cout << x << std::endl;
});


混合的捕獲方法也可以使用:


int total = 0;
bool displayInput = true;
std::for_each(v.begin(), v.end(), [&total, displayInput](int x) {
total += x;
if (displayInput){
  std::cout << x << std::endl;
}
});


這裡,變數displayInput通過副本被捕獲。Visual C++編譯器在編譯時會報錯C3491:'displayInput':一個在lambda函式內數值被改變的變數無法在一個非可變lambda中通過數值被捕獲。


Lambda函式中還有一個值得留意的地方,就是它的返回值型別。編譯器一般會盡可能的(也是被要求的)推斷lambda表示式的返回值型別,不外 對於複雜的多行表示式而言,有可能會需要切當的宣告返回值型別。返回值型別宣告通過在lambda函式引數和函式主體之間新增-﹥執行符以及需要被宣告的 返回值型別來實現:


std::for_each(v.begin(), v.end(),
[&](int x)->void {total += x;});
}


C++中有了lambda函式,這令宣告式程式設計以及使用STL運演算法則變得更加簡潔。Lambda函式答應在函式主體內的可執行程式碼字行間進行定義。在為 編譯器提供強盛的優化提示之外,Lambda函式所推崇的程式碼模式可以令人更加簡樸的理解哪段程式碼是要實現怎樣的功能。Visual C++ 2010將帶來在並行處理上的明顯功能晉升,而lambda函式將是詳細實現這些晉升的重要手段之一。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/12639172/viewspace-600453/,如需轉載,請註明出處,否則將追究法律責任。

相關文章