本文章是由機器翻譯。

使用 C ++ 的 Windows

Visual C ++ 2010 及: 平行模式的程式庫

Kenny Kerr

本專欄根據的 Visual Studio 2010 搶鮮版所撰寫。 所有的資訊有變更。

內容

語言的增強功能
平行演算法
工作和工作群組

Visual C ++ 在 2010 版本的 Visual Studio 中取得主要的升級。 許多新的語言和程式庫的功能被設計完全,讓更容易且更自然表達您的渴望,在程式碼中。 但這些功能的組合,一直是 C ++ 的是讓 C ++ 這類功能強大且具表達力的語言。

因此這個月我打算介紹某些 Visual C ++ 已加入做為即將推出的 C 的一部分的 C ++ 語言新增項目 ++ 0x 標準。 我將再探討, 平行模式的程式庫 (PPL),Microsoft 已開發超過 C ++ 0x 標準會引進平行處理原則方式自然補充 Standard C ++ 程式庫的應用程式。

語言的增強功能

可能 2008 文件 」 中 C ++ Plus: Beef Up Windows 應用程式,與 Visual C ++ 2008 Feature Pack",原先已引進 Visual C ++ 2008 Feature Pack 現在技術報告 1 (TR1) 的一部分隨附 Visual Studio 2008 的 SP1,我會引入新增項目至 Standard C ++ 程式庫。 本文我將示範透過函式樣板類別和繫結樣板函式的函式物件的支援。 能夠 polymorphically 處理函式,解決許多的 C ++ 開發人員經常碰到寫入,或使用泛型演算法時拙劣的問題。

為一的概述拖曳到這裏] 是的方式您函式以初始化物件在標準加上演算法範例:

function<int (int, int)> f = plus<int>();
int result = f(4, 5);

您可以繫結函式的協助,轉換函式會提供所需的功能,但沒有完全正確的簽名碼。

在下列範例中,我使用繫結函式與預留位置來初始化成員函式,函式物件:

struct Adder
{
   int Add(int x, int y, void* /*reserved*/)
   {
       return x + y;
   }
};

Adder adder;
function<int (int, int)> f = bind(&Adder::Add, &adder, _1, _2, 
    static_cast<void*>(0));
int result = f(4, 5);

有兩個問題會產生無法輕易地語言的增強功能不克服的這些程式庫加入的使用的。 初學者通常很沒有效率,明確地定義 T: 函式物件,它會加入某些編譯器無法否則避免的額外負荷。 它可以也是很多餘,而且瑣碎 re-declare 函式原型,編譯器會清楚地知道簽名碼時,最符合初始設定運算式。

這是新的自動關鍵字,協助您。 它可以用來代替明確定義一個變數 metaprogramming 的範本中有用的型別的特定型別是難定義或表示複雜。 這裡的它樣子:

auto f = plus<int>();

函式本身的定義也可以獲益一些改進。 通常您可以重複使用例如 Standard C ++ 程式庫中的的有用演算法。 更經常個不,但是,您需要撰寫非常特定目的不是一般可重複使用某些網域特定的函式。

但是,因為函式必須其他地方定義,您就必須考慮邏輯和實體設計。 那不是很棒如果定義無法放置在最需要的地方的位置,簡化程式碼的瞭解改善的邏輯,位置,並改善整體設計的封裝嗎? Lambda 運算式的可允許完全的:

auto f = [](int x, int y) { return x + y; };

Lambda 運算式會定義有時候稱為 closure 的未命名的函式物件。 [] 會是提示,告訴編譯器開始 Lambda 運算式。 這會稱為 Lambda 的 introducer,,後面參數清單。 雖然它通常省略編譯器可以明確,推算型別,因為是種情形在先前的程式碼片段時,這個參數的宣告也可以包含在傳回的型別。 事實上,如果函式以不接受任何參數,可以省略參數清單本身。 Lambda 運算式會結束以大括號內的 C ++ 陳述式的任何數字。

Lambda 運算式也可以擷取變數的範圍內,在其中定義 Lambda 運算式的函式。 以下是使用 Lambda 運算式來計算在容器中值的總和的一個範例:

int sum = 0;
for_each(values.begin(), values.end(), [&sum](int value)
{
    sum += value;
});

授與,這無法有已執行更簡潔使用 Accumulate 函式,但是,會遺失點。 這個範例會示範如何是擷取參考的 sum 變數,並將其函式中使用。

平行演算法

在 PPL 引進了一組工作導向平行處理原則的建構,以及平行演算法類似於可用 OpenMP 今天的數。 PPL 演算法但是,,撰寫使用 C ++ 樣板,而非 pragma 指示詞,而且的結果會是更具表達力,和彈性。 在 PPL 是,但是,基本上不同 OpenMP,PPL 提升一組基本型別和更 composable 和一組模式中重複使用的演算法的。 同時,OpenMP 是原本就更宣告式和明確的問題,例如排程的而且最後不是適當的 C ++ 的一部分。 在 PPL 也建置上並行執行階段的最上層根據相同的執行階段其他程式庫讓更可能的互通性。 讓我們來查看 [PPL 演算法,並再請參閱如何您可以使用基礎的功能,直接的工作為導向的平行處理。

在這個資料行的 10 月 2008 (」 探索高效能的演算法「),我示範有效率的演算法的優點,以及位置和快取意識的設計,在效能上的效果。 我會顯示將大型的影像轉換為灰階演算法的效率不佳、 單一執行緒如何在仍然只使用單一執行緒有效率的實作所花費的只是 2 秒時花費 46 的秒。 與在 OpenMP 我的小 sprinkling 無法平行處理 Y 座標軸上的演算法中,並減少更進一步的時間。 [圖 1 會顯示在 OpenMP 演算法,程式碼。

[圖 1 的灰階演算法使用 OpenMP

struct Pixel
{
    BYTE Blue;
    BYTE Green;
    BYTE Red;
    BYTE Alpha;
};

void MakeGrayscale(Pixel& pixel)
{
    const BYTE scale = static_cast<BYTE>(0.30 * pixel.Red +
                                         0.59 * pixel.Green +
                                         0.11 * pixel.Blue);

    pixel.Red = scale;
    pixel.Green = scale;
    pixel.Blue = scale;
}

void MakeGrayscale(BYTE* bitmap,
                   const int width,
                   const int height,
                   const int stride)
{
    #pragma omp parallel for
    for (int y = 0; y < height; ++y)
    {
        for (int x = 0; x < width; ++x)
        {
            const int offset = x * sizeof(Pixel) + y * stride;

            Pixel& pixel = *reinterpret_cast<Pixel*>(bitmap + offset);

            MakeGrayscale(pixel);
        }
    }
}

在 PPL 包含平行的演算法,可以很自然,與從 Lambda 運算式的一些說明,取代使用的 OpenMP 從 MakeGrayscale 函式中 [圖 1 :

parallel_for(0, height, [&] (int y)
{
    for (int x = 0; x < width; ++x)
    {
        // omitted for brevity
    }
});

您可以看到的迴圈,以及在 OpenMP Pragma 已經取代 parallel_for 函式。 函式的前兩個參數會定義反覆項目的範圍,就像先前的 for 迴圈的。 不像 OpenMP 的將大量的限制,對指示詞,parallel_for 樣板函式讓您可以為例,逐一查看更複雜的 Iterator,從標準的容器或不帶正負號的型別。 最後一個參數會是函式物件,我為 Lambda 運算式所定義。

您會發現,Lambda introducer 包含連字號,不需要明確宣告任何變數,來擷取。 這會告訴來擷取所有可能的變數參考編譯器。 因為 Lambda 運算式中的陳述式會使用許多變數,我會使用此為一個縮寫。 請小心,但是,因為編譯器無法立即最佳化的所有未使用的變數,導致不良的執行階段效能。 我無法明確地擷取我需要使用下列清單中,擷取的變數:

 [&bitmap, width, stride]

就像在 parallel_for 函式會是一個平行替代方式,提供平行反覆項目的索引在範圍內的迴圈,the PPL 也提供 parallel_for_each 樣板函式做為標準 for_each 函式在平行替代方案。 它會提供平行反覆運算項目,例如所提供的標準的容器的 Iterator 一組所定義的資料範圍內。 雖然它進行更多適合使用 parallel_for 函式,以明確的索引前一個範例,通常是更自然會使用 Iterator,來定義某個範圍的元素。 您無法指定數字的陣列,方形,如下所示使用 parallel_for 函式其值:

array<int, 5> values = { 1, 2, 3, 4, 5 };

parallel_for(0U, values.size(), [&values] (size_t i)
{
    values[i] *= 2;
});

但這種方法的是過的詳細資訊,需要陣列本身要擷取的 Lambda 運算式,並,視容器 (Container) 的類型,可能會沒有效率。 parallel_for_each 函式可以剛好解決這些問題:

parallel_for_each(values.begin(), values.end(), [] (int& value)
{
    value *= 2;
});

如果您只是要平行,執行許多函式,或為平行為可能根據數目的可用的硬體執行緒,您可以使用 parallel_invoke 樣板函式。 有可用的任何位置從 2 接受 10 個函式物件的多載。 以下是以平行方式執行 3 Lambda 函式的範例:

combinable<int> sum;

parallel_invoke([&] { sum.local() += 1; },
                [&] { sum.local() += 2; },
                [&] { sum.local() += 3; });

int result = sum.combine([] (int left, int right)
{
    return left + right;
});

ASSERT(6 == result);

這個範例也會說明在 PPL 所提供的另一個 Helper 類別。 可組合的類別可讓您結合許多平行工作的結果最少的鎖定相當輕鬆。 藉由提供值的本機複本,每個執行緒,並再平行工作已完成後,只能結合每個執行緒的結果中,可組合的類別可避免大部分的鎖定通常中這種情況下所發生的。

工作和工作群組

您可以直接使用任意一個簡單的工作為導向 API 的方式,方法是,我所討論的演算法的實際的平行處理。 工作定義與 task_handle 類別初始化為函式物件。 工作會一起執行工作,並等候它們完成一個 task_group 類別分組。 不用說,task_group 提供有用的多載,以便在許多情況下您甚至不必自己定義 task_handle 物件而且可以讓 task_group 物件配置,並為您管理其存留期。 以下是如何使用一個 task_group 以取代前一個範例 parallel_invoke 函式的範例:

task_group group;
group.run([&] { sum.local() += 1; });
group.run([&] { sum.local() += 2; });
group.run([&] { sum.local() += 3; });
group.wait();

除了演算法和我所討論的 API 中,其他平行演算法和 Helper 類別也可能包含與 Visual C ++ 2010 最後釋放平行模式程式庫時。如果要維持最新的最新的並行存取,請造訪平行程式設計,在原生程式碼中.

您問題或意見寄至mmwincpp@Microsoft.com.

Kenny Kerr 會是一個軟體工匠,專精於 Windows 的軟體開發。他有熱情,以寫入的教導有關程式設計和軟體設計的開發人員。到達 Kenny 在weblogs.asp。net / kennykerr.