Linux on POWER のパフォーマンスを評価する

Linux ツールを使用してパフォーマンスを分析する

コンパイル言語 (C または C++) 環境に焦点を当てた Linux on POWER のパフォーマンス問題を評価する方法を学んでください。この記事では、POWER7 の CPI モデルを解説し、一般に入手できる Linux ツールを使って潜在的な CPU ストール、パイプライン・ハザード、そしてパフォーマンス問題を明らかにする方法を具体的に紹介します。最後のセクションでは、POWER7 のアルゴリズムを分析し、最適化を行います。

Adhemerval Zanella Netto, Software Engineer, IBM

Photo of Adhemerval Zanella photoAdhemerval Netto は、IBM Linux Technology Center に勤務する熟練のソフトウェア・エンジニアです。現在、Power Architecture のパフォーマンス分析およびツールチェーン開発に取り組んでいます。彼は、spec.org で Linux でのさまざまな Power パフォーマンス・レポートを担当し、オープンソース・ソフトウェア (glibc) に貢献しています。



Ryan S. Arnold, Advisory Software Engineer, IBM China

Photo of Ryan S. ArnoldRyan Arnold は、IBM Linux Technology Center の IBM Advisory Software Engineer です。GNU ツールチェーンの統合、および Linux システム・ライブラリーの開発で経験を積んでいます。彼は、GLIBC の Power Architecture の保守者であり、Libdfp の中心的ライブリー保守者です。また、「Power Architecture 32-bit Application Binary Interface Supplement 1.0」の共著者でもあります。



2012年 7月 12日

はじめに

最近のマシンにおける、アプリケーションのパフォーマンス評価は複雑なタスクになりがちです。そのため、手近に使えるツールでは、パフォーマンスに関するすべての変数に対処しきれません。また、それぞれのワークロードがどのコンピューター・サブシステムにストレスをかけるかは、ワークロードによって異なり、CPU バウンドなプログラムを測定して調整することと、I/O バウンドなプログラムやメモリー・バウンドなプログラムを調整することには大きな違いがあります。この記事ではコンパイル済み言語環境 (C、C++、その他) でのCPU バウンドなプログラムとメモリー・バウンドなプログラムに焦点を当て、以下のタスクを行う方法を具体的に説明します。

  • プログラムのホットスポット (プログラムの中で命令が実行される割合が高い領域、関数、メソッド) を見つける
  • プロセッサーに用意されているハードウェア・パフォーマンス・カウンターを使用して POWER7 上でのプログラムの動作状況を測定する
  • Linux でパフォーマンス評価に使用できるツールを識別する

POWER7 の CPI モデル

アプリケーションのパフォーマンス分析について理解するには、まず、CPI メトリックの説明から始めます。CPI (Cycles per Instruction) メトリックとは、1 つの命令を完了するために必要なプロセッサー・サイクル数です。命令はそれぞれ複数のステージに分割されて処理されます。典型的な RISC パイプラインでは、「命令フェッチ」ステージの後、「命令デコード/レジスター・フェッチ」、「実行」、オプションの「メモリー・アクセス」と続き、最後に「ライトバック」が行われます。CPU の CPI メトリックを改善するには (つまり、測定される CPI 値を小さくするには)、命令レベルの並列処理を利用するという方法があります。これは、複数のステージで、それぞれのステージが異なる命令を処理するということです。最適化の際には、CPI 値を最小限にしてシステムを最大限使用するように心掛けてください。パイプライン化されたプロセッサーで最適化された命令フローの一例を図 1 に示します。

図 1. パイプライン化されたプロセッサーで最適化された命令フローの一例
独立した複数の命令が同時にフェッチ、デコード、実行、およびライトバックの各ステージで処理されるフローを示した図

あるステージが他のステージから完全には独立していないこともあります。つまり、依存関係のある命令が発行されると、プロセッサーはその依存関係を満たしてからでないと、命令の実行を続けられません。例えば、メモリー・ロードの後に演算命令が続く場合、プロセッサーはキャッシュまたはメモリーへのデータの取り込みが完了してからでないと、演算命令を発行することができません。このような事態が起こり、パイプラインが停止することを、プロセッサーのパイプラインで「ストール」が発生したと言います。図 2 に、パイプラインでストールが発生する様子を示します。

図 2. パイプライン化されたプロセッサーでストールが発生する様子
命令に依存関係があるため、他の命令が完了するのを待機してから次の命令が実行される様子を示した図

図 1 と図 2 の例を比較してみると、図 1 のようにパイプラインが完全に満たされている場合 (サイクルごとに 1 つの命令が完了するパイプラインの場合)、クロックが 11 サイクル動作する間にプロセッサーは 8 つの命令を実行することができますが、図 2 のように 3 つのサイクルでストールが発生する場合、同じ 11 サイクルでも 5 つの命令しか実行されません。このパフォーマンス損失は、約 40% です。アルゴリズムによっては、ある程度のストールが不可避であることもありますが、慎重に分析することで、ストールが発生しないようにコードの一部を書き直すか、調整する方法を知る手掛かりやヒントを得ることができます。最近の CPU パイプライン化、および命令レベルの並列性についての詳細は、記事「Modern Microprocessors - A 90 Minute Guide」で教訓的に説明しています (「参考文献」を参照)。

CBM (CPI Breakdown Model) は、機能別のプロセッサー・ステージにパフォーマンス・カウンターを関連付けて、CPU のどの機能ユニットがストールを発生させているかを明らかにするためのモデルです。CBM は CPU のアーキテクチャーとプロセッサー・モデルに依存するため、例えば Power Architecture と Intel Architecture の CBM はまったく異なります。また、POWER5 と POWER7 でも、その CBM は似ているとは言え、同じではありません。図 3 に、POWER7 CBM の一部を抜粋したものを示します (図 3 の情報をテキストとして表示したものを見るにはここをクリックしてください)。

図 3. POWER7 CBM の一部抜粋
POWER7 CBM のスクリーン・キャプチャー

Power Architecture でのハードウェア・パフォーマンス・カウンターは、プロセッサー内で特定のイベントが発生すると更新される、一連の特殊用途のレジスターです。POWER7 プロセッサーには、PMU (Performance Monitoring Unit) が内蔵されており、PMU ごとに 6 つのスレッド・レベルの PCM (Performance Counter Monitor) があります。そのうち 4 つはプログラマブル PCM です。これはつまり、発生する可能性のある 500 以上のパフォーマンス・イベントのうち、同時に 4 つのイベントを監視できることを意味します。POWER7 のパフォーマンス・カウンターはグループ別に定義されていて、PMU が同時に監視できるのは、同じグループのイベントに限られます。図 3 に示されているのは、POWER7 CBM の定義に使用されているパフォーマンス・カウンターのサブセットです。図 3 のカウンターがプロファイルで使用されて、プロセッサー・ストールの原因となっている CPU の機能ユニットを示すとともに、ストールを廃絶するためにアルゴリズムを調整する方法を知る手掛かりを提供します。

図 3 で背景が白のボックスは、プロファイルで監視される POWER7 の具体的な PCM です。これらの値に基づき、グレーのボックス (アスタリスク (*) が付けられています) が計算されます (これらのメトリックには、固有のハードウェア・カウンターがありません)。

注: POWER7 の包括的な PMU リファレンスとなる「Comprehensive PMU Event Reference POWER7」を参照してください (「参考文献」を参照)。


Linux でのツール

POWER7 プロセッサーの PCM は、どのように利用できるのでしょうか? POWER では、ハードウェア割り込み、コード・インスツルメンテーション (gprof など)、動作関連のシステム・フック (systemtap) など、各種のプロファイリング手法を使用できますが、PCM にはプロセッサー機能と直接連動する広範なカウンターが揃っています。PCM プロファイラーはオペレーティング・システム割り込みを使用して、常時一定の間隔でプロセッサー・レジスターの値をサンプリングします。命令トレースの結果と比べると、サンプル・プロファイリングによる結果は数値的な精度に劣るかもしれませんが、全体的なシステム・パフォーマンスへの影響は小さいため、ターゲットとするベンチマークをほぼフルスピードで実行することができます。生成されるデータは正確でないにしても、許容誤差の範囲内の近似値です。

Linux での PCM プロファイリングに最もよく使われているツールには、OProfileperf の 2 つがあります (「参考文献」を参照)。両方とも動作原理は同じで、ワークロードのバックトレースと併せて、常に (syscall によって) 特殊なハードウェア・レジスターをサンプリングしますが、その構成と使用方法はそれぞれに異なります。

Linux システムの OProfile ツールは、すべての実行中コードを低オーバーヘッドでプロファイリングできる、システム規模のプロファイラーです。このツールは、カーネル・ドライバー、サンプル・データを収集するデーモン、そしてプロファイリング後にデータを情報に変換するための数個のツールからなります。アノテーション付きのソースを必要とするのでない限り、デバッグ・シンボル (gcc の -g オプション) は必要ありません。最近の Linux 2.6 カーネルでは、OProfile が gprof スタイルのコール・グラフ・プロファイル情報を提供できるようになっています。OProfile の通常のオーバーヘッドは 1% から 8% です (サンプリング間隔とワークロードに依存)。

POWER 上では、OProfile はパフォーマンス・ハードウェア・カウンターのグループとパフォーマンス・カウンターのグループを監視することによって機能します。ただし、異なるグループを同時に使用することはできません。これは、同じワークロードから異なる複数のパフォーマンス・カウンターを取得するためには、異なる OProfile イベント構成でワークロードを複数回実行しなければならないことを意味します。つまり、POWER7 CBM 全体を一度に監視できないということでもあります。使用可能なグループについては、前述の「Comprehensive PMU Event Reference POWER7」に記載されている「Detailed Event Descriptions」を参照するか、リスト 1 のコマンドを実行して調べてください。

リスト 1. OProfile グループの一覧表示
# opcontrol -l

リスト 2 に、単純な OProfile の構成および起動コマンドを記載します。

リスト 2. OProfile POWER7 CPU サイクルの構成
# opcontrol -l
# opcontrol -–no-vmlinux
# opcontrol -e PM_CYC_GRP1:500000 -e PM_INST_CMPL_GRP1:500000 -e PM_RUN_CYC_GRP1:500000 
-e PM_RUN_INST_CMPL_GRP1:500000
# opcontrol --start

ワークロードを実行するコマンドはリスト 3 のとおりです。

リスト 3. OProfile 実行コマンド・シーケンス
# opcontrol --dump 
# opcontrol –-stop
# opcontrol --shutdown

パフォーマンス・カウンター・レポートを取得するには、リスト 4 のコマンドを実行します。

リスト 4. OProfile レポートの生成
# opreport -l > workload_report

注: developerWorks の記事「Identify performance bottlenecks with OProfile for Linux on POWER」(「参考文献」を参照) は、OProfile の包括的なガイドになります (ただし、POWER7 用に更新されてはいません)。

Linux カーネル 2.6.29 で導入された perf ツールは、ハードウェア・レベルとソフトウェア・レベルの両方でパフォーマンス・イベントを分析します。perf ツールの利点は、OProfile のようにシステム指向ではなく、プログラム指向であることです。このツールには、事前設定された「cpu-cycles OR cycles」、「branch-misses」、「L1-icache-prefetch-misses」などのパフォーマンス・カウンターがあります。また、サンプルの精度は犠牲になりますが、PMU グループを多重化して、異なるグループから同時に複数のパフォーマンス・カウンターを収集することもできます。

perf ツールの欠点は、ハードウェア・パフォーマンス・カウンターを直接収集できるとは言え、POWER7 CBM が示すカウンター名を認識しないことです。したがって、16 進数をそのまま使用しなければなりません。表 1 に、OProfile イベントと 16 進数のマッピングを記載します。このマッピングを使用すれば、(16 進数のままでイベントを記録するオプションを使用した) perf で POWER7 の CBM を利用することができます。

表 1. POWER7 の perf で認識可能な 16 進のイベント・コード
カウンター16 進のイベント・コード
PM_RUN_CYC200f4
PM_CMPLU_STALL 4000a
PM_CMPLU_STALL_FXU20014
PM_CMPLU_STALL_DIV40014
PM_CMPLU_STALL_SCALAR40012
PM_CMPLU_STALL_SCALAR_LONG20018
PM_CMPLU_STALL_VECTOR2001c
PM_CMPLU_STALL_VECTOR_LONG4004a
PM_CMPLU_STALL_LSU20012
PM_CMPLU_STALL_REJECT40016
PM_CMPLU_STALL_ERAT_MISS40018
PM_CMPLU_STALL_DCACHE_MISS20016
PM_CMPLU_STALL_STORE2004a
PM_CMPLU_STALL_THRD1001c
PM_CMPLU_STALL_IFU4004c
PM_CMPLU_STALL_BRU4004e
PM_GCT_NOSLOT_CYC100f8
PM_GCT_NOSLOT_IC_MISS2001a
PM_GCT_NOSLOT_BR_MPRED4001a
PM_GCT_NOSLOT_BR_MPRED_IC_MISS4001c
PM_GRP_CMPL30004
PM_1PLUS_PPC_CMPL100f2

: IBM の Wiki「Using perf on POWER7 systems」(「参考文献」を参照) は、perf の包括的なガイドになります (ただし、POWER7 用に更新されてはいません)。

OProfile に定義されている POWER7 イベントに対応した、perf で使用可能な 16 進のイベント・コードを libpfm4 プロジェクト (「参考文献」を参照) から取得することができます。これらのコードは、POWER7 専用のヘッダー (lib/events/power7_events.h) 内で定義されています。サンプル・プログラム examples/showevtinfo にも、イベント名とそれぞれに対応する 16 進コードが示されています。

カウンター情報を取得する手法としては、プロファイリングが一般的です。プロファイリングにより、開発者はコードの実行およびデータ・アクセスにおけるホットスポットを特定したり、パフォーマンスの影響を受けやすい領域を突き止めたり、メモリー・アクセス・パターンを理解したりするなど、さまざまな実態を明らかにすることができます。プロファイリングを開始するには、まずその前に、パフォーマンス評価の戦略を練り上げる必要があります。プログラムには、各種のモジュールや DSO (Dynamic Shared Object: 動的共有オブジェクト) で構成されているもの、カーネルを集中的に使用するもの、データ・パターン・アクセスへの依存度が高いもの (つまり、L2 または L3 キャッシュ・アクセスへの負担が高いもの)、あるいはベクトル演算ユニットに重点を置くものなど、さまざまな特性があります。次のセクションでは、考えられるパフォーマンス評価の戦略に目を向けます。

パフォーマンス評価の戦略

パフォーマンス評価の初期段階では、CPU サイクル使用状況カウンターを調べて、プログラムのホットスポットを見つけます。この評価を POWER7 で行う場合には、表 2 に記載するイベントを監視します。

表 2. POWER7 の CPU サイクル使用状況カウンター
カウンター内容
PM_CYCプロセッサー・サイクル
PM_INST_CMPL完了した PowerPC 命令の数
PM_RUN_CYC実行ラッチによってゲート制御されるプロセッサー・サイクル。オペレーティング・システムは、有用な処理を行っているときに実行ラッチを使用してそのことを示します。実行ラッチは通常、OS アイドル・ループでクリアされます。実行ラッチによってゲート制御すると、アイドル・ループが除外されます。
PM_RUN_INST_CMPL完了した実行命令の数

以上のイベントで OProfile を実行すると、シンボルにプロセッサーが費やした合計時間が示されます。以下に、IBM Advance Toolchain 5.0 for POWER でコンパイルされた SPECcpu2006 ベンチマーク・スイートに含まれる 403.gcc コンポーネントに対するプロファイリングの出力例を記載します。コマンド opreport -l による出力は以下のとおりです。

リスト 5. 403.gcc ベンチマークに対する「opreport -l」の出力 (カウンター PM_CYC_GRP1 および PM_INST_CMPL_GRP1)
CPU: ppc64 POWER7, speed 3550 MHz (estimated) 
Counted PM_CYC_GRP1 events ((Group 1 pm_utilization) Processor Cycles) with a unit 
mask of 0x00 (No unit mask) count 500000 
Counted PM_INST_CMPL_GRP1 events ((Group 1 pm_utilization) Number of PowerPC 
Instructions that completed.) with a unit mask of 0x00 (No unit mask) count 500000 

samples  %        samples  %        image name      app name        symbol name 
204528    7.9112  32132     1.3848  gcc_base.none   gcc_base.none   reg_is_remote_cons\
                                                                    tant_p.isra.3.part.4 
125218    4.8434  246710   10.6324  gcc_base.none   gcc_base.none   bitmap_operation 
113190    4.3782  50950     2.1958  libc-2.13.so    libc-2.13.so    memset 
90316     3.4934  22193     0.9564  gcc_base.none   gcc_base.none   compute_transp 
89978     3.4804  11753     0.5065  vmlinux         vmlinux         .pseries_dedicated_\
                                                                    idle_sleep 
88429     3.4204  130166    5.6097  gcc_base.none   gcc_base.none   bitmap_element_\
                                                                    allocate 
67720     2.6194  41479     1.7876  gcc_base.none   gcc_base.none   ggc_set_mark 
56613     2.1898  89418     3.8536  gcc_base.none   gcc_base.none   canon_rtx 
53949     2.0868  6985      0.3010  gcc_base.none   gcc_base.none   delete_null_\
                                                                    pointer_checks 
51587     1.9954  26000     1.1205  gcc_base.none   gcc_base.none   ggc_mark_rtx_\
                                                                    children_1 
48050     1.8586  16086     0.6933  gcc_base.none   gcc_base.none   single_set_2 
47115     1.8224  33772     1.4555  gcc_base.none   gcc_base.none   note_stores
リスト 6. 403.gcc ベンチマークに対する「opreport -l」の出力 (カウンター PM_RUN_CYC_GRP1 および PM_RUN_INST_CMPL_GRP1)
Counted PM_RUN_CYC_GRP1 events ((Group 1 pm_utilization) Processor Cycles gated by the 
run latch.  Operating systems use the run latch to indicate when they are doing useful 
work.  The run 
latch is typically cleared in the OS idle loop.  Gating by the run latch filters out 
the idle loop.) with a unit mask of 0x00 (No unit mask) count 500000 
Counted PM_RUN_INST_CMPL_GRP1 events ((Group 1 pm_utilization) Number of run 
instructions completed.) with a unit mask of 0x00 (No unit mask) count 500000 

samples  %        samples  %        samples  %      app name        symbol name 
204538    8.3658  32078     1.3965  gcc_base.none   gcc_base.none   reg_is_remote_consta\
                                                                    nt_p.isra.3.part.4 
124596    5.0961  252227   10.9809  gcc_base.none   gcc_base.none   bitmap_operation 
112326    4.5943  50890     2.2155  libc-2.13.so    libc-2.13.so    memset 
90312     3.6939  21882     0.9527  gcc_base.none   gcc_base.none   compute_transp 
0              0  0              0  vmlinux         vmlinux         .pseries_dedicated\
                                                                    _idle_sleep 
88894     3.6359  124831    5.4346  gcc_base.none   gcc_base.none   bitmap_element_all\
                                                                    ocate 
67995     2.7811  41331     1.7994  gcc_base.none   gcc_base.none   ggc_set_mark
56460     2.3093  89484     3.8958  gcc_base.none   gcc_base.none   canon_rtx
54076     2.2118  6965      0.3032  gcc_base.none   gcc_base.none   delete_null_pointer\
                                                                    _checks
51228     2.0953  26057     1.1344  gcc_base.none   gcc_base.none   ggc_mark_rtx_childr\
                                                                    en_1 
48057     1.9656  16005     0.6968  gcc_base.none   gcc_base.none   single_set_2 
47160     1.9289  33766     1.4700  gcc_base.none   gcc_base.none   note_stores

出力には、監視されたイベントのそれぞれが一対の列によって表されます。最初の列には、指定のイベントの PCM から収集されたサンプル数が示されており、2 番目の列には、そのサンプル数が合計サンプル数に占める割合がパーセントで示されています。このレポートに示されているように、プロセッサー・サイクルの大部分を消費しているのはシンボル reg_is_remote_constant_p です。したがって、これがコード最適化の有力候補となります。このプロファイルは、どのシンボルが最も CPU サイクルを消費しているかを明らかにするだけで、プロセッサー・パイプラインが完全に使用されているかどうかは識別しません。パイプラインの使用状況を調査するには、カウンターの結果を比較します。

リスト 5 でカウンター PM_INST_CMPL_GRP1 (2 番目の列の対) を見ると、シンボル bitmap_operation はシンボル reg_is_remote_constant_p よりも高いパーセンテージを示しています。PM_CYC_GRP1 は使用された CPU サイクル数を意味するだけですが、このパフォーマンス・カウンターは、プロセッサー命令が完了するごとにインクリメントされます。詳しく分析する前の段階では、これはシンボル reg_is_remote_constant_p に、シンボル bitmap_operation よりも多くの CPU ストールが含まれていることを意味する可能性があります。これは、シンボル reg_is_remote_constant_p の完了済み命令の数のほうが大幅に少ないためです。このプロファイルが、今後の最適化作業で重点に置くシンボルを突き止めるための最初のヒントとなります。

コードの内容を詳しく分析する前に、ワークロードが CPU バウンドかメモリー・バウンドかを理解しておくのが賢明です。このことが重要になるのは、それぞれのワークロードのタイプによって、最適化手法がかなり異なるためです。例えば、メモリー・アクセスのほとんどを (NUMA リモート・ノード・メモリー・アクセスではなく) キャッシュまたはメイン・メモリーが占めている場合、パフォーマンスはほぼ完全に、使用されているアルゴリズムとデータ構造に依存します。メモリー・アクセス・パターンを調査するには、以下の表 3 に記載する 2 つのパフォーマンス・カウンターを監視します。

表 3. POWER7 のメモリー使用状況カウンター
カウンター内容
PM_MEM0_RQ_DISPメイン・メモリーに対する読み取り要求の数
PM_MEM0_WQ_DISPメイン・メモリーに対する書き込み要求の数

この 2 つのカウンターから、メモリー・アクセス・パターンが主にメモリーの読み取りによるものか、書き込みによるものか、あるいはその両方によるものかがわかります。前と同じベンチマーク (SPECcpu2006 による 403.gcc) を使用すると、プロファイルには以下の内容が示されます。

リスト 7. 403.gcc ベンチマークに対する「opreport -l」の出力 (カウンター PM_MEM0_RQ_DISP および PM_MEM0_WQ_DISP)
CPU: ppc64 POWER7, speed 3550 MHz (estimated) 
Counted PM_MEM0_RQ_DISP_GRP59 events ((Group 59 pm_nest2)  Nest events (MC0/MC1/PB/GX), 
Pair0 Bit1) with a unit mask of 0x00 (No unit mask) count 1000 
Counted PM_MEM0_WQ_DISP_GRP59 events ((Group 59 pm_nest2)  Nest events (MC0/MC1/PB/GX), 
Pair3 Bit1) with a unit mask of 0x00 (No unit mask) count 1000 
samples  %        samples  %        app name                 symbol name 
225841   25.8000  289       0.4086  gcc_base.none            reg_is_remote_constant_p.\
                                                             isra.3.part.4 
90068    10.2893  2183      3.0862  gcc_base.none            compute_transp 
54038     6.1733  308       0.4354  gcc_base.none            single_set_2 
32660     3.7311  2006      2.8359  gcc_base.none            delete_null_pointer_checks 
26352     3.0104  1498      2.1178  gcc_base.none            note_stores 
21306     2.4340  1950      2.7568  vmlinux                  .pseries_dedicated_idle_sl\
                                                             eep 
18059     2.0631  9186     12.9865  libc-2.13.so             memset 
15867     1.8126  659       0.9316  gcc_base.none            init_alias_analysis

もう 1 つの興味深いパフォーマンス・カウンターとして、キャッシュ (L2 および L3 の両方) にかかるアクセス負荷を監視します。以下の例では、perf を使用して、RHEL6.2 Linux システム GCC でビルドされた SPECcpu2006 483.xalancbmk コンポーネント (「参考文献」を参照) をプロファイリングします。このコンポーネントはメモリー割り当てルーチンを駆使するため、メモリー・サブシステムにかなりの負荷がかかることが予想されます。このプロファイリングを行うには、以下の表 4 に記載するカウンターを OProfile で監視します。

表 4. POWER7 のキャッシュ/メモリー・アクセス・カウンター
カウンター内容
PM_DATA_FROM_L2デマンド・ロードによってローカルの L2 からプロセッサーのデータ・キャッシュがリロードされた回数。
PM_DATA_FROM_L3デマンド・ロードによってローカルの L3 からプロセッサーのデータ・キャッシュがリロードされた回数。
PM_DATA_FROM_LMEMこのプロセッサーが配置されているのと同じモジュールに接続されたメモリーからプロセッサーのデータ・キャッシュがリロードされた回数。
PM_DATA_FROM_RMEMこのプロセッサーが配置されているのとは異なるモジュールに接続されたメモリーからプロセッサーのデータ・キャッシュがリロードされた回数。

プロファイリングによって、以下の結果が出力されます。

リスト 8. 489.Xalancbmk ベンチマークに対する「opreport -l」の出力 (カウンター PM_DATA_FROM_L2_GRP91 および PM_DATA_FROM_L3_GRP91)
CPU: ppc64 POWER7, speed 3550 MHz (estimated) 
Counted PM_DATA_FROM_L2_GRP91 events ((Group 91 pm_dsource1) The processor's Data Cache
was reloaded from the local L2 due to a demand load.) with a unit mask of 0x00 (No unit
 mask) count 1000 
Counted PM_DATA_FROM_L3_GRP91 events ((Group 91 pm_dsource1) The processor's Data Cache
 was reloaded from the local L3 due to a demand load.) with a unit mask of 0x00 (No unit
 mask) count 1000 
samples  %        samples  %        image name     app name       symbol name 
767827   25.5750  7581      0.2525  gcc_base.none  gcc_base.none  bitmap_element_allocate
377138   12.5618  8341      0.2778  gcc_base.none  gcc_base.none  bitmap_operation 
93334     3.1088  3160      0.1052  gcc_base.none  gcc_base.none  bitmap_bit_p 
70278     2.3408  5913      0.1969  libc-2.13.so   libc-2.13.so   _int_free 
56851     1.8936  22874     0.7618  oprofile       oprofile       /oprofile 
47570     1.5845  2881      0.0959  gcc_base.none  gcc_base.none  rehash_using_reg 
41441     1.3803  8532      0.2841  libc-2.13.so   libc-2.13.so   _int_malloc
リスト 9. 489.Xalancbmk ベンチマークに対する「opreport -l」の出力 (カウンター PM_DATA_FROM_LMEM_GRP91 および PM_DATA_FROM_RMEM_GRP91)
Counted PM_DATA_FROM_LMEM_GRP91 events ((Group 91 pm_dsource1) The processor's Data Cache
was reloaded from memory attached to the same module this proccessor is located on.) with
 a unit mask of 0x00 (No unit mask) count 1000 
Counted PM_DATA_FROM_RMEM_GRP91 events ((Group 91 pm_dsource1) The processor's Data Cache
 was reloaded from memory attached to a different module than this proccessor is located 
on.) with a unit mask of 0x00 (No unit mask) count 1000
samples  %        samples  %        image name     app name       symbol name 
1605      0.3344  0              0  gcc_base.none  gcc_base.none  bitmap_element_allocate
1778      0.3704  0              0  gcc_base.none  gcc_base.none  bitmap_operation 
1231      0.2564  0              0  gcc_base.none  gcc_base.none  bitmap_bit_p 
205       0.0427  0              0  libc-2.13.so   libc-2.13.so   _int_free 
583       0.1215  327      100.000  oprofile       oprofile       /oprofile 
0              0  0              0  gcc_base.none  gcc_base.none  rehash_using_reg 
225       0.0469  0              0  libc-2.13.so   libc-2.13.so   _int_malloc

このプロファイル出力を解釈すると、L2 アクセス (PM_DATA_FROM_L2) のカウンター・サンプルの合計値と相対値は、L3 デマンド・リロード (PM_DATA_FROM_L3) よりも遥かに大きいことから、キャッシュの負荷の大部分は L2 アクセスによるもので、デマンドによる L3 リロードはほとんどないことがわかります。キャッシュ・ミスが原因で L2 アクセスが CPU ストールを発生させているのかどうかなど、詳しい情報を得るためには、(さらに多くのカウンターを監視することで) より包括的な分析を行わなければなりませんが、このプロファイルの例から、メイン・メモリー・アクセス (PM_DATA_FROM_LMEM イベント) はキャッシュ・アクセスに比べて大幅に少ないという結論を引き出せます。さらに、リモート・アクセス (PM_DATA_FROM_RMEM イベント) がないことが、リモート NUMA ノードのメモリー・アクセスは行われていないことを示しています。このように、ホットスポットとメモリー・アクセス・パターンを分析することで、最適化作業の方向性が明らかになりますが、この例の場合、CPU ストールの真の原因を突き止めるには、さらに詳しい分析が必要となります。ワークロードのホットスポットとメモリー・アクセス・パターンを単純に明らかにするだけでは、CPU ストールを正確に突き止めることはできません。

より有効なパフォーマンス最適化戦略を考え出すには、OProfile ではなく perf ツールを使ったより詳しい分析が必要です。なぜなら、図 3 に示されている 22 の POWER7 CBM カウンターの多くを同時に監視することで、より効果的なパフォーマンス最適化戦略が見えてくるからです。これらのイベントの多くは、異なるグループに属します。つまり、OProfile を使用するには、同じワークロードを数多く実行しなければなりません。その一方、指定のカウンターが複数のグループに属している場合、perf ツールはハードウェア・カウンターの監視を多重化します。この方法では、結果の精度は低くなるものの、全体としての結果は期待する結果とそれほど変わらない傾向があり、しかもプロファイリングにかかる時間が短縮されるという利点があります。

今度は、perf を使用して SPECcpu2006 483.xalancbmk コンポーネントをプロファイリングしてみます。このコンポーネントをプロファイリングするには、リスト 10 のコマンドを実行します。

リスト 10. POWER7 CBM を生成する perf コマンド
$ /usr/bin/perf stat -C 0 -e r100f2,r4001a,r100f8,r4001c,r2001a,r200f4,r2004a,r4004a,
r4004e,r4004c,r20016,r40018,r20012,r40016,r40012,r20018,r4000a,r2001c,r1001c,r20014,
r40014,r30004 taskset -c 0 ./Xalan_base.none -v t5.xml xalanc.xsl > power7_cbm.dat

上記のコマンドにより、perf は、-c 引数で指定された CPU 上で、-e 引数で指定された 16 進コードで表されたイベントを監視します。taskset を呼び出しているのは、CPU 番号 0 上でコンポーネントが排他的に実行されるようにするためです。ワークロード ./Xalan_base.none -v t5.xml xalanc.xsl は、プロファイリング対象の別のアプリケーションに置き換えることもできます。プロファイリングが完了すると、perf コマンドは 16 進コードで表されたイベントごとの合計カウントを簡単な表のような形で出力し、最後に合計経過秒数も出力します。

リスト 11. 489.Xalancbmk ベンチマークに対する「perf stat」の出力
 Performance counter stats for 'taskset -c 0 ./Xalan_base.none -v t5.xml xalanc.xsl': 


   366,860,486,404 r100f2                                                       [18.15%] 
     8,090,500,758 r4001a                                                       [13.65%] 
    50,655,176,004 r100f8                                                       [ 9.13%] 
    11,358,043,420 r4001c                                                       [ 9.11%] 
    10,318,533,758 r2001a                                                       [13.68%] 
 1,301,183,175,870 r200f4                                                       [18.22%] 
     2,150,935,303 r2004a                                                       [ 9.10%] 
                 0 r4004a                                                       [13.65%] 
   211,224,577,427 r4004e                                                       [ 4.54%] 
   212,033,138,844 r4004c                                                       [ 4.54%] 
   264,721,636,705 r20016                                                       [ 9.09%] 
    22,176,093,590 r40018                                                       [ 9.11%] 
   510,728,741,936 r20012                                                       [ 9.10%] 
    39,823,575,049 r40016                                                       [ 9.07%] 
     7,219,335,816 r40012                                                       [ 4.54%] 
         1,585,358 r20018                                                       [ 9.08%] 
   882,639,601,431 r4000a                                                       [ 9.08%] 
     1,219,039,175 r2001c                                                       [ 9.08%] 
         3,107,304 r1001c                                                       [13.62%] 
   120,319,547,023 r20014                                                       [ 9.09%] 
    50,684,413,751 r40014                                                       [13.62%] 
   366,940,826,307 r30004                                                       [18.16%] 

     461.057870036 seconds time elapsed

perf 出力を POWER7 CBM に照らし合わせて分析するための Python スクリプトが用意されています (「ダウンロード」の power7_cbm.zip を調べてください)。このスクリプトは、収集された仮想カウンターおよびハードウェア・カウンターからカウンター・メトリックを作成します。レポートを作成するには、リスト 12 のコマンドを実行します。

リスト 12. POWER7 CBM Python スクリプトの呼び出し
$ power7_cbm.py power7_cbm.dat

すると、リスト 13 のような結果が出力されます。

リスト 13. 489.Xalancbmk ベンチマークに対する「power7_cbm.py」の出力
CPI Breakdown Model (Complete) 

Metric                         :            Value :    Percent 
PM_CMPLU_STALL_DIV             :    49802421337.0 :        0.0 
PM_CMPLU_STALL_FXU_OTHER       :    67578558649.0 :        5.2 
PM_CMPLU_STALL_SCALAR_LONG     :        2011413.0 :        0.0 
PM_CMPLU_STALL_SCALAR_OTHER    :     7195240404.0 :        0.6 
PM_CMPLU_STALL_VECTOR_LONG     :              0.0 :        0.0 
PM_CMPLU_STALL_VECTOR_OTHER    :     1209603592.0 :        0.1 
PM_CMPLU_STALL_ERAT_MISS       :    22193968056.0 :        1.7 
PM_CMPLU_STALL_REJECT_OTHER    :    18190293594.0 :        1.4 
PM_CMPLU_STALL_DCACHE_MISS     :   261865838255.0 :       20.3 
PM_CMPLU_STALL_STORE           :     2001544985.0 :        0.2 
PM_CMPLU_STALL_LSU_OTHER       :   202313206181.0 :       15.7 
PM_CMPLU_STALL_THRD            :        2025705.0 :        0.0 
PM_CMPLU_STALL_BRU             :   208356542821.0 :       16.2 
PM_CMPLU_STALL_IFU_OTHER       :     2171796336.0 :        0.2 
PM_CMPLU_STALL_OTHER           :    30895294057.0 :        2.4 
PM_GCT_NOSLOT_IC_MISS          :     9805421042.0 :        0.8 
PM_GCT_NOSLOT_BR_MPRED         :     7823508357.0 :        0.6 
PM_GCT_NOSLOT_BR_MPRED_IC_MISS :    11059314150.0 :        0.9 
PM_GCT_EMPTY_OTHER             :    20292049774.0 :        1.6 
PM_1PLUS_PPC_CMPL              :   365158978504.0 :       28.3 
OVERHEAD_EXPANSION             :      590057044.0 :        0.0 
Total                                             :       96.1

このレポートは、許容誤差の範囲内の統計値に基づいているため、最終的なパーセンテージは完全に正確なわけではありません。許容誤差が大きいとしても、全 CPU ストールの約 20% は、データのキャッシュ・ミスが原因となっています (PM_CMPLU_STALL_DCACHE_MISS)。完了した命令の最終的なパーセンテージは、約 28% です (PM_1PLUS_PPC_CMPL)。

今後の最適化では、CPU ストールや GCT (Global Completion Table) のパーセンテージを下げることで、完了した命令のパーセンテージが最大になるように努める必要があります。このレポートに基づき、今度はもう 1 つの分析手段として、ストールが発生しているコードを特定します。それには、perf record コマンドを使用します。このコマンドは、カウンターの値そのもののパフォーマンスを追跡して、プロセスのバックトレースとのマップを作成し、ハードウェア・イベントを最も多く生成したシンボルを特定できるようにします。これは、OProfile が機能する仕組みと同様です。この例では、リスト 14 のコマンドを実行して、PM_CMPLU_STALL_DCACHE_MISS イベントを追跡します。

リスト 14. PM_CMPLU_STALL_DCACHE_MISS イベントに対して perf record を実行する
$ /usr/bin/perf record -C 0 -e r20016 taskset -c 0 ./Xalan_base.none -v t5.xml xalanc.xsl

上記の perf コマンドにより、結果が格納されたデータ・ファイル (通常は「perf.dat」) が作成されます。このファイルは、リスト 15 のperf report コマンドを使用してインタラクティブに読み取ることができます。

リスト 15. 489.Xalancbmk ベンチマークに対する perf report の出力
Events: 192  raw 0x20016
    39.58%  Xalan_base.none  Xalan_base.none  [.] xercesc_2_5::ValueStore::contains 
    11.46%  Xalan_base.none  Xalan_base.none  [.] xalanc_1_8::XStringCachedAllocator
     9.90%  Xalan_base.none  Xalan_base.none  [.] xalanc_1_8::XStringCachedAllocator
     7.29%  Xalan_base.none  Xalan_base.none  [.] xercesc_2_5::ValueStore::isDuplica
     5.21%  Xalan_base.none  libc-2.13.so     [.] _int_malloc 
     5.21%  Xalan_base.none  Xalan_base.none  [.] __gnu_cxx::__normal_iterator<xa
     4.17%  Xalan_base.none  libc-2.13.so     [.] __GI___libc_malloc 
     2.08%  Xalan_base.none  libc-2.13.so     [.] malloc_consolidate.part.4 
     1.56%  Xalan_base.none  Xalan_base.none  [.] xalanc_1_8::ReusableArenaBlock<xa
     1.56%  Xalan_base.none  Xalan_base.none  [.] xalanc_1_8::ReusableArenaBlock<xa
     1.04%  Xalan_base.none  libc-2.13.so     [.] __free
[...]

POWER7 CBM カウンターと perf report ツールを使用したこの分析により、最適化で重点的に取り組まなければならないのは、シンボル xercesc_2_5::ValueStore::contains(xercesc_2_5::FieldValueMap const*) のメモリーおよびキャッシュ・アクセスであると考えられます。

この例は、実行可能な分析のほんの一部に過ぎません。POWER7 CBM から、データ・キャッシュ・ストールが CPU ストールの原因として高い割合を占めている一方、ロード・ストア・ユニット (PM_CMPLU_STALL_LSU) と分岐ユニット (PM_CMPLU_STALL_BRU) の両方がストールの発生元であることがわかります。これらのカウンターについては、さらに詳しい分析を行うことで対処することができます。

事例研究

これから取り上げる事例研究では、ここまで述べたパフォーマンス評価戦略を三角関数の実装を分析するために適用します。分析結果に基づき、最適化の可能性が明らかにされることになります。この事例研究で使用する関数は、直角三角形の斜辺の長さとして定義される、ISO C の hypot 関数です。この関数は、C99 または POSIX.1-2001 で以下のように定義されています。

double hypot(double x, double y);

hypot() 関数は、sqrt(x*x+y*y) を返します。正常に処理が行われると、この関数は、隣辺 x および対辺 y の直角三角形の斜辺の長さを返します。x または y が無限大の場合は、正の無限大が返されます。x あるいは y の一方が NaN であり、他方の引数が無限大でない場合は、NaN が返されます。結果がオーバーフローした場合は、範囲エラーが発生し、関数は対応する HUGE_VAL、HUGE_VALF、または HUGE_VALL を返します。両方の引数が非正規数であり、結果も非正規数となる場合には、範囲エラーが発生し、正しい結果が返されます。

このアルゴリズムは単純そうに思えますが、無限大と NaN を処理する FP (Floating-Point: 浮動小数点) 引数、そして FP 演算に関連するオーバーフロー/アンダーフローによって、パフォーマンスに影響を及ぼす難題が生じます。GNU C ライブラリー (「参考文献」を参照) には、hypot の実装がソース・ツリーの sysdeps/ieee754/dbl-64/e_hypot.c に用意されています。

: このサンプル・コードのライセンス情報は、「付録」に記載されています。

リスト 16. デフォルトの GLIBC hypot ソース・コード
double __ieee754_hypot(double x, double y) 
{ 
        double a,b,t1,t2,y1,y2,w; 
        int32_t j,k,ha,hb; 

        GET_HIGH_WORD(ha,x); 
        ha &= 0x7fffffff; 
        GET_HIGH_WORD(hb,y); 
        hb &= 0x7fffffff; 
        if(hb > ha) {a=y;b=x;j=ha; ha=hb;hb=j;} else {a=x;b=y;} 
        SET_HIGH_WORD(a,ha);    /* a <- |a| */ 
        SET_HIGH_WORD(b,hb);    /* b <- |b| */ 
        if((ha-hb)>0x3c00000) {return a+b;} /* x/y > 2**60 */ 
        k=0; 
        if(ha > 0x5f300000) {   /* a>2**500 */ 
           if(ha >= 0x7ff00000) {       /* Inf or NaN */ 
               u_int32_t low; 
               w = a+b;                 /* for sNaN */ 
               GET_LOW_WORD(low,a); 
               if(((ha&0xfffff)|low)==0) w = a; 
               GET_LOW_WORD(low,b); 
               if(((hb^0x7ff00000)|low)==0) w = b; 
               return w; 
           } 
           /* scale a and b by 2**-600 */ 
           ha -= 0x25800000; hb -= 0x25800000;  k += 600; 
           SET_HIGH_WORD(a,ha); 
           SET_HIGH_WORD(b,hb); 
        } 
        if(hb < 0x20b00000) {   /* b < 2**-500 */ 
            if(hb <= 0x000fffff) {      /* subnormal b or 0 */ 
                u_int32_t low; 
                GET_LOW_WORD(low,b); 
                if((hb|low)==0) return a; 
                t1=0; 
                SET_HIGH_WORD(t1,0x7fd00000);   /* t1=2^1022 */ 
                b *= t1; 
                a *= t1; 
                k -= 1022; 
            } else {            /* scale a and b by 2^600 */ 
                ha += 0x25800000;       /* a *= 2^600 */ 
                hb += 0x25800000;       /* b *= 2^600 */ 
                k -= 600; 
                SET_HIGH_WORD(a,ha); 
                SET_HIGH_WORD(b,hb); 
            } 
        } 
    /* medium size a and b */ 
        w = a-b; 
        if (w>b) { 
            t1 = 0; 
            SET_HIGH_WORD(t1,ha); 
            t2 = a-t1; 
            w  = __ieee754_sqrt(t1*t1-(b*(-b)-t2*(a+t1))); 
        } else { 
            a  = a+a; 
            y1 = 0; 
            SET_HIGH_WORD(y1,hb); 
            y2 = b - y1; 
            t1 = 0; 
            SET_HIGH_WORD(t1,ha+0x00100000); 
            t2 = a - t1; 
            w  = __ieee754_sqrt(t1*y1-(w*(-w)-(t1*y2+t2*b))); 
        } 
        if(k!=0) { 
            u_int32_t high; 
            t1 = 1.0; 
            GET_HIGH_WORD(high,t1); 
            SET_HIGH_WORD(t1,high+(k<<20)); 
            return t1*w; 
        } else return w; 
}

上記コードでは、FP から INT へのビット単位の変換が数多く実行されるアルゴリズムとなっていることが主な原因で、かなり複雑な実装となっています。この実装で前提となっているのは、比較や乗算などの特定の FP 演算は、浮動小数点命令を使用する場合の方が固定小数点命令を使用する場合よりもコストが高くなる、ということです。これは、一部のアーキテクチャーには当てはまりますが、Power Architecture には当てはまりません。

この実装を評価する最初のステップは、プロファイリング可能なベンチマークを作成することです。この例で評価するのは、2 つの引数を取り、(内部関数呼び出しや別のパスがない) 単純なアルゴリズムを使用する関数に過ぎません。したがって、関数を評価するために作成するベンチマークも単純なものにすることができます (「ダウンロード」の hypot_bench.tar.gz を参照)。このベンチマークをパフォーマンス評価の一部として、最適化によってアルゴリズムの実行が高速になるように、あるいはワークロード・パフォーマンス全体に影響を及ぼすアルゴリズムの重要部分が最適化によって高速に実行されるようにしてください。このような合成ベンチマークは、この関数の通常の使用方法を表すものでなければなりません。そして最適化の取り組みでは、リソースも時間も大量に費やす傾向があるため、最も一般的な使用ケースや最も一般的なものとして想定される振る舞いに焦点を絞る必要があります。使用されることが少ないプログラムのコードを最適化しようとすると、リソースを浪費する結果になりがちです。

この事例研究は、1 つの関数のパフォーマンスを分析する例なので、ホットスポット分析は省略して、CMB 分析に焦点を絞ることができます。hypot_bench.c のベンチマークで perf を使用すると、リスト 17 の CBM 情報が出力されます、

リスト 17. hypot ベンチマークに対する「power7_cbm.py」の出力
CPI Breakdown Model (Complete) 

Metric                         :            Value :    Percent 
PM_CMPLU_STALL_DIV             :        8921688.0 :        8.7 
PM_CMPLU_STALL_FXU_OTHER       :    13953382275.0 :        5.0 
PM_CMPLU_STALL_SCALAR_LONG     :    24380128688.0 :        8.7 
PM_CMPLU_STALL_SCALAR_OTHER    :    33862492798.0 :       12.0 
PM_CMPLU_STALL_VECTOR_LONG     :              0.0 :        0.0 
PM_CMPLU_STALL_VECTOR_OTHER    :      275057010.0 :        0.1 
PM_CMPLU_STALL_ERAT_MISS       :         173439.0 :        0.0 
PM_CMPLU_STALL_REJECT_OTHER    :         902838.0 :        0.0 
PM_CMPLU_STALL_DCACHE_MISS     :       15200163.0 :        0.0 
PM_CMPLU_STALL_STORE           :        1837414.0 :        0.0 
PM_CMPLU_STALL_LSU_OTHER       :    94866270200.0 :       33.7 
PM_CMPLU_STALL_THRD            :         569036.0 :        0.0 
PM_CMPLU_STALL_BRU             :    10470012464.0 :        3.7 
PM_CMPLU_STALL_IFU_OTHER       :      -73357562.0 :        0.0 
PM_CMPLU_STALL_OTHER           :     7140295432.0 :        2.5 
PM_GCT_NOSLOT_IC_MISS          :        3586554.0 :        0.0 
PM_GCT_NOSLOT_BR_MPRED         :     1008950510.0 :        0.4 
PM_GCT_NOSLOT_BR_MPRED_IC_MISS :         795943.0 :        0.0 
PM_GCT_EMPTY_OTHER             :    42488384303.0 :       15.1 
PM_1PLUS_PPC_CMPL              :    53138626513.0 :       18.9 
OVERHEAD_EXPANSION             :       30852715.0 :        0.0 
Total                                             :      108.7

プロファイル分析から、CPU ストールの大部分、つまりパフォーマンス損失の主な原因は、ロード・ストア・ユニット (LSU ― PM_CMPLU_STALL_LSU_OTHER カウンター) であることがわかります。LSU にはさまざまなカウンターが関連付けられていますが、CPUストールを分析する際に焦点となるのは、パフォーマンスの低下に関連するカウンターです。POWER でパフォーマンスの低下を示すカウンターは、ロード・ヒット・ストア (LHS) ハザードに関連します。LHS ハザードは、CPU がデータをあるアドレスに書き込んだ直後にそのデータをリロードしようとすると発生する、大規模なストールです。次のステップでは、LHS ハザードがこの特定のアルゴリズムで発生しているのかどうかを調べるために、最初に PM_LSU_REJECT_LHS イベント (16 進のイベント・コード「c8ac」) を確認します (リスト 18 を参照)。

リスト 18. POWER7 のイベント PM_LSU_REJECT_LHS に対して perf record を実行し、結果を perf report で表示する
$ perf record -C 0 -e rc8ac taskset -c 0 ./hypot_bench_glibc
$ perf report
Events: 14K raw 0xc8ac
    79.19%  hypot_bench_gli  libm-2.12.so       [.] __ieee754_hypot
    10.38%  hypot_bench_gli  libm-2.12.so       [.] __hypot
     6.34%  hypot_bench_gli  libm-2.12.so       [.] __GI___finite

このプロファイル出力から、シンボル __ieee754_hypot が PM_LSU_REJECT_LHS イベントの大多数を生成していることがわかります。次に、どの命令がこのイベントを生成しているかを特定するために、コンパイラーが生成したアセンブリー・コードを調査します。シンボル __ieee754_hypot を展開してアセンブリー・コードを表示し、各コードの実行に要した時間の割合を表示するには、perf annotate で __ieee754_hypot シンボルを指定して実行します。これにより、リスト 19 の出力が表示されます。

リスト 19. POWER7 のイベント PM_LSU_REJECT_LHS に対して perf annotate __ieee754_hypot を実行する
         :        00000080fc38b730 <.__ieee754_hypot>:
    0.00 :          80fc38b730:   7c 08 02 a6     mflr    r0
    0.00 :          80fc38b734:   fb c1 ff f0     std     r30,-16(r1)
    0.00 :          80fc38b738:   fb e1 ff f8     std     r31,-8(r1)
   13.62 :          80fc38b73c:   f8 01 00 10     std     r0,16(r1)
    0.00 :          80fc38b740:   f8 21 ff 71     stdu    r1,-144(r1)
   10.82 :          80fc38b744:   d8 21 00 70     stfd    f1,112(r1)
    0.23 :          80fc38b748:   e9 21 00 70     ld      r9,112(r1)
   17.54 :          80fc38b74c:   d8 41 00 70     stfd    f2,112(r1)
    0.00 :          80fc38b750:   79 29 00 62     rldicl  r9,r9,32,33
    0.00 :          80fc38b754:   e9 61 00 70     ld      r11,112(r1)
    0.00 :          80fc38b758:   e8 01 00 70     ld      r0,112(r1)
    8.46 :          80fc38b75c:   d8 21 00 70     stfd    f1,112(r1)
[...]

この実装は、コードの最初のほうでマクロ GET_HIGH_WORD を使用して、後のほうで行うビット単位の演算のために「浮動小数点数」を「整数」に変換します。このマクロは、GLIBC の math/math_private.h 内でリスト 20 のコードを使用して定義されています。

リスト 20. GET_HIGH_WORD マクロの定義
#define GET_HIGH_WORD(i,d)                                      \
do {                                                            \
  ieee_double_shape_type gh_u;                                  \
  gh_u.value = (d);                                             \
  (i) = gh_u.parts.msw;                                         \
} while (0)

このマクロで LHS ストールを発生させている原因として考えられるのは、浮動小数点数の属性を内部値に読み込んでから、それを変数 i に読み出す演算です。POWER7 プロセッサーには、浮動小数点レジスターの内容をビット単位で固定小数点レジスターに移すためのネイティブ命令がありません。POWER でそのネイティブ命令の代わりの方法となるのは、ストア命令によって浮動小数点レジスター内の浮動小数点数をメモリーにストアしてから、同じメモリー位置を固定小数点 (汎用) レジスターにロードすることです。メモリー・アクセスは (L1 データ・キャッシュにアクセスする場合でも) レジスター操作よりも遅いため、ストアが後続のロードを完了するまで、CPU のストールが発生します。

: 「POWER ISA Version 2.06 (POWER7)」(「参考文献」を参照) に詳細が説明されています。

ほとんどの場合、パフォーマンス・カウンター (PC) イベントによってトリガーされる割り込みでは、実行中の命令に近い命令の PC が保存されるため、アセンブリー・アノテーションが完全には正確なものでなくなる可能性があります。こうした振る舞いが発生するのを抑えるために、POWER4 以降では、marked という限定されたパフォーマンス・カウンター一式が用意されています。これを利用した場合、命令にマークが付けられると、その命令が時間フレームあたりに生成するイベント数は少なくなりますが、命令の PC は正確になるはずです。そのため、アセンブリー・アノテーションも正確になります。opcontrol -l によって取得される OProfile カウンターのリストでは、マークが付けられたイベントには PM_MRK というプレフィックスが付けられます。

分析をダブルチェックするために、PM_MRK_LSU_REJECT_LHS カウンターを監視します。PM_MRK_LSU_REJECT_LHS と PM_LSU_REJECT_LHS の両方のカウンターで、同じパフォーマンス・イベントを監視してください。marked カウンター (PM_MRK_LSU_REJECT_LHS) が時間フレームあたりに生成するイベント数は少ないはずですが、アセンブリー・アノテーションの精度は高くなるはずです (リスト 21 を参照)。

リスト 21. POWER7 のイベント PM_MRK_LSU_REJECT_LHS に対して perf record を実行し、結果を perf report で表示する
$ perf record -C 0 -e rd082 taskset -c 0 ./hypot_bench_glibc
$ perf report
Events: 256K raw 0xd082
    64.61%  hypot_bench_gli  libm-2.12.so       [.] __ieee754_hypot
    35.33%  hypot_bench_gli  libm-2.12.so       [.] __GI___finite

続いて、perf annotate __ieee754_hypot を実行することで、リスト 22 のアセンブリー・アノテーションが生成されます。

リスト 22. POWER7 のイベント PM_MRK_LSU_REJECT_LHS に対して perf annotate __ieee754_hypot を実行する
         :        00000080fc38b730 <.__ieee754_hypot>:
[...]
    1.23 :          80fc38b7a8:   c9 a1 00 70     lfd     f13,112(r1)
    0.00 :          80fc38b7ac:   f8 01 00 70     std     r0,112(r1)
   32.66 :          80fc38b7b0:   c8 01 00 70     lfd     f0,112(r1)
[...]
    0.00 :          80fc38b954:   f8 01 00 70     std     r0,112(r1)
    0.00 :          80fc38b958:   e8 0b 00 00     ld      r0,0(r11)
    0.00 :          80fc38b95c:   79 00 00 0e     rldimi  r0,r8,32,0
   61.72 :          80fc38b960:   c9 61 00 70     lfd     f11,112(r1
[...]

別のシンボルにも、生成されたイベントの約 35% に同様の振る舞いが見られます (リスト 23 を参照)。

リスト 23. perf report のもう 1 つの注目点
         :        00000080fc3a2610 <.__finitel>>
    0.00 :          80fc3a2610:   d8 21 ff f0     stfd    f1,-16(r1)
  100.00 :          80fc3a2614:   e8 01 ff f0     ld      r0,-16(r1)

この情報に基づくと、これらのストールを排除するための最適化手法として考えられるのは、FP から INT への変換をなくすことです。POWER プロセッサーには、高速かつ効率的な浮動小数点実行ユニットがあるため、これらの計算を固定小数点命令で行う必要はありません。POWER が GLIBC で現在使用しているアルゴリズム (sysdeps/powerpc/fpu/e_hypot.c) は、LHS ストールを完全になくすために、FP 演算のみを使用しています。その結果、アルゴリズムはリスト 24 のように大幅に単純化されます。

リスト 24. PowerPC の GLIBC hypot ソース・コード
double
__ieee754_hypot (double x, double y)
{
  x = fabs (x);
  y = fabs (y);

  TEST_INF_NAN (x, y);

  if (y > x)
    {
      double t = x;
      x = y;
      y = t;
    }
  if (y == 0.0 || (x / y) > two60)
    {
      return x + y;
    }
  if (x > two500)
    {
      x *= twoM600;
      y *= twoM600;
      return __ieee754_sqrt (x * x + y * y) / twoM600;
    }
  if (y < twoM500)
    {
      if (y <= pdnum)
        {
          x *= two1022;
          y *= two1022;
          return __ieee754_sqrt (x * x + y * y) / two1022;
        }
      else
        {
          x *= two600;
          y *= two600;
          return __ieee754_sqrt (x * x + y * y) / two600;
        }
    }
  return __ieee754_sqrt (x * x + y * y);
}

TEST_INF_NAN マクロにも、簡単な最適化を行います。このマクロは、数字が NaN または INFINITY であるかどうかを調べてから、その後の FP 演算を開始します (これは、NaN と INFINITY での演算によって、この関数の仕様では許可されない FP 例外が発生する可能性があるためです)。POWER7 では、isinf および isnan 関数呼び出しはコンパイラーによって FP 命令に最適化されるため、追加の関数呼び出しは生成されません。それよりも古いプロセッサー (POWER6 以前) では、それぞれの関数呼び出しが生成されます。この最適化は基本的に同じ実装ですが、関数呼び出しを避けるためにインライン化されています。

最後に両方の実装を比較するために、以下の単純なテストを行います。新しいアルゴリズムを使用した場合と使用しない場合とで GLIBC を再コンパイルして、各ベンチマークの実行時間の合計を比較します。デフォルトの GLIBC 実装の結果は、リスト 25 のとおりです。

リスト 25. デフォルトの GLIBC hypot でのベンチマーク
$ /usr/bin/time ./hypot_bench_glibc
INF_CASE       : elapsed time: 14:994339 
NAN_CASE       : elapsed time: 14:707085 
TWO60_CASE     : elapsed time: 12:983906 
TWO500_CASE    : elapsed time: 10:589746 
TWOM500_CASE   : elapsed time: 11:215079 
NORMAL_CASE    : elapsed time: 15:325237 
79.80user 0.01system 1:19.81elapsed 99%CPU (0avgtext+0avgdata 151552maxresident)k 
0inputs+0outputs (0major+48minor)pagefaults 0swaps

最適化したバージョンの結果をリスト 26 に記載します。

リスト 26. 最適化した GLIBC hypot でのベンチマーク
$ /usr/bin/time ./hypot_bench_glibc 
INF_CASE       : elapsed time: 4:667043 
NAN_CASE       : elapsed time: 5:100940 
TWO60_CASE     : elapsed time: 6:245313 
TWO500_CASE    : elapsed time: 4:838627 
TWOM500_CASE   : elapsed time: 8:946053 
NORMAL_CASE    : elapsed time: 6:245218 
36.03user 0.00system 0:36.04elapsed 99%CPU (0avgtext+0avgdata 163840maxresident)k 
0inputs+0outputs (0major+50minor)pagefaults 0swaps

最終的なパフォーマンスの改善は 100% を超え、ベンチマークの時間は半分に縮小されています。

まとめ

ハードウェア・カウンターのプロファイリングによるパフォーマンス評価は、特定のプロセッサーでのワークロードの振る舞いを理解し、パフォーマンスの最適化をするとよい対象を知る手掛かりをつかむ上で強力なツールです。最新の POWER7 プロセッサーでは、何百ものパフォーマンス・カウンターを使用することができます。この記事ではワークロードと CPU ストールを対応付ける単純なモデルを紹介しました。また、POWER7 CBM を理解するのは多少複雑な作業であるため、Linux でその作業を単純化するツールについても説明しました。パフォーマンス評価の戦略では、ホットスポットを見つける方法、アプリケーションのメモリー・パターンを理解する方法、POWER7 CBM を使用する方法に焦点を絞りました。そして最後に、GLIBC 内で最近行われた最適化を三角関数に対して適用する例を用いて、パフォーマンス分析を使用してコードを最適化する方法を説明しました。

付録

この記事を複製、配布、変更する許可は、GNU Free Documentation License, Version 1.3 の条件の下に与えられています。変更不可部分、表カバー・テキスト、裏カバー・テキストは存在しません。ライセンスのコピーは、http://www.gnu.org/licenses/fdl-1.3.txt から入手することができます。


ダウンロード

内容ファイル名サイズ
GLIBC hypot benchmarkhypot_bench.tar.gz6KB
Python script to format perf outputpower7_cbm.zip2KB

参考文献

学ぶために

製品や技術を入手するために

  • OProfile について、このプロジェクトの Web サイトで詳細を学んでください。
  • perf ツールの詳細を学んでください。このツールのコードは Linux カーネル・ソース内で保守されています。
  • libpfm4 プロジェクトのサイトで、perf に固有の POWER7 コードを調べてください。
  • GNU C ライブラリーについては、このプロジェクトのページを参照してください。
  • Advance Toolchain for Linux on POWER について調べてください。これは、IBM ハードウェアの機能を使いやすくする目的でカスタマイズされた無料のソフトウェア・アプリケーション開発製品のコレクションです。
  • ご自分に最適な方法で IBM 製品を評価してください。評価の方法としては、製品の試用版をダウンロードすることも、オンラインで製品を試してみることも、クラウド環境で製品を使用することもできます。また、SOA Sandbox では、数時間でサービス指向アーキテクチャーの実装方法を効率的に学ぶことができます。

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。プロフィールで選択した情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux, Open source
ArticleID=824372
ArticleTitle=Linux on POWER のパフォーマンスを評価する
publish-date=07122012