Parallel STL: C++ STL コードのパフォーマンスの向上

同カテゴリーの次の記事

OpenMP* 誕生から 20 年

この記事は、インテルの The Parallel Universe Magazine 28 号に収録されている、新機能の Parallel STL を利用してパフォーマンスを向上する方法を紹介した章を抜粋翻訳したものです。

コンピューティング・システムは、シングルスレッドと SISD アーキテクチャーから最新のマルチコア/ メニーコアと SIMD アーキテクチャーへと急速に発展し、さまざまな分野およびフォームファクターで利用されています。C++ は、これらの最新のシステムで広く利用されているパフォーマンス指向の汎用言語です。しかし、最近まで、これらの最新のシステムをフルに活用する標準化された手段は提供されていませんでした。最新バージョンの C++ でも、並列処理を引き出す機能は制限されています。これまで、各ベンダーは、並列処理をサポートするさまざまな仕様、手法、ソフトウェアを開発してきました1 (図 1)。C++ 標準規格の次のバージョン (C++17) では、スレッド化やベクトル化のようなハードウェア機能を活用できるように、既存のシーケンシャル C++ コードを並列コードに変換する Parallel STL が追加される予定です。


図 1. C++ における並列処理のサポート

Parallel STL

Parallel STL は、実行ポリシー引数で C++ 標準テンプレート・ライブラリーを拡張します。実行ポリシーは、STL アルゴリズムの関数多重定義を一義化するため固有の型として使用される C++ クラスです。便宜上、C++ ライブラリーはポリシー引数として使用できる各クラスのオブジェクトも定義します。ポリシーは、従来のアルゴリズム (transform、for_each、copy_if など) や新しいアルゴリズム (reduce、transform_reduce、スキャンのバリエーション [prefix sum] など) とともに使用することができます。並列実行ポリシーのサポートは、並列処理の C++ 拡張技術仕様 (Parallelism TS) として数年にわたり開発されてきましたが、C++17 標準規格ドラフトに含まれました (ドキュメント n464022)。ベクトル化ポリシーのサポートは、Parallelism TS の 2 つ目のバージョンで提案されています (ドキュメント p007533 および p007644)。全体的に、これらのドキュメントは 5 つの異なる実行ポリシーを説明しています (図 2)。

  • sequenced_policy (seq) クラスはアルゴリズムの実行が並列化されないことを示します。2
  • parallel_policy (par) クラスはアルゴリズムの実行が並列化されることを示します。2 実行中に呼び出されるすべてのユーザー指定関数にデータ競合が含まれていてはなりません。
  • parallel_unsequenced_policy (par_unseq) クラスは実行が並列化およびベクトル化されることを示します。2
  • unsequenced_policy (unseq) クラスは実行ポリシーの Parallelism TS v24 における提案で、アルゴリズムの実行はベクトル化されるが並列化されないことを示します。提供されるすべての関数が SIMD セーフである必要があります。
  • vector_policy (vec) クラスは実行ポリシー型の提案4 で、要素間の前方依存性を保つ方法でベクトル 化されることを示します。


図 2. C++ 標準テンプレート・ライブラリーの実行ポリシー

図 2 は、これらの実行ポリシー間の関係を示しています。より上位の実行ポリシーは多くのことを行うことができますが、ユーザーコードに追加する要件も多くなります。Parallel STL の実装は、図の下に示す限定的な実行ポリシーの代わりに使用することができます。

単純な等価の STL と Parallel STL アルゴリズムは次のように記述することができます。

#include <execution>
#include <algorithm>

void increment_seq( float *in, float *out, int N ) {
    using namespace std;
    transform( in, in + N, out, []( float f ) {
        return f+1;
    });
}

void increment_unseq( float *in, float *out, int N ) {
    using namespace std;
    using namespace std::execution;
    transform( unseq, in, in + N, out, []( float f ) {
        return f+1;
    });
}

void increment_par( float *in, float *out, int N ) {
    using namespace std;
    using namespace std::execution;
    transform( par, in, in + N, out, []( float f ) {
        return f+1;
    });
}

ここで、

std::transform( in, in + N, out, foo );

は次のループと同じくらい単純です。

for (x = in; x < in+N; ++x) *(out+(x-in)) = foo(x);

また、

std::transform( unseq, in, in + N, out, foo );

は次のループと同じくらい単純です (今回の実装は最内レベルで #pragma omp simd を使用していますが、異なるアプローチを使用して unseq ポリシーを実装する Parallel STL に置き換えることもできます)。

関連記事

  • インテル Parallel Universe 28 号日本語版の公開インテル Parallel Universe 28 号日本語版の公開 インテル Parallel Universe マガジンの最新号が公開されました。この号では、次の C++ 標準 (C++17) で実装される Parallel Standard Template Library (PSTL) の概要とその使用法を示すサンプルコードを紹介します。また、誕生から 20 周年を迎えた […]
  • マルチスレッド開発ガイド: 1.2 ループの最適化によるデータの並列パフォーマンスの強化マルチスレッド開発ガイド: 1.2 ループの最適化によるデータの並列パフォーマンスの強化 データ並列アプリケーションでは、独立した同じ演算が異なるデータを繰り返し実行します。 データ並列を行うループは、通常、計算量の最も多いコード領域であるため、ループを最適化することで直接パフォーマンスに効果をもたらします。入れ子構造のループでは、スレッドに割り当てる計算粒度が直接パフォーマンスに影響します。入れ子構造 […]
  • インテル Parallel Universe 26 号日本語版の公開インテル Parallel Universe 26 号日本語版の公開 インテル Parallel Universe マガジンの最新号が公開されました。この号では、インテル® Xeon Phi™ プロセッサー向けのコードの現代化について説明します。また、インテル® Parallel Studio XE 2017 […]
  • マルチスレッド開発ガイド: 4.6 インテル® Parallel Composer を利用して並列コードを開発するマルチスレッド開発ガイド: 4.6 インテル® Parallel Composer を利用して並列コードを開発する コードの並列化にはさまざまな手法があります。この記事では、インテル® Parallel Composer で利用可能な手法の概要を説明し、各手法の主な長所を比較します。インテル® Parallel Composer は Windows* 上の C/C++ を使用した開発のみを対象としていますが、これらの手法の多くは Fortran や […]
  • C++11 とインテル® TBB スレッドの併用C++11 とインテル® TBB スレッドの併用 この記事は、インテル® デベロッパー・ゾーンに掲載されている「Mixing C++11 and TBB Threads」 (http://goparallel.sourceforge.net/mixing-c11-tbb-threads/) の日本語参考訳です。 インテル® TBB […]