在C++中使用Lambda函數提高代碼效能

來源:互聯網
上載者:User

使編譯器以及作業系統從正在建立的應用中榨取更高效能的關鍵在於提供充足的有關代碼意圖的資訊。在充分瞭解這個代碼意圖實現的功能等資訊的情況下, 就有可能將代碼在編譯時間和運行時的並行輸送量最大化,令開發人員可以將更多精力放在他們所關注的商業領域的問題,將重量級的多核多處理器的任務計劃交託給編 譯器,執行階段程式庫以及作業系統中的基礎設施代碼來處理。

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

std::vector<int> 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<int> 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<int> 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<int> 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函數將是具體實現這些提升的重要手段之一。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.