OpenMP
OpenMP(Open Multi-Processing)は、共有メモリ型並列計算のためのAPI(Application Programming Interface)です。C、C++、Fortranなどのプログラミング言語で使用でき、マルチコアプロセッサを効率的に活用するための並列プログラミングを容易にします。OpenMPは、プラグマディレクティブ、ライブラリルーチン、環境変数を組み合わせて使用します。
OpenMPの基本的な特徴:
- プログラムの並列性を簡単に記述できます。
- 指示子(ディレクティブ)やライブラリ関数を使って、コードに並列処理を追加します。
- マルチプロセッサ環境でのアプリケーションのパフォーマンスを向上させるために使用されます。
環境設定
[編集]OpenMPを使用するには、対応するコンパイラが必要です。以下に各言語での環境設定方法を示します。
C/C++
[編集]Clang/GCCのインストール: 多くのUnix系システムにデフォルトでインストールされているか、パッケージ管理システムを使ってインストール可能です。
コンパイルオプション: OpenMPを有効にするためには、コンパイル時に-fopenmp
オプションを指定します。
clang -fopenmp my_program.cpp -o my_program gcc -fopenmp my_program.c -o my_program
Fortran
[編集]GFortranのインストール: GCCの一部として提供されており、多くのシステムで利用可能です。
コンパイルオプション: Fortranでも同様に-fopenmp
オプションを使用します。
gfortran -fopenmp my_program.f90 -o my_program
基本的な概念
[編集]- スレッド: プロセス内で並行して実行される軽量な実行単位です。
- 並列領域: 指定されたコードブロックが複数のスレッドによって同時に実行される領域です。
基本的なディレクティブ
[編集]並列化ディレクティブ
[編集]並列化ディレクティブは、プログラムの特定の部分を並列に実行するように指示します。
- C/C++の例
#include <omp.h> #include <stdio.h> int main() { #pragma omp parallel { int thread_id = omp_get_thread_num(); printf("Hello from thread %d\n", thread_id); } return 0; }
- Fortranの例
program hello use omp_lib implicit none integer :: thread_id !$omp parallel private(thread_id) thread_id = omp_get_thread_num() print *, "Hello from thread", thread_id !$omp end parallel end program hello
for/doディレクティブ
[編集]for/doディレクティブは、ループの反復を並列に実行するために使用されます。
- C++の例
#include <omp.h> #include <iostream> #include <vector> int main() { const int size = 1000; std::vector<int> vec(size); #pragma omp parallel for for (int i = 0; i < size; i++) { vec[i] = i * 2; } std::cout << "First element: " << vec[0] << ", Last element: " << vec[size-1] << std::endl; return 0; }
- Fortranの例
program vector_init implicit none integer, parameter :: size = 1000 integer :: vec(size), i !$omp parallel do do i = 1, size vec(i) = i * 2 end do !$omp end parallel do print *, "First element:", vec(1), ", Last element:", vec(size) end program vector_init
データ共有と同期
[編集]データ共有
[編集]- 共有変数: デフォルトで、すべてのスレッドは共有変数にアクセス可能です。
- プライベート変数: 各スレッドが独自のコピーを持つ変数。
private
キーワードを使用して指定します。
- C++の例
#include <omp.h> #include <iostream> int main() { int shared_var = 0; int private_var; #pragma omp parallel private(private_var) { private_var = omp_get_thread_num(); #pragma omp critical { shared_var += private_var; std::cout << "Thread " << omp_get_thread_num() << ": private_var = " << private_var << ", shared_var = " << shared_var << std::endl; } } std::cout << "Final shared_var = " << shared_var << std::endl; return 0; }
- Fortranの例
program data_sharing use omp_lib implicit none integer :: shared_var = 0 integer :: private_var !$omp parallel private(private_var) private_var = omp_get_thread_num() !$omp critical shared_var = shared_var + private_var print *, "Thread", omp_get_thread_num(), ": private_var =", private_var, & ", shared_var =", shared_var !$omp end critical !$omp end parallel print *, "Final shared_var =", shared_var end program data_sharing
同期
[編集]クリティカルセクション: 共有リソースへの同時アクセスを防ぐために、#pragma omp critical
(C/C++)または!$omp critical
(Fortran)を使用します。
- C++の例
#include <omp.h> #include <iostream> int main() { int sum = 0; #pragma omp parallel { #pragma omp critical { sum += 1; // スレッド間での競合を防ぐ } } std::cout << "Sum: " << sum << std::endl; return 0; }
- Fortranの例
program critical_section use omp_lib implicit none integer :: sum = 0 !$omp parallel !$omp critical sum = sum + 1 ! スレッド間での競合を防ぐ !$omp end critical !$omp end parallel print *, "Sum:", sum end program critical_section
実践的な例
[編集]ベクトル加算: OpenMPを使用して、2つのベクトルを加算するプログラムを実装します。
- C++の例
#include <omp.h> #include <iostream> #include <vector> #define N 1000 int main() { std::vector<int> a(N), b(N), c(N); // 配列の初期化 for (int i = 0; i < N; i++) { a[i] = i; b[i] = i * 2; } #pragma omp parallel for for (int i = 0; i < N; i++) { c[i] = a[i] + b[i]; } // 結果の表示(最初の10要素のみ) for (int i = 0; i < 10; i++) { std::cout << a[i] << " + " << b[i] << " = " << c[i] << std::endl; } return 0; }
- Fortranの例
program vector_addition use omp_lib implicit none integer, parameter :: N = 1000 integer :: a(N), b(N), c(N) integer :: i ! 配列の初期化 do i = 1, N a(i) = i - 1 b(i) = (i - 1) * 2 end do !$omp parallel do do i = 1, N c(i) = a(i) + b(i) end do !$omp end parallel do ! 結果の表示(最初の10要素のみ) do i = 1, 10 print *, a(i), "+", b(i), "=", c(i) end do end program vector_addition
OpenMP機能一覧
[編集]以下の表は、OpenMPの主要な機能とそのC/C++およびFortranでの表現、そして簡単な解説をまとめたものです。
OpenMP機能一覧 名称 C/C++のプラグマ Fortranのプラグマ 解説 並列領域 #pragma omp parallel !$omp parallel ... !$omp end parallel コードブロックを複数のスレッドで並列実行します。 for/doループ #pragma omp for !$omp do ... !$omp end do ループの反復を複数のスレッドに分配します。 並列for/doループ #pragma omp parallel for !$omp parallel do ... !$omp end parallel do 並列領域の生成とループの分配を同時に行います。 セクション #pragma omp sections !$omp sections ... !$omp end sections 異なるコードブロックを並列に実行します。 シングル #pragma omp single !$omp single ... !$omp end single 1つのスレッドのみが実行するブロックを指定します。 クリティカル #pragma omp critical !$omp critical ... !$omp end critical 一度に1つのスレッドのみが実行できる領域を指定します。 バリア #pragma omp barrier !$omp barrier すべてのスレッドが到達するまで待機します。 アトミック #pragma omp atomic !$omp atomic メモリ位置への原子的なアクセスを保証します。 マスター #pragma omp master !$omp master ... !$omp end master マスタースレッドのみが実行するブロックを指定します。 タスク #pragma omp task !$omp task ... !$omp end task 非構造化の並列タスクを作成します。 タスクウェイト #pragma omp taskwait !$omp taskwait 子タスクの完了を待ちます。 スレッドプライベート #pragma omp threadprivate(var) !$omp threadprivate /var/ スレッド固有のグローバル変数を宣言します。 リダクション #pragma omp parallel for reduction(op:var) !$omp parallel do reduction(op:var) 並列ループでの集計操作を効率的に行います。 スケジューリング #pragma omp for schedule(type, chunk) !$omp do schedule(type, chunk) ループ反復の分配方法を制御します。
この表は、OpenMPの主要な機能を簡潔にまとめたものです。各機能の詳細な使用方法や最適な適用シナリオについては、本章の他のセクションや公式のOpenMPドキュメントを参照してください。
OpenMPの世代ごとの機能の変遷
[編集]OpenMPは時間とともに進化し、新しい機能が追加されてきました。以下の表は、OpenMPの主要なバージョンとそれぞれで導入された重要な機能をまとめたものです。
OpenMPの世代ごとの機能の変遷 バージョン リリース年 主な新機能 OpenMP 1.0 1997 - 基本的な並列化ディレクティブ(parallel, for/do)
- データ共有属性(shared, private)
- リダクション操作
OpenMP 2.0 2002 - Fortranのサポート強化
- Nested Parallelism(ネストされた並列性)
- スケジューリング制御の改善
- 動的スレッド数の制御
OpenMP 2.5 2005 - C/C++とFortranの規格の統合
- フラッシュ操作の導入
OpenMP 3.0 2008 - タスク並列性(tasking)の導入
- ループ構造のない並列処理のサポート
- 新しいスケジューリング形式(auto)
OpenMP 3.1 2011 - アトミック構造の拡張
- final節とmergeable節の導入
OpenMP 4.0 2013 - アクセラレータデバイス(GPUなど)のサポート
- SIMD構造の導入
- エラー処理の改善
- スレッドアフィニティ制御
OpenMP 4.5 2015 - タスク依存関係の改善
- デバイス構造の拡張
- ループ指示文の拡張(SIMD強化)
- 配列区分のサポート
OpenMP 5.0 2018 - タスク並列性の強化(taskloop構造)
- メモリ管理の改善
- ループ構造の強化(collapse節の拡張)
- アフィニティポリシーの改善
OpenMP 5.1 2020 - ループ変換指示文の導入
- デバイスメモリ管理の改善
- 条件付き修飾子の導入
- 新しい組み込み関数の追加
OpenMP 5.2 2021 - メモリ管理指示文の強化
- 新しいループ変換指示文の追加
- 組み込み関数の拡張
- デバイス構造のさらなる改善
OpenMP 6.0 2024(予定) - フリーエージェントスレッドのサポート(タスク実行の改善)
- デバイスサポートの改善(coexecuteディレクティブの導入)
- C23およびC++23の完全サポート
- ループ変換の拡張(fusion, reversal, interchangeの確実な適用)
- 非推奨機能の削除(OpenMP 5.0, 5.1, 5.2で非推奨とされた機能)
この表は、OpenMPの主要なバージョンとそれぞれで導入された重要な機能を時系列で示しています。各バージョンで、並列処理能力が向上し、新しいハードウェアや並列プログラミングのニーズに対応するための機能が追加されていることがわかります。
特に注目すべき点:
- OpenMP 3.0でのタスク並列性の導入
- OpenMP 4.0でのアクセラレータデバイス(GPU)サポート
- OpenMP 5.0以降でのタスク並列性とメモリ管理の大幅な強化
- OpenMP 6.0でのフリーエージェントスレッドの導入とC23/C++23の完全サポート
この変遷を理解することで、OpenMPの発展と現在の能力をより深く把握することができます。また、将来のバージョンでどのような機能が追加される可能性があるかを予測する手がかりにもなります。OpenMP 6.0は2024年11月にリリース予定であり、エクサスケールコンピューティングに向けた重要な一歩となることが期待されています。
まとめ
[編集]OpenMPは、共有メモリシステムでの並列プログラミングを簡素化する強力なツールです。本章では、基本的なディレクティブから高度な機能まで、OpenMPの主要な概念を紹介しました。C、C++、Fortranの各言語でのコード例を通じて、OpenMPの使用方法を実践的に学びました。
これらの機能を適切に使用することで、マルチコアプロセッサの性能を最大限に引き出すことができます。実践的な経験を積むことで、より効率的な並列プログラムを開発する能力が向上していくでしょう。さらに高度な機能(スレッドプールや動的スケジューリングなど)を利用することで、複雑な並列処理のニーズにも対応可能です。
この入門ガイドを通じて、OpenMPの基本的な使い方を理解し、実際のアプリケーションに活かせることを願っています。並列プログラミングの世界は広大で、常に新しい技術や最適化手法が登場しています。継続的な学習と実践を通じて、並列プログラミングのスキルを磨いていくことをお勧めします。