マルチスレッド開発ガイド: 4.3 スレッド化とインテル® インテグレーテッド・パフォーマンス・プリミティブ (インテル® IPP)

インテル® IPPインテル® Parallel Studio XE特集

この記事は、インテル® ソフトウェア・ネットワークに掲載されている「Threading and Intel® Integrated Performance Primitives」 (https://software.intel.com/en-us/articles/threading-and-intel-integrated-performance-primitives) の日本語参考訳です。


編集注記:
本記事は、2011 年 5 月 4 日に公開されたものを、加筆・修正したものです。

すべてのアプリケーションに対応するスレッド化のソリューションはありません。これと同じように、インテル® インテグレーテッド・パフォーマンス・プリミティブ (インテル® IPP) を使用してアプリケーションをビルドし、マルチスレッドを利用する方法もさまざまです。スレッド化は、インテル® IPP ライブラリー内の各プリミティブや、OS レベルのインターフェイスを利用してアプリケーション自身に実装することができます。ここでは、インテル® IPP を利用したアプリケーションで安全にマルチスレッド実行の利点を活かす方法を説明します。

この記事は、「マルチスレッド・アプリケーションの開発のためのガイド」の一部で、インテル® プラットフォーム向けにマルチスレッド・アプリケーションを効率的に開発するための手法について説明します。

はじめに

インテル® IPP は、デジタルメディアやデータ処理アプリケーション向けに高度に最適化されています。このライブラリーには、信号処理、画像、オーディオ、ビデオエンコード/デコード、データ圧縮、ストリング処理、暗号化など、各種分野の基本的なアルゴリズムのうちよく使用される最適化関数が含まれています。

ライブラリーは、広範囲に SIMD (Single Instruction Multiple Data) 命令や SSE (ストリーミング SIMD 拡張命令) 、および現在のインテル® プロセッサーで利用可能なハードウェア・マルチスレッドを活用します。最近のプロセッサーでサポートされる SSE 命令の多くは、DSP (デジタル・シグナル・プロセッサー) にならったもので、データの配列やベクトルを処理するアルゴリズムを最適化するのに理想的です。

インテル® IPP ライブラリーは、Windows*、Linux*、Mac OS* X、QNX*、VxWorks* オペレーティング・システム向けにビルドされたアプリケーションに使用できます。また、インテル® C/Fortran コンパイラーや、Microsoft* Visual Studio* C/C++ コンパイラー、最新の Linux ディストリビューションに含まれている gcc コンパイラーと互換性があります。

さらに、インテル® Core™ プロセッサーおよびインテル® Atom™ プロセッサーを含む、複数の世代のインテル® プロセッサーや互換 AMD プロセッサーとの動作も確認されています。32 ビットと 64 ビットの両オペレーティング・システムおよびアーキテクチャーに対応しています。

序説

インテル® IPP ライブラリーは、マルチスレッド化を実現するための各種手法を取り入れて構築されています。スレッドセーフな設計で、1 つのアプリケーション内で複数のスレッドからライブラリー関数を安全に呼び出せます。既存のアプリケーションを記述し直してマルチスレッド・アプリケーションを作成しなくても、インテルの OpenMP* ライブラリーを通じて、マルチスレッドが実装されたライブラリーにより、すぐさまパフォーマンスを向上させることができます。

インテル® IPP のプリミティブ (インテル® IPP ライブラリーの基礎を成す低レベルの関数) は、データのベクトルと配列で、繰り返し演算が行われるよう設計されたアルゴリズム要素のコレクションで、マルチスレッド・アプリケーションの実装に理想的な条件です。プリミティブは、下層のオペレーティング・システムから独立しており、ロック、セマフォー、グローバル変数を使用しません。

また、一時およびステート・メモリー・ストレージには C 言語の標準ライブラリーのメモリー割り当てルーチン (malloc/realloc/calloc/free) のみを利用します。外部システム関数との依存関係をより排除するため、i_malloc インターフェイスを使用して、C 言語の標準ライブラリーの代わりに独自のメモリー割り当てルーチンを使用することもできます。

低レベルのアルゴリズム・プリミティブのほか、インテル® IPP ライブラリーには画像、メディア、音声コーデック (エンコーダー/デコーダー)、データ圧縮ライブラリー、ストリング処理関数、暗号化を実装する、業界標準の高レベルなアプリケーションとツールが含まれています。このようなツールの多くで、複数の論理スレッド間で処理を分散できるよう複数のスレッドが使用されます。

インテル® IPP ライブラリーは、大量の数値計算を行うアルゴリズムの要件を満たすように設計された関数セットを通じて、SIMD 命令 (MMX、SSE、AVX など) に簡単にアクセスできるため、シングルスレッド・アプリケーションでもパフォーマンスを大幅に向上させることができます。

図 1 は、MMX/SSE 命令を利用せずに実装された関数と (灰色のバー)、インテル® IPP 製品の各種ドメインで測定された (青いバー) 性能向上の相対的な平均値です。(実際の性能は異なることがありますのでご注意ください。)


図 1. 各種インテル® IPP 製品ドメインの相対的なパフォーマンス向上
システム構成: クアッドコア インテル® Xeon® プロセッサー、2.8GHz、2GB: Windows* XP、インテル® IPP 6.0 ライブラリーを使用

アドバイス

インテル® IPP ライブラリーで複数の論理スレッドを活用するには、ライブラリーのマルチスレッド版を使用するか、あるいはライブラリー付属のスレッド化されたサンプル・アプリケーションを使用するのが最も早く簡単な方法でしょう。大幅にコードを変更しなくても、単にインテル® IPP を使用するよりも優れたパフォーマンスが得られます。

ライブラリー (バージョン 6.1) の基本版には、シングルスレッドのスタティック・ライブラリー、マルチスレッドのスタティック・ライブラリー、マルチスレッドのダイナミック・ライブラリーの 3 つがあります。3 つはすべてスレッドセーフです。シングルスレッドのスタティック・ライブラリーは、カーネルモードのアプリケーションか、または OpenMP* ライブラリーが利用できない、あるいはサポートされていない場合 (リアルタイム・オペレーティング・システムで使用される場合など) に使用します。

2 つの選択肢: OpenMP* によるスレッド化とインテル® IPP

インテル® IPP の低レベルのプリミティブは基本的なアトミック演算であり、活用できる並列化をライブラリー関数の約 15% に制限します。インテルの OpenMP* ライブラリーは、この「隠れた」並列化を実装するために利用され、マルチスレッドのライブラリーが使用されるときにはデフォルトで有効になります。

マルチスレッド・プリミティブのリストは、インテル® IPP のドキュメント・ディレクトリーの ThreadedFunctionsList.txt ファイルに記載されています。

注:インテル® IPP ライブラリーはインテル® C コンパイラーと OpenMP* を使用して構築されていますが、これらのツールを使ってアプリケーションをビルドしなければならないわけではありません。インテル® IPP ライブラリーのプリミティブは関連するオペレーティング・システム (OS) プラットフォーム向けの C コンパイラーと互換性のあるバイナリー形式で提供されており、アプリケーションへのリンク準備ができています。つまり、インテル® IPP を使用するアプリケーションは、インテルのツールでも、またその他の開発ツールでもビルドできます。

インテル® IPP プリミティブの OpenMP* によるスレッド化の制御

スレッド化されたインテル® IPP プリミティブで使用される OpenMP* スレッドのデフォルトの数は、システムの論理スレッドの数と同じで、プロセッサー・コアの数と種類によって決まります。例えば、インテル® ハイパースレッディング・テクノロジー (インテル® HT テクノロジー) 対応のクアッドコア・プロセッサーの場合、8 つの論理スレッド (4 コアがそれぞれ 2 つのスレッドをサポート) があります。インテル® HT テクノロジーが搭載されていないデュアルコア・プロセッサーの場合は、2 つの論理スレッドがあります。

2 つのインテル® IPP プリミティブ、ippSetNumThreads()ippGetNumThreads() は、インテル® IPP ライブラリーのマルチスレッド版の OpenMP* によるスレッド化におけるユニバーサルな制御とステータスを提供します。ippGetNumThreads を呼び出して、現時点のスレッドキャップ (スレッド数) を特定し、ippSetNumThreads を使用してそのスレッドキャップを変更します。

ippSetNumThreads では、利用可能な論理スレッド数を超える数のスレッドキャップは設定できません。このスレッドキャップは、マルチスレッド・プリミティブで使用できる OpenMP* ランタイムのスレッドの上限数を設定する役目を果たします。一部のインテル® IPP 関数で最適な並列効率性を得るため、スレッドキャップで指定されたスレッド数よりも少ない数のスレッドが使用されることはありますが、スレッドキャップで許容された数を超える数のスレッドが使用されることはありません。

インテル® IPP ライブラリーのスレッド版にある OpenMP* を無効にするには、アプリケーションの冒頭で ippSetNumThreads(1) を呼び出すか、あるいはアプリケーションをインテル® IPP のシングルスレッドのスタティック・ライブラリーにリンクします。

OpenMP* ライブラリーは、いくつかの環境変数を参照します。OMP_NUM_THREADS は実行時に OpenMP* ライブラリーで使用されるデフォルトのスレッド数 (スレッドキャップ) を設定します。ただし、インテル® IPP ライブラリーはアプリケーションで使用される OpenMP* のスレッド数を、前述したようにシステムの論理スレッド数か、あるいは ippSetNumThreads への呼び出しで指定された値のいずれかに制限することで、この設定を無効にします。

インテル® IPP ライブラリーを使用しない OpenMP* アプリケーションも OMP_NUM_THREADS 環境変数に影響を受けますが、インテル® IPP アプリケーションの ippSetNumThreads 呼び出しの影響を受けません。

入れ子の OpenMP

インテル® IPP アプリケーションで OpenMP* によるマルチスレッド化も実装されている場合には、そのアプリケーションで呼び出されるスレッド化されたインテル® IPP プリミティブは、シングルスレッドのプリミティブとして実行されることがあります。これは、インテル® IPP プリミティブがアプリケーションの OpenMP* 並列化コード領域で呼び出され、またインテルの OpenMP* ライブラリーで入れ子の並列化が無効 (デフォルト) の場合に発生します (OMP_DYNAMICがFALSE)。

OpenMP* の並列領域の入れ子は、利用可能な論理スレッド数のオーバーサブスクリプションを引き起こす (多数のスレッドを生成する) 恐れがあります。並列領域の作成にはオーバーヘッドがつきものです。OpenMP* の並列領域の入れ子によるオーバーヘッドが、得られる利点よりも上回ることもあります。

一般に、インテル® IPP プリミティブが使用される OpenMP* のマルチスレッド・アプリケーションでは、ippSetNumThreads(1) を呼び出すか、またはインテル® IPP のシングルスレッドのスタティック・ライブラリーを使用して、インテル® IPP ライブラリー内のマルチスレッド化を無効にします。

コア・アフィニティー

信号処理ドメインのインテル® IPP プリミティブの中には、共有キャッシュを活用する並列スレッドを実行するよう設計されたものがあります。このような関数 (単精度/倍精度 FFT、Div、Sqrt など) の場合、マルチスレッド・パフォーマンスを最大限に引き出すために、共有キャッシュを持つシングルダイの複数のコア上で実行されなければなりません。この条件が確実に満たされるよう、次の OpenMP* 環境変数をインテル® IPP ライブラリーが実行される前に設定します。

KMP_AFFINITY=compact

シングルダイに複数のコアが搭載されているプロセッサーでは、この条件は自動的に満たされるため環境変数は不要です。ただし、複数のダイが搭載されたシステム (インテル® Pentium® D プロセッサーやマルチソケットのマザーボードなど) でキャッシュが共有されない場合、この OpenMP* 環境変数が設定されていないと、このクラスのマルチスレッドのインテル® IPP プリミティブで大幅なパフォーマンス低下を招くことがあります。

利用ガイド

インテル® IPP アプリケーションのスレッド化

インテル® IPP ライブラリーには、インテル® IPP プリミティブを使用したマルチスレッドのアプリケーション・サンプルが多数用意されています。サンプルのすべてにソースコードが含まれています。アプリケーション・レベルでスレッド化を実装するサンプルや、インテル® IPP ライブラリーにビルトインされているスレッド化を利用するサンプルもあります。ほとんどの場合、マルチスレッド化によって大きなパフォーマンス・ゲインが得られます。

マルチスレッド・アプリケーションでプリミティブを使用する場合は、前のセクションで説明した手法を用いて、インテル® IPP ライブラリーのビルトインのスレッド化を無効にすることをお勧めします。こうすることで、ライブラリーのビルトインのスレッド化とアプリケーションのスレッド化の競合がなくなり、利用可能な論理スレッドに対してソフトウェア・スレッドがオーバーサブスクリプションを引き起こすのを防ぎます。

インテル® IPP ライブラリーは、ベクトル演算に適したプロセッサーの SIMD 命令を有効利用します。そのため、大半のライブラリー・プリミティブでデータ配列における演算に重点が置かれています。スレッド化は、大部分が独立している複数のデータ要素での演算に適しています。ライブラリーによるスレッド化では、データ分解を使用するか、またはデータブロックを小さなブロックに分割し、同一の並列スレッドを複数実行してそのブロックを処理する方法が一般に最も簡単です。

メモリーとキャッシュのアライメント

大きなデータブロックを処理する際、アライメントが適切でないとスループットが低下します。インテル® IPP ライブラリーには、メモリーの割り当てとアライメントを行う関数セットが含まれており、この問題に対処します。さらに、多くのコンパイラーは、バス効率の良いデータアライメントになるよう構造体をパディングするよう設定されています。

並列スレッドを実装する際、キャッシュラインに対するキャッシュ・アライメントとデータ間隔は非常に重要です。特に、インテル® IPP プリミティブをループする構造体が含まれた並列スレッドでは重要です。複数の並列スレッドの操作で同時あるいは共有データ構造が頻繁に利用される場合、あるスレッドの書き込み操作は「隣接した」スレッドのデータ構造に関連するキャッシュラインを無効にすることがあります。

同一のインテル® IPP の操作 (データ分解) の並列スレッドをビルドする際には、並列スレッドで処理される分解されたデータブロックの相対間隔と、その並列スレッド内のプリミティブが使用する制御データ構造の間隔を検討してください。インテル® IPP 関数の各反復で更新される状態を制御構造が保存する場合には特に注意が必要です。このような制御構造でキャッシュラインが共有されている場合には、更新処理によって近隣の構造のキャッシュラインが無効化されることがあります。

この場合、プロセッサーのキャッシュ・ライン・サイズ (通常は 64 バイト) を複数占有するよう制御データ構造を割り当てるのが一番簡単な解決方法でしょう。また、コンパイラーのアライメント操作を使用して、常にキャッシュラインの境界でこのような構造や構造配列がアライメントされるようにすることもできます。制御構造をパディングするために使用されたバイトは、プリミティブの反復でキャッシュラインの更新に必要とされる、失ったバスサイクルを補います。

DMIP によるパイプライン処理

SIMD 命令、論理スレッド数、高速キャッシュサイズの利用を最適化するためには、実行時にアプリケーションで対応することが理想的です。このような重要なリソースを最適に使用することで、ほぼ完璧に近い並列操作が達成される可能性があり、これはインテル® IPP の DMIP ライブラリーの最重要目標とされているものです。

DMIP による並列化の手法 (キャッシュに適したサイズのデータブロックで実行されるインテル® IPP プリミティブの並列シーケンスをビルドする) は、各関数呼び出しでデータセット全体を逐次処理していた部分から、アプリケーションのパフォーマンス・ゲインを引き出します。

例えば、画像全体を処理するのではなく、画像をキャッシュ可能なセグメントに分割し、キャッシュ中にある間、各セグメントで複数の処理を実行します。処理シーケンスは計算パイプラインで、データセット全体が処理されるまで、各タイルに適用されます。その後、並列に実行する複数のパイプラインをビルドし、パフォーマンスの向上を図ります。

この方法についての詳細な説明は、「A Landmark in Image Processing: DMIP」(http://software.intel.com/en-us/articles/a-landmark-in-image-processing-dmip/) を参照してください。

スレッド化によるパフォーマンス効果

インテル® IPP に含まれているその他の高レベルのマルチスレッド・ライブラリー・ツールをマルチコア環境で使用すると、パフォーマンスの大幅な向上を実現することができます。例えば、インテル® IPP のデータ圧縮ライブラリーは、よく使用されるデータ可逆圧縮ライブラリー、ZLIB、BZIP2、GZIP、LZO とのドロップイン互換が確保されています。

インテル® IPP の BZIP2 ライブラリーや GZIP ライブラリーでは、ネイティブスレッドを使用して、並列に圧縮できるように大きなファイルを複数のブロックに分けるか、あるいは個別のスレッドで複数のファイルを処理することにより、マルチスレッド環境を有効利用します。この手法を用いると、クアッドコア・プロセッサーでインテル® IPP を使用していないシングルスレッドの実装と比べ、GZIP ライブラリーで 10 倍ものパフォーマンス・ゲインの達成が可能になります。

マルチメディアの分野 (ビデオ、画像処理) では、インテル® IPP の H.264、VC 1 デコーディングにより、ビデオフレームのデコーディング処理、再構成処理、デブロッキング処理を複数のネイティブスレッドを使用して並列化することで、理論上のほぼ限界の性能向上までスケーリングを実現できます。インテル® IPP で強化された H.264 デコーダーをクアッドコア・プロセッサーで実行すると、HD ビットストリームで 3 倍から 4 倍のパフォーマンス・ゲインがもたらされます。

最後に

すべての状況において最高のパフォーマンスが保証される万能な方法はありません。スレッド化やインテル® IPP ライブラリーを使用して達成されるパフォーマンスの向上は、アプリケーションの性質 (スレッド化をどの程度簡単に行えるか) や使用できるインテル® IPP プリミティブの組み合わせ (スレッド化バージョン/スレッド化されていないバージョン、使用頻度など)、ハードウェア・プラットフォーム (コア数、メモリー帯域幅、キャッシュサイズや種類など) により異なります。


この記事は古いバージョンのインテル® IPP を対象に記述されています。最新の実装では、インテル® IPP 内部ではスレッド化を行わず、ライブラリーを呼び出す上位のアプリケーションでスレッド化を実装および制御することが推奨されています。

関連情報

  • インテル® IPP
  • Stewart Taylor, Optimizing Applications for Multi-Core Processors: Using the Intel® Integrated Performance Primitives, Second Edition.Intel Press, 2007. (http://www.intel.com/intelpress/sum_ipp2.htm)
  • 『User’s Guide, Intel® Integrated Performance Primitives for Windows* OS on IA-32 Architecture』 (http://software.intel.com/sites/products/documentation/hpc/ipp/pdf/userguide_win_ia32.pdf)、資料番号: 318254-007US、2009 年 3 月
  • 『Reference Manual, Intel® Integrated Performance Primitives for Intel® Architecture: Deferred Mode Image Processing Library』 (http://software.intel.com/en-us/articles/intel-integrated-performance-primitives-documentation/)、資料番号: 319226-002US、2009 年 1 月
  • インテル® IPP の i_malloc サンプルコード (http://software.intel.com/en-us/articles/create-your-own-memory-management-i_malloc-using-intel-ipp/)
  • ウィキペディアの OpenMP* (英語)
  • OpenMP.org (英語)
  • A Landmark in Image Processing: DMIP (http://software.intel.com/en-us/articles/a-landmark-in-image-processing-dmip/)
タイトルとURLをコピーしました