C++Builderでは、以下の3つのレベルでの開発が可能です。
- コンポーネント(VCL および FMX)
- 共通ライブラリ(RTL)
- プラットフォームAPI (iOS, Android, macOS)
このブログ記事では、共通ライブラリ(以下、RTL)について説明していきます。
C++Builderには、低レベルおよび高レベルの I/O、文字列やファイルの操作、メモリ割り当て、プロセス制御、データ変換、数学的計算など、C/C++プログラム内から呼び出すことができる様々なタスクを実行するための数百の関数、マクロ、およびクラスが用意されています。
C++Builderのランタイムライブラリ(RTL) は、VCLおよびFireMonkeyコンポーネントライブラリを根底から支えるコアライブラリで、多数のヘッダーファイルで構成されています。RTLには、グローバルルーチン、ストリームやリストなどのユーティリティクラス、TObject、TPersistent、TComponentなどの基本クラスが含まれています。
RTLは、 VCLおよびFireMonkey と密接に関連していますが、ツールパレットのコンポーネント一覧には表示されません。
ただ、RTLのクラスとルーチンは、ツールパレットに表示されるビジュアルコンポーネントによって内部的に使用されているので、VCLプロジェクトまたはFireMonkeyプロジェクトのアプリケーションコードや、開発者が独自に作成するコンポーネント(クラス)のコードで使用することができます。
例えば、Systemヘッダーには、ほとんどのRTLが含まれております。
以下は、RTLに含まれているヘッダーファイル名で、ここではその機能の一部を紹介いたします。
- System.Math.hpp 数学演算、ベクタ、マトリックスに関連する、クラス、ルーチン、型、変数、定数の定義
- System.Bluetooth.hpp アプリケーションの動作デバイスの Bluetooth 機能を使用して、リモート デバイスで動作するアプリケーションに接続するためのクラスを提供
- System.Sensors.hpp センサ(アプリケーションで物理的な量を測定するためのハードウェアまたはソフトウェア)を管理したり情報を取得するためのクラスやコンポーネントを提供
- System.Threading.hpp 並列プログラミングライブラリを実装したクラスと型の定義
今回ブログで紹介しているサンプルプロジェクトは、こちらからダウンロードできます。
このサンプルコードは、C++Builder 10.4で作成したマルチデバイスアプリケーションのプロジェクトです。
ここでは、C++BuilderのマルチデバイスアプリケーションからRTLのSystem.Threadingヘッダと並列プログラミングライブラリを使用するアプリケーションの例を紹介します。以下のコードのように必要なライブラリのヘッダファイル(.hpp)を定義します。
1 2 3 |
#include <System.Threading.hpp> // TParallel::For #include <System.Diagnostics.hpp> // System::Diagnostics::TStopwatch #include <System.SyncObjs.hpp> // TInterlocked::Increment |
このコード例では、並列プログラミングライブラリのTParallel.Forメソッドの使用方法を示しています。TParallel.Forはforループを並列に実行する部分に分割し、それらを実行するためにタスクを使用します。
このアプリケーションでは、1からMAX値(500万)までの素数を次の2種類の方法で求め、その処理時間を出力します。
- 従来のforループを使用して実行
- TParallel.Forメソッドを使用した並列バージョンで実行
上記のそれぞれ方法で、同じ計算を行い、測定にかかる時間を比較してみたいと思います。
アプリケーションのフォーム上の[For Loop]ボタンを押すと、素数計算の従来forループ版が起動します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
void __fastcall TForm2::btnForLoopClick(TObject *Sender) { int Max = 5000000; // 5 Million Tot = 0; System::Diagnostics::TStopwatch sw = System::Diagnostics::TStopwatch::Create(); sw.Start(); for (int I = 1;I <= Max;I++) { if (IsPrime(I)) { Tot++; } } sw.Stop(); Memo1->Lines->Add (String().sprintf (L"Sequential For loop. Time (in milliseconds): %lld, Primes found: %lld", sw.ElapsedMilliseconds, Tot)); } |
そして[Parallel Loop]ボタンを押すと、素数計算のTParallel.For並列版が起動します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
void __fastcall TForm2::btnParallelLoopClick(TObject *Sender) { int Max = 5000000; // 5 Million Tot = 0; System::Diagnostics::TStopwatch sw = System::Diagnostics::TStopwatch::Create(); sw.Start(); // In C++, the IsPrime function passes to the TParallel.For as an Iterator Event. TParallel::For(NULL, 1, Max, MyIteratorEvent); // Second option is to use a C++11 Lambda for the Parallel::For /* TParallel::For(NULL, 1, Max, System::Sysutils::_di_TProc__1( // [this] (int AIndex) { [&](int AIndex) { if (IsPrime(AIndex)) {TInterlocked::Increment(Tot);};})); */ sw.Stop(); Memo1->Lines->Add (String().sprintf (L"Parallel For loop. Time (in milliseconds): %lld, Primes found: %lld", sw.ElapsedMilliseconds, Tot)); } |
TMemoコントロールには、 for (int I = 1; I <= Max; I++) を使用した従来のfor ループ版と TParallel::For メソッドを使用した並列for loop版の計算時間が表示されます。
上図の計算時間の結果を見ればわかるように、1から 500 万までの素数を計算するのに従来のforループ版は、814ミリ秒かかりましたが、TParallel::For メソッドを使って同じことを実行すると、なんと 267ミリ秒しかかかりませんでした。
TParallel :: Forメソッドの方がより効率的で、プロシージャを並列実行するために空いているCPUリソースを利用するため、アプリケーションをより速く実行できることは明白だと思います。
このブログで確認したWindowsの仮想マシンでは、4つのプロセッサコアを使用しているため、TParallel :: Forメソッドを使用すると、アプリケーションは基本的に4倍高速に実行されます。つまりプロセッサコアの数が多いほど、TParallel :: Forメソッドを使用したアプリケーションは、高速に実行することができます。
コードを見ると、このアプリケーションの実装方法がわかります。
コードエディタ内の#include <System.Threading.hpp>にマウスカーソルを合わせて右クリックし、[カーソル位置のファイルを開く(Ctrl+Enter)]を選択すると、C:Program Files(x86) EmbarcaderoStudio21.0 includewindowsrtl System.Threading.hppファイルが開きます。
次に、コードエディタの上部にあるメソッドインサイトで”For”の文字列を入力します。
メソッドインサイトの画面で、TParallel.Forを使用している43個のメソッドが見つかります。最初の1つを見てみると、このSystem.Threading.TParallel.Forヘッダーには、C++開発に非常に適したオーバーロードされた同名メソッドの引数が多数含まれています。
並列プログラミングライブラリには、ループを並列実行できるTParallel :: Forというループ関数が含まれています。
なお、ループを並列実行する目的のために開発者は難しい管理は必要ありません。これらの管理はすべてライブラリ側で(CPUの負荷に基づき)自動的にスレッドプールの調整が行われます。開発者がスレッドを明示的に作成したり、それを管理する必要はありません。
開発者が並列プログラミングライブラリをアプリケーションで使用するには、System.Threading.hppヘッダを含めるだけです。このヘッダーには、新規および既存の C++Builderプロジェクトに組み込むことができるいくつかの機能があります。
アプリケーションのフォーム画面に戻り、[For Loop]ボタンをクリックすると、従来のforループ関数が使用されていることがわかり、1から500万までの数が素数であるかどうかを確認します。
数字が素数かどうかを調べるために、アプリケーションではIsPrime関数を使用しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
bool __fastcall TForm2::IsPrime(int N) { int Test, k; bool aPrime; if (N 1; } else if (((N % 2) == 0) || ((N % 3) == 0)) { return false; } else { aPrime = true; k = (int)sqrt(N); Test = 5; while (Test <= k) { if (((N % Test) == 0) || ((N % (Test + 2)) == 0)) { aPrime = false; break; // jump out of the for loop } Test = Test + 6; } return aPrime; } } |
次に、[Parallel Loop]ボタンをクリックすると、RTLのTParallel::Forメソッドを使用していることがわかります。 C++では、IsPrime関数をTParallel::Forメソッドに、イテレータ(Iterator)イベントまたはC++ 11のラムダ式として渡す必要があります。
イテレーター(Iterator )については、以下のようにIsPrime 関数を Iteratorイベントとして実装しました。
1 2 3 4 5 6 7 8 9 10 11 12 |
// Parallel For Iterator Event Proc void __fastcall TForm2::MyIteratorEvent(TObject* Sender, int AIndex) { // The Iterator Event implementation code is as follows. // In C++ you create an Iterator event function or C++11 lambda and // pass that as part of the TParallel::For loop call. // // Within parallel loops, you must transform your operations into thread-safe operations. // Here we use a member of the RTL System.SyncObjs header, the TInterlocked method. if (IsPrime(AIndex)) { TInterlocked::Increment(Tot); } } |
2 番目のオプションは、C++のIsPrime 関数を TParallel.For メソッドに渡すために、C++11のラムダ式を Parallel::For に使用することです。ここにラムダ式[&](int AIndex)を示します。これは ref で変数を捕捉しています。
1 2 3 4 5 |
// Second option is to use a C++11 Lambda for the Parallel::For TParallel::For(NULL, 1, Max, System::Sysutils::_di_TProc__1( // [this] (int AIndex) { [&](int AIndex) { if (IsPrime(AIndex)) {TInterlocked::Increment(Tot);};})); |
上記のコード例のようにラムダ式を使って同じアプリケーションを実行しても、同じ結果が得られます。
サンプルのアプリケーションを実行してみると、並列処理の実行に使用可能なCPUを利用するため、TParallel::Forの方がより効率的であることがわかりました。また、このアプリケーションでは、ラムダ式が利用できるためC++BuilderではC++ 11をサポートしていることもわかります。
結論として、C++Builder RTL には並列プログラミングライブラリが含まれています。
並列プログラミングライブラリを利用する最大の目的の1つは、Parallel.Forメソッドを使用してループを高速化することです。
C++Builder RTLの一部でSystem.Threadingヘッダーをもつ並列プログラミングライブラリは、複数の CPU デバイスやコンピュータにまたがる作業を利用して、アプリケーションでタスクを並列実行する機能も提供します。このサンプルアプリケーションで紹介した TParallel::Forメソッドだけでなく、タスクの実行、タスクの結合、タスクのグループの待機など、多くの高度な機能が含まれています。
以下は、並列プログラミングライブラリの理解をより深めるためにとても有用な参考情報です。
チュートリアル:並列プログラミング ライブラリの for ループを使用する
ビデオ: How to use the Parallel Prime for C++ Builder VCL.
Design. Code. Compile. Deploy.
Start Free Trial Upgrade Today
Free Delphi Community Edition Free C++Builder Community Edition