はじめに
情報産業の進歩は目覚しいものがあります。特にCPUの進化は著しく、ムーアの法則が指し示すとおりに急激に進化してきました。しかし2006年頃、ついにシングルプロセッサでの性能向上に限界が見え、マルチコア時代に突入しました。筆者が聞くところによると、これからはコア数を上げることによりムーアの法則が示す性能向上がもたらされることになるそうです。
むろん進化はハードウェアだけに留まりません。マルチコアプロセッサを有効利用するために並列プログラミングに関するソフトウェア技術も同様に進化しました。昨今では、並列指向の関数型プログラミング言語Erlang、並列プログラミングを実現するライブラリや技術、.NET 4.0で並列化に関する機能が付加されるなど、並列化に関する話題を見聞きする機会が多くなりました。
しかし、多くの開発者達は多忙です。本音を言えば、並列プログラミングは苦痛だと言う人もいることでしょう。それも無理からぬことです。そこで筆者は、インテルTBB(インテル スレッディング・ビルディング・ブロック)を通じて「並列プログラミングの考え方」を紹介することにしました。一見すると並列プログラミングは難しいのですが、コツさえ掴めばそれ程難しくはありません。
多くの並列化技術からインテルTBBを選んだ理由は、並列化の考え方がよく表現されていると感じたからです。並列化の考えさえ掴めば、これから出現するであろう様々な並列的な技術を習得するのが容易になります。このインテルTBBの連載を通じて、並列化時代を迎える一助になれば幸いです。
対象読者
筆者が想定している読者はC++の基本的文法をマスターし、並列化プログラミングに興味を持っている方です。高度なC++テクニックを極力さけ、基本的な文法さえ分かれば読めるように極力注意しますので、並列化に興味を持っている方はぜひこの連載に目を通して下さい。
必要な環境
C++コンパイラが必要です。お持ちでない方は無償で提供されているマイクロソフト社の「Visual Studio C++ 2008 Express Edition」をダウンロードするなどして入手してください。
この連載は基本的にWindows環境を想定して解説しますが、インテルTBBそのものは他のOS上でも動作しますので、適宜読み替えて参考にしてください。
インテルTBBの概要
インテルTBBとは、C++プログラムで並列処理を行うためのアプローチを提供するテンプレートライブラリです。最大の特徴はパフォーマンスとスケーラビリティーが高く、プラットフォームの詳細とスレッドのメカニズムを抽象化していることです。高度に抽象化されていますので、マルチスレッドプログラミングの複雑さが軽減され、より自然に並列処理を実装することができます。
筆者は何度もマルチスレッドプログラミングで苦労した経験があります。というのも、システムの設計時に苦労しますし、実装後もデバッグが難しく、最初から最後まで苦労することになるからです。ですがその分、システムが完成した後の喜びは格別でした。同様の経験をした開発者は多いと思います。
マルチスレッドプログラミングに馴染みがない方もいると思いますので、簡単なマルチスレッドプログラミングのサンプルを掲載します。
/* グローバル変数の値を複数のスレッドから変更します。 */ #include <stdlib.h> #include <time.h> #include <limits.h> #include <iostream> #include <windows.h> #include <winnt.h> #include <tchar.h> #include <StrSafe.h> #include <process.h> using namespace std; long global = 100; //グローバル変数の値を増加します DWORD WINAPI AddThreadFunc( PVOID pvParam ) { int value = PtrToUlong( pvParam ); InterlockedExchangeAdd( &global, value ); //ここに注意 return 0; } //グローバル変数の値を減算します DWORD WINAPI SubThreadFunc( PVOID pvParam ) { int value = PtrToUlong(pvParam); InterlockedExchangeAdd( &global, ( value * -1 ) ); //ここに注意 return 0; } typedef unsigned (__stdcall *PTHREAD_START) (void *); int _tmain(int argc, _TCHAR* argv[]) { //グローバル変数の初期値を表示 _tsetlocale( LC_ALL, _T("") ); TCHAR str[ 20 ] = _T ( "グローバル変数の初期値=%d" ); TCHAR* buffer = ( TCHAR* ) malloc( _countof( str ) * sizeof( TCHAR ) ); StringCchPrintf ( buffer, _countof( str ), str, global ); _tprintf( buffer ); free( buffer ); int value = 10; HANDLE hThreads[2]; DWORD dwThreadID; //グローバル変数を増加 hThreads[ 0 ] = ( HANDLE ) _beginthreadex ( ( void * ) NULL, ( unsigned ) 0, ( PTHREAD_START ) AddThreadFunc, ( void * ) (PVOID) (INT_PTR) value, ( unsigned ) 0, ( unsigned * ) &dwThreadID ); //グローバル変数を減算 hThreads[ 1 ] = ( HANDLE ) _beginthreadex ( ( void * ) NULL, ( unsigned ) 0, ( PTHREAD_START ) SubThreadFunc, ( void * ) (PVOID) (INT_PTR) value, ( unsigned ) 0, ( unsigned * ) &dwThreadID ); //処理が終わるのを待つ WaitForMultipleObjects(2, hThreads, TRUE, INFINITE); //念のためにスレッドを閉じる CloseHandle( hThreads[0] ); CloseHandle( hThreads[1] ); //グローバル変数の最終値を表示 TCHAR str1[20] = _T ( "グローバル変数の最終値=%d" ); buffer = ( TCHAR* ) malloc( _countof( str1 ) * sizeof( TCHAR ) ); StringCchPrintf ( buffer, _countof( str1 ), str1, global ); _tprintf( buffer ); free( buffer ); return 0; }
簡単な加算演算がInterlockedExchangeAddメソッドで処理されている点に注意して下さい。マルチスレッドプログラミングでは、変数は複数のスレッドから同時にアクセスされる可能性があるので変数を保護しなければなりません。この例は一番簡単なマルチスレッドプログラミングの例であり、ミューテックス、セマフォ、同期処理……などのシングルスレッドプログラミングにない新しい概念を習得しなければなりません。
また、この様に実装面で考える必要が生じますので、並列処理を行うシステムの設計が大変です。その点インテルTBBは高度に抽象化されていますので比較的無理なく設計と実装が行えます。
インテルTBBは高度に抽象化されていますが、全く学ぶ必要がないということではありません。インテルTBBを用いても並列処理に関する知識は必要ですし、マルチスレッドプログラミングで得た知識は有効です。その点に注意して下さい。
あくまでも、細部を実装する手間が省けると考えて下さい。
スケーラビリティーとマルチコアCPU
前節で少し触れましたがインテルTBBはスケーラビリティーが高いのが特徴です。ここで言うスケーラビリティーとは、CPUのコア数が変化しても対応できることです。マルチコア時代で一番気をつけなければならないことは、システムが稼動するCPUのコア数が変化するということです。
エンドユーザーはシステム納品後、CPUを買い換えることが十二分に考えられます。その時、あえて現在よりも古いCPUを買う人はいませんから自然とコア数が増加することになります。この時システムの性能が向上しなければ、エンドユーザーはシステムの不備だと考えるでしょう。エンドユーザーにしてみれば、新しいCPUを買えば性能が良くなるのは当然のことなのです。
ここで改めて、従来のマルチスレッドプログラミングを考えてください。プログラミング時にスレッドの数が固定なので、CPUのコア数が増加されることが視野に入れられていません。このままでは性能はUPしません。この問題を解決し、スケーラブルなシステムを開発できるようにするのがインテルTBBです。
おわり
今回の記事では、初回ですので並列処理とインテルTBBの概要を解説しました。今後並列処理は当たり前のものとなることでしょう。その準備をするに当たって少しでもこの記事がお役に立ては幸いです。
次回からインテルTBBの具体的な使い方と並列化処理についての考え方を書きます。お楽しみに。
参考資料
書籍
- 『インテル スレッディング・ビルディング・ブロック』 James Reinderss著、菅原清文・エクセルソフト 訳、オライリー・ジャパン、2008年2月26日