マルチコア実行には、アプリケーションの高速化や低消費電力化など、さまざまな利点がある。ただし、その長所を引き出すためには、複数のコアを有効に利用できるように、適切な方法でソフトウェアを作成及び配置する必要がある。
マルチコア実行では、複数のプログラムが同時並列に動作するため、デッドロックなど、さまざまな問題が起こる。さらに、OSなどのタスクのコアに対する動的割り当てを利用する場合は、問題が発生した状況の把握や問題の再現性困難となる現象が起こりがちである。
こうしたマルチコア特有の問題を解析したり、解決したりする際には、CPU負荷やプログラム内部の依存関係などを可視化する手法が有効である。マルチコア向けソフトウェア開発支援ツール製品の中には、さまざまな可視化機能を備えているものが少なくない。
本章では、マルチコア上での理想的な実行状態を阻害するいくつかの課題を取り上げ、それらの課題を解析、解決するための情報の可視化について説明する。
最初に、マルチコアソフトウェア開発における可視化の有用性について述べる。
シングルコア実行では、動作プロセッサが一つであるため、時間順に表示されたログのような情報でも、ある程度の解析ができる。
マルチコア実行の場合、複数プロセッサが同時に動作するため、その動作状況を表示する際には、コア方向と時間方向の少なくとも2軸が必要であり、視覚的表示が有効である。
マルチコア動作に問題がある場合、可視化により、短時間で問題点を発見できることが多い。
マルチコア実行の理想的状況と可視化について述べる。
図 1 のように、各プロセッサ(コア0~コア2)ごとの実行状況について、時間軸を合わせて可視化することにより、CPU利用状況や負荷分散状況が分かる。
「ガントチャート」や「タイミングチャート」などの呼び方がある。
図 1 の例は、すべてのコアにおいて、常に処理が実行され、同時に終了しているため、理想的な状況と言える。
並列実行に課題がある例を紹介し、可視化を行う理由について説明する。
図 2 は、並列実行があまりうまくいっていない例
可視化により、ひと目で問題の有無や深刻度、状況が分かる。さらなる原因解析についても、可視化した結果(グラフや図)を参照しながら進めることができる。
組込みシステムではOS(リアルタイムOS)を使うことが多い。リアルタイムOSにはOSオブジェクトの状態をログとして記録する機能があるが、ログだけから現象を把握することは容易ではないため,可視化が重要である。
ログでは、各コアのイベントが1次元で並んでいるため、コア間の関係が読み取りづらい。
次に、マルチコア実行に課題がある場合の原因についていくつか述べ、可視化により原因解析する例について説明する。
負荷分散に問題があると、性能に影響する。ここでは負荷分散に関するマルチコア実行の課題と可視化について紹介する。
図 4 の例では、改善前、コア0は常に処理を実行しているにもかかわらず、コア1には時間の空きがある。可視化により、負荷分散に問題があることがひと目で分かる。
例えば、処理Dをコア1に移すことにより、性能を改善できる。
負荷分散の可視化に関連する用語を説明する。
依存関係は並列実行に制約を与えるため、性能に影響する。ここでは依存関係によるマルチコア実行の課題と可視化について紹介する。
図 5 は処理の実行順序制約を示している。
処理C、Dはともに、処理Aと処理Bの両方が終了していないと実行できない。
実際の実行が 図 6 のように可視化されたとすると、コア0に長い待ちがあり、効率のよいマルチコア実行ができていないことがひと目で分かる。
このような処理の関係を「依存関係」と呼ぶ。
依存関係には「制御依存」と「データ依存」がある。
制御依存:プログラム制御上の順序関係
例:
if 処理A then 処理B else 処理C
データ依存:データ(変数など)の読み書き順序によって規定される順序関係
例:
// 処理A
i = f(x); // 処理B j = g(i);
としたとき、処理Bは処理Aの結果データを使っているのでデータ依存。
同一メモリアドレスに対する読み書き順序の問題により、不具合が起こる場合がある。ここではメモリ読み書き順序にかかわるマルチコア実行の課題と可視化について紹介する。
いずれの場合も、その対応により並列性が下がるので、対応箇所の範囲は最小限に抑える必要がある。ここで、ポインタの利用などを見逃すと、不具合が生じる。不具合箇所発見のため、可視化が重要になる。
逐次実行および並列実行に対し、トレースなどを用いた動的解析によりメモリアクセス履歴を取り、対応する命令アドレス(プログラム上の場所)、メモリアドレス(プログラム内の共有変数名)、アクセス属性(Read/Write、Lockなど)、値、時刻などを表示する。
同一メモリアドレスに対し、逐次実行におけるメモリアクセス順と異なる箇所、あるいは同時実行可能性がある箇所の色分け表示などを行って可視化する。
図 8 は右図はIntel社 Inspectorにおける可視化の例。
保護されていないデータ競合(同時アクセスの可能性)箇所、および該当共有変数をソースコード上で色分け表示している。
メモリ読み書き順序が問題となる場合には、RAW(Read After Write)、WAW(Write After Write)、WAR(Write After Read)の3種類がある。Read After Readは順序が変わっても不具合が起こらないため、考慮しない。
図 9 、 図 10 、 図 11 は、逐次実行ではスレッド1→スレッド2の順だったものが、同時実行により、メモリアクセス順が入れ替わる例である。すべてのRead、Writeは同じメモリアドレスへのアクセスとする。
メモリ読み書き順序を解析するためには、プログラム上のメモリアクセス箇所と、その時のメモリアドレスを特定する必要がある。解析には「静的解析」と「動的解析」があるが、以下の理由で可視化には動的解析が使われることが多い。
大域変数宣言された共有変数などは静的解析で特定可能であり、コンパイラなどで自動的に対応できる。
C言語のポインタ変数などは、多くの場合、実際に実行させなければ同一アドレスになるかどうかは分からず、動的解析が必要。すべてのポインタ変数への読み書きを保護する、あるいは順序制約をつけると性能が大幅に落ちるため、必要な箇所のみの保護にしたい。
例えば、次の関数
void func1(int \*a, int \*b) { ... }
において変数aとbが同一アドレスになるかどうかは、呼び出し側に依存している。開発プロジェクト内の関数利用規則で制約をかけたとしても、人的ミスにより誤った使い方をすると不具合が生じる。このような不具合の発見には、動的解析による可視化が有効である。
可視化では、リアルタイムOSの考慮が必要になる。
タスク状態を可視化することにより,処理が滞りなく進んでいるかどうかを判断できる。
マルチコア実行におけるタスクスケジューリングの基本は、以下の二つ。
マルチコア実行では、コアごとのタスクの状態を可視化する。
リアルタイムOSには、数種類の同期通信オブジェクトがある。マルチコア実行で処理が理想的に動作している場合、これらのオブジェクトの状態を可視化することにより原因を解析が容易となる。
FIFOによるデータ通信で知りたい情報( 図 18 の可視化例を参照)
セマフォによる排他制御で知りたい情報( 図 19 の可視化例を参照)
スピンロックによる排他制御で知りたい情報( 図 20 の可視化例を参照)
割込み応答時間の悪化の原因を特定するために、可視化が有効。
マルチコア実行で発生する状況を解析するために、可視化が有効。
近年のプロセッサは、高速化・高性能化のためにさまざまな手法を取り入れている。これらの手法がプログラムの性能に影響を及ぼすため、性能解析のためにプロセッサの情報を可視化したい。
ここまで説明した要因について問題がないにもかかわらず並列実行性能が向上しない場合、バスネットワーク競合が考えられる。この問題の解析に対しても、可視化が有効である。
マルチコア実行において、すんなりと最高並列性能が達成できることは少ない。場合によっては途中でデッドロックするなど、並列要因により実行が止まることすらある。
そのような場合のデバッグや性能解析においては、時間軸のほかに、複数コアや複数タスクの状況を解析しなければならず、少なくとも2次元の解析が必要となる。
そのため、可視化を行い、問題発生時刻付近における複数コアや複数タスクの状況を見ることが、問題解析を容易にする。
並列実行を妨げる要因にはさまざまなものがあり、本資料ではそのいくつかの要因について説明し、可視化の例、およびその見方を紹介した。
コア数が多くなると、画面上の情報量が増え、解析が困難になるため、必要最小限、かつ、設計者に分かりやすい表示方法が重要となる。