マルチスレッドにおいて並列に処理を実行するマルチスレッド並列機能には、換言すれば、スレッド並列には、以下の2つの並列化モデルがある。第1の並列化モデル(以下、並列化モデル1という)は、処理の絶対量が確定している場合の並列化である。並列化モデル1が適用される処理としては、例えば、ループ、配列、代入等の処理がある。第2の並列化モデル(以下、並列化モデル2という)は、処理の絶対量が不確定な場合の並列化である。並列化モデル2が適用される処理としては、例えば、リスト検索、再帰的アルゴリズム等の処理がある。マルチスレッド並列機能のロジックとしては、ループを並列処理する並列化モデル1が主に使用されてきたが、リスト検索や再帰的アルゴリズムを並列処理する並列化モデル2が増加している。
並列化モデル1では、処理の絶対量、例えばループの繰り返し数を事前に知ることができる。このため、ループの実行を各スレッドに均等に分割することができる。これにより、並列化モデル1において、実行性能を向上させることができる。
並列化モデル2では、処理の絶対量、例えばリストの長さや再帰呼出の回数等を事前に知ることができない。このため、処理を均等に分割することができない。そこで、プログラムを相互に依存関係を持たない複数のブロックに分割し、分割したブロックを、空きスレッド、換言すれば、同期待ちのスレッドに割り当てる。これにより、並列化モデル2において、各スレッドに処理を均等に分割して、実行性能を向上させることができる。
図10は、本発明者が検討したマルチスレッド並列機能の説明図であり、並列化モデル2における、複数のスレッド間の同期(以下、単に同期という)から未実行の命令コードへのジャンプを示す。
ここで、以下の並列化モデル2の説明においては、同期から未実行の命令コードへジャンプする処理の一例として、OpenMPのTASK構文を用いる。TASK構文によれば、リスト検索や再帰的アルゴリズム等、処理の絶対量が決まっていない不規則なアルゴリズムを並列化することができる。
図10に示すように、1つのプログラムが、ブロックB101〜B103、B201、B301に分割され、マルチスレッドにおいて、並列処理される。例えば、ブロックB101がスレッドaで実行される場合、TASK構文で囲まれた領域の命令コードの実行を、他のスレッドにスケジューリングすることができる。例えば、TASK構文指示行「!$OMP TASK」とTASK構文終了行「!$OMP END TASK」とにより囲まれたブロックB201及びB301が、TASK構文で囲まれた領域である。図10の場合、最初に出現するTASK構文を含むブロックB201に含まれる命令コードをスレッドbにスケジューリングし、次に出現するTASK構文を含むブロックB301に含まれる命令コードをスレッドcにスケジューリングすることができる。
スレッドb及びスレッドcは、空きスレッド、換言すれば、BARRIER構文等で同期待ちをしているスレッドである。従って、スレッドb及びスレッドcは、同期から未実行の命令コード、換言すれば、TASK構文で囲まれた領域に含まれる命令コードにジャンプすることになる。
ここで、マルチスレッド並列機能における同期について考察すると、適用される処理の相違から、並列化モデル1における同期には、高速であることが要求される。一方、並列化モデル2における同期には、前述したように、同期から未実行の命令コードへのジャンプが可能であることが要求される。従って、本来は、並列化モデル1及び並列化モデル2の双方をサポートした場合、並列化モデル1及び並列化モデル2について、別々の同期ロジックを用意することが望ましい。
図11は、本発明者が検討したマルチスレッド並列機能の説明図であり、並列化モデル1及び並列化モデル2における同期について示す。
OpenMPのBARRIER構文、例えば「!$OMP END SINGLE」には、同期処理の実行が暗黙的に含まれている。従って、図11に示すように、並列化モデル1(MODEL#1)に、BARRIER構文「!$OMP END SINGLE」が含まれる場合がある。また、並列化モデル2(MODEL#2)に、同様に、BARRIER構文「!$OMP END SINGLE」が含まれる場合がある。
このため、並列化モデル1及び並列化モデル2をサポートした場合、実際には、別々の同期ロジックを用意することはできず、並列化モデル1及び並列化モデル2の双方に対応した同期ロジックが必要となる。
一方、同期それ自体の実行時間(以下、同期コストという)は、並列して実行されるスレッドの数に比例する。このため、CPUコアの数の増加に伴って、並列して実行されるスレッドの数が増加すると、同期コストの増加が問題となる。換言すれば、バリア内の処理量の増加、同期のためのスレッド間の通信の増加が、各々のCPUコアの処理の負担となり、性能低下を招く原因となる。
この結果、CPUコアの数を増加したとしても、コンピュータの性能がCPUコアの数を増加に比例して向上しなくなる。また、マルチスレッド並列機能による処理時の性能が、逐次処理時の性能よりも劣化する可能性が生じてしまう。
そこで、本発明者は、並列化モデル1及び並列化モデル2に別々の同期ロジックを適用する代わりに、並列化モデル1及び並列化モデル2の双方に適用することができる高速な同期ロジックについて検討した。
図12及び図13は、本発明者が検討したスレッド並列処理を示す。特に、図12は、並列化モデル1において用いられる同期ロジックであるカスケード方式を示す。また、図13は、カスケード方式を並列化モデル2の同期ロジックとして適用する場合における問題点を示す。
図12に示すように、並列化モデル1における同期ロジックとして、カスケード方式が適用される。カスケード方式の同期は、マスターとなるスレッドが存在せず、各スレッドがlog2N回(小数点以下繰上げ)のバリアフラグのロード及び/又はストアを繰り返すことにより実現される同期である。Nはスレッドの数である。
図12において、同期には、「log2N個のバリアフラグ×スレッド数」の領域を使用する。この場合、スレッドの数Nは「5」であるので、3個のバリアフラグを1組として5組使用する。5個のスレッド#0〜#4に対応するバリアフラグの設定領域を、各々、同期領域#0〜#4とする。同期領域#0〜#4は、各々、3個のバリアフラグを設定することができるようにされる。
step#1において、スレッド#Nは、同期領域Nのi番目のバリアフラグを設定する。ここで、変数iは「1」から開始される。例えば、スレッド#0は、同期領域#0の1番目のバリアフラグを設定する(換言すれば、ストアする)。図12においては、同期領域#0の1番目のバリアフラグの設定が、同期領域#0の1番目の領域に網掛けにより示される。他のスレッド#1〜#4においても、同期領域#1〜#4の1番目のバリアフラグが設定される。
step#2において、スレッド#Nは、同期領域「(N+Σ2j)%スレッド数」のi番目のバリアフラグをロードする。ここで、変数jは「0」から開始される。この場合、同期領域「(N+1)%5」の1番目のバリアフラグがロードされる。例えば、スレッド#0についての同期領域#0の1番目のバリアフラグは、スレッド#1についての同期領域#1の1番目の領域にロードされる。
スレッド#Nは、step#2においてロードしたバリアフラグが設定済みの場合、変数iをインクリメントしてstep#3に進む。
step#3において、スレッド#Nは、同期領域Nのi番目のバリアフラグを設定する。例えば、スレッド#0は、同期領域#0の2番目のバリアフラグを設定する。他のスレッド#1〜#4においても、同期領域#1〜#4の2番目のバリアフラグが設定される。
step#4において、スレッド#Nは、同期領域「(N+Σ2j)%スレッド数」のi番目のバリアフラグをロードする。この場合、同期領域「(N+2)%5」の2番目のバリアフラグがロードされる。例えば、スレッド#0についての同期領域#0の2番目のバリアフラグは、スレッド#2についての同期領域#2の2番目の領域にロードされる。
スレッド#Nは、step#4においてロードしたバリアフラグが設定済みの場合、変数iをインクリメントしてstep#5に進む。
step#5において、スレッド#Nは、同期領域Nの3番目のバリアフラグを設定する。例えば、スレッド#0は、同期領域#0の3番目のバリアフラグを設定する。他のスレッド#1〜#4においても、同期領域#1〜#4の3番目のバリアフラグが設定される。
step#6において、スレッド#Nは、同期領域「(N+Σ2j)%スレッド数」のi番目のバリアフラグをロードする。この場合、同期領域「(N+4)%5」の3番目のバリアフラグがロードされる。例えば、スレッド#0についての同期領域#0の3番目のバリアフラグは、スレッド#4についての同期領域#4の3番目の領域にロードされる。
スレッド#Nにおいて、step#6においてロードしたバリアフラグが設定済みの場合、バリア同期が成立する。
本発明者の検討によれば、このような並列化モデル1における同期ロジックであるカスケード方式を並列化モデル2に適用すると、同期が成立していないにも拘わらず、スレッドが同期を終えてしまう。これは、並列化モデル2の同期において、同期から未実行の命令コードへのジャンプが可能であることに起因している。
図13に示すように、step#1において、スレッド#Nは、同期領域Nのi番目のバリアフラグを設定する。従って、前述したように、スレッド#0〜4は、各々、同期領域#0〜4の1番目のバリアフラグを設定する。この後、step#2において、スレッド#Nは、同期領域「(N+Σ2j)%スレッド数」のi番目のバリアフラグをロードするはずである。
ところが、この場合、スレッド#4が未実行の命令コードにジャンプしたことで、同期が未成立の状態となっているが、スレッド#1は同期を終了してしまっている。具体的には、スレッド#4が、TASK構文で囲まれた領域に、同期からのジャンプにより実行される命令コード「call traverse(P)」を含む。このため、スレッド#4は、この命令コードを実行することにより、step#1の実行後かつstep#2の実行前に、一点鎖線で示すように、同期から実行にジャンプする。換言すれば、スレッド#4は、命令コード「call traverse(P)」により、未実行の命令コードへジャンプする。しかし、この場合、スレッド#4が実行にジャンプしたにも拘わらず、スレッド#1は、同期を終了してしまっている。このため、同期において必要とされる、同期後の命令コードが同期前の全ての実行が終了するまで実行されないことが、保証されないことになる。
具体的には、スレッド#1の1番目のバリアフラグがスレッド#2にロードされるので、スレッド#2の2番目のバリアフラグがストアされる。このスレッド#2の2番目のバリアフラグがスレッド#4にロードされるので、スレッド#4の3番目のバリアフラグがストアされる。このスレッド#4の3番目のバリアフラグがスレッド#3にロードされるので、スレッド#3ではバリア同期が成立する。この結果、スレッド#3は、同期から抜けて、同期後の命令コードを、同期前の全ての実行が終了する前に実行してしまう。
開示されるマルチスレッド処理装置及び方法は、マルチスレッド並列機能、換言すれば、スレッド並列において、複数の並列化モデルについて同一の同期ロジックで高速に同期することができる。
図1は、スレッド処理装置の構成の一例を示す図である。
図1に示すように、スレッド処理装置は、複数のCPU1と、メインメモリ2とを含む。CPU1の数は2個に限られない。複数のCPU1とメインメモリ2との間は、バス3により相互に接続される。メインメモリ2は、複数のCPU1により使用される。
CPU1は、複数のCPUコア12、複数のキャッシュ13、共用キャッシュ14を含む。CPU1におけるCPUコア12の数は2個に限られない。キャッシュ13はCPUコア12に対応して設けられる。共用キャッシュ14は、CPU1において1個設けられ、複数のCPUコア12により共用される。
複数のCPUコア12上で、複数のスレッド11が並列に実行される。換言すれば、複数のCPUコア12は、プログラムを分割した複数のスレッド11を並列して実行する。従って、複数のCPUコア12がスレッド実行部であると考えてよい。複数のスレッド11の並列実行は、スレッド実行部である複数のCPUコア12がライブラリ21を参照することにより、ライブラリ21に従って制御される。従って、複数のCPUコア12及びライブラリ21がスレッド制御部であると考えてよい。
なお、図1の例においては、1個のCPUコア12上で、1個のスレッド11が実行される。ここで、CPUコア12は、物理CPUコアであるが、これに限られず、論理的なCPUコアであっても良い。
メインメモリ2は、ライブラリ(スレッドライブラリ)21と、実行可能ファイル22とを含む。ライブラリ21は、メインメモリ2に予め用意される。実行可能ファイル22は、図2を参照して後述するように、例えば、ソースプログラムをコンパイルすることにより得られる。実行可能ファイル22は、実行可能な形式とされた1個のプログラムである。ライブラリ21は、実行可能ファイル22に対応して、換言すれば、プログラム(従って、ソースプログラム)毎に設けられる。
ライブラリ21は、taskフラグ領域211と、idフラグ領域212と、バリアフラグ領域213とを含む。taskフラグ領域211は、スレッド11がtaskフラグを設定する領域である。idフラグ領域212は、スレッド11がidフラグを設定する領域である。バリアフラグ領域213は、スレッド11がバリアフラグを設定する領域である。
taskフラグ領域211は、1個だけ、換言すれば、プログラム毎に設けられる。idフラグ領域212は、1個だけ、換言すれば、プログラム毎に設けられる。従って、idフラグ及びtaskフラグは、プログラム全体で1個、換言すれば、shared変数である。
前述したように、同期には「log2N個のバリアフラグ×スレッド数」の領域を使用するので、バリアフラグ領域213は各々のスレッド11に対応して設けられ、かつ、各々のバリアフラグ領域213はlog2N個のフラグを設定できるようにされる。Nはスレッド11の数である。
図2は、スレッド処理装置の構成の説明図である。
実行可能ファイル22は、ソースプログラム41をコンパイラ42によりコンパイルすることにより得られる。例えば、ソースプログラム41及びコンパイラ42は、スレッド処理装置以外のコンピュータに設けられ、コンパイルにより得られた実行可能ファイル22がメインメモリ2に格納される。
ソースプログラム41は、例えば、同期からのジャンプにより実行される命令コードを含まないことにより、処理の絶対量が確定しているプログラムである。この場合、ソースプログラム41は、例えば、ループ、配列、代入等の処理を含むプログラムである。処理の絶対量は、例えばループの繰り返し数であり、事前に知ることができる。
ここで、同期からのジャンプにより実行される命令コードを、第1の命令コードということとする。第1の命令コード以外の命令コード、換言すれば、同期からのジャンプによっては実行されない命令コードを、第2の命令コードということとする。
ソースプログラム41が第1の命令コードを含まない場合には、ソースプログラム41は、並列化モデル1の並列化指示行を含む。並列化モデル1の並列化指示行は、並列化モデル1によりスレッドを並列実行することを指示する。
また、ソースプログラム41は、例えば、第1の命令コードを含むことにより処理の絶対量が不確定なプログラムである。この場合、ソースプログラム41は、例えば、リスト検索、再帰的アルゴリズム等の処理を含むプログラムである。処理の絶対量は、例えばリストの長さや再帰呼出の回数等であり、事前に知ることができない。
ソースプログラム41が第1の命令コードを含む場合には、ソースプログラム41は、並列化モデル2の並列化指示行を含む。並列化モデル2の並列化指示行は、例えばOpenMPのTASK構文指示行である。並列化モデル2の並列化指示行は、並列化モデル2によりスレッドを並列実行することを指示する。
コンパイラ42は、並列化指示行を解析して、ライブラリ21を呼び出すライブラリ呼出指示を生成する。なお、コンパイラ42が、ソースプログラム41が並列化可能であるか否かを解析して、並列化ライブラリを追加するようにしても良い。
以上により、実行可能ファイル22が生成される。実行可能ファイル22は、ソースプログラム41をコンパイルした結果として、例えば、OpenMPのTASK構文指示行を含む。実行可能ファイル22は、TASK構文で囲まれた命令コードを実行することにより、当該TASKの実行へジャンプする。実行可能ファイル22は、ソースプログラム41をコンパイルした結果として、同期指示行を含む。実行可能ファイル22は、同期指示行を実行することにより、同期処理を実行する。
前述したように、TASK構文は、リスト検索、再帰的アルゴリズム等のような、処理の絶対量が決まっていない不規則なアルゴリズムを並列化する構文である。従って、実行可能ファイル22はリスト検索、再帰的アルゴリズム等の処理を実行するプログラムであり、実行可能ファイル22には前述の並列化モデル2が適用される。
なお、処理の絶対量が決まっていない不規則なアルゴリズムを並列化することにより実行されるプログラムは、TASK構文を含むプログラムに限られない。換言すれば、TASK構文に代えて、処理の絶対量が決まっていない不規則なアルゴリズムを並列化する構文を含むプログラムであれば、並列化モデル2により並列化される。従って、開示される例は、TASK構文以外の処理の絶対量が決まっていない不規則なアルゴリズムを並列化する構文を含むプログラムについての同期処理に適用することができる。
実行可能ファイル22は、相互に依存関係を持たない複数のブロック即ちスレッド11に分割されて、並列実行される。従って、TASK構文指示行を含むスレッド11と、TASK構文指示行を含まないスレッド11とが存在する。また、複数のスレッド11は、各々、同期指示行を含む。同期指示行は、ライブラリ21による同期処理の実行を指示する。
一方、ライブラリ21は、図1に示すtaskフラグ領域211、idフラグ領域212及びバリアフラグ領域213に加えて、TASK構文検出部214、TASK構文終了部215、同期処理部216を含む。
TASK構文検出部214は、当該スレッド11、換言すれば、プログラムに第1の命令コードが存在する場合に、taskフラグ領域211にtaskフラグ(第1のフラグ)を設定する。taskフラグは、プログラム、換言すれば、実行可能ファイル22における第1の命令コードの存在を示す。具体的には、taskフラグは、第1の命令コードが検出される毎に「+1」だけインクリメントされる。taskフラグの初期値は、例えば「0」とされる。従って、taskフラグの値は、基本的には、プログラムに存在する第1の命令コードの数を示す。
一方、TASK構文検出部214は、当該スレッド11に第1の命令コードが存在しない場合には、換言すれば、当該スレッド11に第2の命令コードのみが存在する場合には、taskフラグ領域211にtaskフラグを設定しない。
また、TASK構文検出部214は、当該スレッド11、換言すれば、プログラムに第1の命令コードが存在する場合に、idフラグ領域212にidフラグ(第2のフラグ)を設定する。idフラグは、複数のスレッド11の中で第1の命令コードが検出されたスレッド11を表す。idフラグとしては、TASK構文検出部214を実行中のスレッド11のスレッド番号が用いられる。スレッド番号は、予め定められ、スレッド11を一意に定める識別情報である。idフラグ領域の初期値は、例えば「−1」、換言すれば、スレッド番号が取りえない値とされる。
更に、TASK構文検出部214は、taskフラグが設定された場合には、その後に第1の命令コードを検出しても、当該検出をしたスレッド11を示す新たなidフラグを設定しない。換言すれば、taskフラグが設定された場合には、idフラグの更新は、禁止される。従って、idフラグは、taskフラグがリセットされた場合に、更新される。
これに加えて、TASK構文検出部214は、idフラグが設定された場合、換言すれば、idフラグが「−1」以外の値である場合には、その後に第1の命令コードを検出しても、当該検出をしたスレッド11を示す新たなidフラグを設定しない。換言すれば、idフラグが設定された場合には、idフラグの更新は、禁止される。従って、idフラグは、その値が「−1」である場合に、更新される。これにより、idフラグにより示される1個のスレッド11が必ず同期の成立を待つことになる。この結果、同期後の命令コードが同期前の全ての実行が終了するまで実行されないことを、保証することができる。
一方、TASK構文検出部214は、当該スレッド11に第1の命令コードが存在しない場合には、換言すれば、当該スレッド11に第2の命令コードのみが存在する場合には、idフラグ領域212にidフラグを設定しない。
TASK構文終了部215は、当該スレッド11、換言すれば、プログラムに存在する第1の命令コードが実行された場合に、taskフラグをデクリメントする。具体的には、taskフラグは、第1の命令コードが実行される毎に「−1」だけデクリメントされる。従って、taskフラグの値は、実際には、プログラムに存在する第1の命令コードであって実行されていない第1の命令コードの数を示す。一旦インクリメントされたtaskフラグがデクリメントされて「0」となった状態をリセットという。
同期処理部216は、カスケード方式による同期処理を実行する。これにより、複数のスレッド11は、各々、カスケード方式により同期する。カスケード方式による同期においては、マスターとなるスレッドが存在しない。マスターとなるスレッドが存在しないので、全てのスレッドが均等な処理を実行することができる。
また、同期処理部216は、taskフラグがリセットされた場合に、その時点で設定されているidフラグをリセットする。これにより、前述したように、新たなidフラグを設定することができる。一旦「−1」以外の値とされたidフラグが再度「−1」とされた状態をリセットという。
図2において、例えば、第1のスレッド11(図3におけるスレッド#0)は、実行可能ファイル22の先頭行から順に処理を実行して、TASK構文指示行に到達する。これに応じて、第1のスレッド11は、ライブラリ21のTASK構文検出部214を参照、換言すれば、実行する。これにより、第1のスレッド11は、詳しくは図3〜図6を参照して後述するように、idフラグとtaskフラグを設定する。換言すれば、スレッド制御部により、taskフラグとidフラグとが設定される。これにより、同期処理部216は、並列化モデル1によるスレッド並列処理及び並列化モデル2によるスレッド並列処理の双方について、カスケード方式による同期処理を実行することができる。
第1のスレッド11は、TASK構文指示行を実行した後、当該TASK構文で囲まれた命令コードを第2のスレッド11にスケジューリングし、更に、その後、当該TASK構文終了行を検出する。なお、TASK構文指示行とTASK構文終了行とを合わせて、TASK構文という。TASK構文終了行を検出した第1のスレッド11は、当該TASK構文終了行の次の行から順に処理を実行して、同期指示行に到達する。これに応じて、第1のスレッド11は、ライブラリ21の同期処理部216を参照して実行する。
この時、idフラグには、TASK構文指示行、換言すれば、第1の命令コードを検出した第1のスレッド11を示すスレッド番号が設定されている。従って、第1のスレッド11は、同期処理を実行しない。
一方、第2のスレッド11(図3におけるスレッド#1、#2)は、TASK構文で囲まれた命令コードの実行にジャンプする。この後、第2のスレッド11は、TASK構文で囲まれた命令コードの実行を終了すると、これに応じて、ライブラリ21のTASK構文終了部215を参照して実行する。これにより、第2のスレッド11は、詳しくは後述するように、taskフラグをデクリメントする。この後、第2のスレッド11は、同期指示行に到達して、これに応じて、ライブラリ21の同期処理部216を参照して実行する。
この時、taskフラグのデクリメントに応じて、同期処理を実行せずに同期待ちの状態にあった第1のスレッド11が、同期処理部216により、idフラグをデクリメントする。これにより、第1のスレッド11は、同期処理を実行することができる。
ここで、複数のスレッド11は、各々、log2N回(小数点以下繰上げ)のバリアフラグのロード及びストアを繰り返すことにより同期する。Nはスレッド11の数である。log2N回というロードの回数は最低必要回数である。バリアフラグの初期値は、例えば「0」とされる。ロードしたバリアフラグが未設定である(「0」である)場合には、設定済みの(「1」である)バリアフラグがロードされるまで、ロードが繰り返される。
このカスケード方式の同期において、同期処理部216は、idフラグが設定された場合に、idフラグにより表されるスレッド11において、同期処理を実行することなく、taskフラグがリセットされるまで同期処理の実行を待つ。換言すれば、同期処理部216は、idフラグが設定された場合に、idフラグにより表されるスレッド11において、バリアフラグを設定しない(換言すれば、バリアフラグの「1」をストアしない)。
従って、同期処理部216は、idフラグにより示されるスレッド11において、taskフラグのリセットの後に、同期処理を実行する。換言すれば、同期処理部216は、idフラグが設定された場合に、idフラグにより表されるスレッド11において、バリアフラグを設定する(換言すれば、ストアする)。これにより、idフラグにより示されるスレッド11においては、taskフラグがリセットされるまで、同期処理が実行されないことを保証することができる。この結果、同期後の命令コードが同期前の全ての実行が終了するまで実行されないこと、換言すれば、同期前の全ての実行が終了するまで同期が成立しないことを保証することができる。
以上のように、スレッド11は、TASK構文終了行を実行した後、同期指示行を検出する。これに応じて、スレッド11は、ライブラリ21の同期処理部216を参照、換言すれば、実行する。これにより、スレッド11は、詳しくは後述するように、idフラグにより示されるスレッド11においては、taskフラグのリセットを待って、同期処理を実行する。換言すれば、スレッド実行部は、idフラグが設定された場合に、idフラグにより表されるスレッドにおいて、同期処理を実行することなく、taskフラグがリセットされるまで同期処理の実行を待つ。
図3は、スレッド処理の一例を示す図である。なお、図3において、スレッド#1及び#2は、スレッド#0が処理を終了した後、換言すれば、「END SUBROUTINE」を実行した後に、taskフラグをデクリメントするものとする。また、スレッド#1がtaskフラグをデクリメントする前に、スレッド#2のジャンプと共にtaskフラグがインクリメントされるものとする。
図3に示すプログラム即ち実行可能ファイル22が複数のブロック即ちスレッド11に分割されて、複数のスレッド11、換言すれば、スレッド#0〜#2が並列に実行される。なお、図3において、例えば、「!$OMP TASK」がTASK構文指示行であり、「!$OMP END TASK」がTASK構文終了行であり、TASK構文で囲まれた「call traverse(P%left)」等が第1の命令コードであり、「IF(associated(P%left)」等が第2の命令コードである。
例えば、スレッド#0が、スレッド#1及び#2と同様に、同期(END SINGLE)で待機中であるとする。この同期(END SINGLE)で待機中のスレッド#0が、実行可能ファイル22の先頭のブロックを実行する。換言すれば、スレッド#0は、同期から、プログラムの先頭の「subroutin traverse(P)」の実行にジャンプする。これにより、スレッド#0は、サブルーチンPの先頭行から順に命令コードを実行して、最初のTASK構文指示行に到達する。
TASK構文指示行は、第1の命令コードではないが、第1の命令コードを含むことを示す命令コードである。従って、第1の命令コードではなく、TASK構文指示行の存在に応じて、taskフラグ及びidフラグが設定される。スレッド#0は、ライブラリ21のTASK構文検出部214を実行することにより、taskフラグ及びidフラグを設定する。具体的には、taskフラグは、初期値「0」を「+1」だけインクリメントすることにより、「1」とされる。idフラグは、初期値「−1」に代えて、当該スレッド#0のスレッド番号「0」とされる。
この後、スレッド#0は、最初のTASK構文指示行の次の行の第1の命令コード「call traverse(P%left)」を、例えば同期(END SINGLE)で待機中のスレッド#1にスケジューリングし、TASK構文終了行を検出すると、当該TASK構文終了行の次の行を実行する。
この後、スレッド#0は、次のTASK構文指示行に到達する。これに応じて、スレッド#0は、TASK構文検出部214を実行することにより、taskフラグを「+1」だけインクリメントする。この時点では、idフラグの値が「−1」以外の値であるので、idフラグは更新されない。
この後、スレッド#0は、次のTASK構文指示行の次の第1の命令コード「call traverse(P%right)」を、例えば同期(END SINGLE)で待機中のスレッド#2にスケジューリングし、TASK構文終了行を検出すると、当該TASK構文終了行の次の行を実行する。
この後、スレッド#0は、最後の指示行、換言すれば、処理の終了を指示する「END SUBROUTINE」に到達し、同期処理部216による同期処理を実行して、同期待ち(END SINGLEで待機中)となる。
一方、同期(END SINGLE)で待機中のスレッド#1は、最初のTASK構文で囲まれた未実行の命令コード「call traverse(P%left)」の実行にジャンプする。スレッド#1は、最初のTASK構文で囲まれた命令コードを実行した後、TASK構文終了部215を実行する。これにより、taskフラグは、「−1」だけデクリメントされる。taskフラグのデクリメントは、最初のTASK構文で囲まれた命令コードを実行したスレッド#1により実行される。スレッド#1は、自己のスレッド番号「1」がidフラグに設定されていないので、同期処理部216による同期処理を実行して、同期待ち(END SINGLEで待機中)となる。
また、同期(END SINGLE)で待機中のスレッド#2は、次のTASK構文で囲まれた未実行の命令コード「call traverse(P%right)」の実行にジャンプする。スレッド#2は、次のTASK構文で囲まれた命令コードを実行した後、TASK構文終了部215を実行する。これにより、taskフラグは、「−1」だけデクリメントされる。taskフラグのデクリメントは、次のTASK構文で囲まれた命令コードを実行したスレッド#2により実行される。スレッド#2は、自己のスレッド番号「2」がidフラグに設定されていないので、同期処理部216による同期処理を実行して、同期待ち(END SINGLEで待機中)となる。
なお、前述したように、idフラグは、taskフラグのリセットに応じて、リセットされる(換言すれば、「−1」とされる)。idフラグのリセットは、スレッド#0、換言すれば、idフラグで示されるスレッド11により実行される。
以上により、スレッド#0は、「END SUBROUTINE」の実行後に、同期待ち(END SINGLEで待機中)となる。しかし、この時点で、idフラグの値が「0」であるので、スレッド#0は、実際には、同期処理を実行しない、換言すれば、バリアフラグをストアしない。なお、スレッド#0から他のスレッド11へのバリアフラグのロードは実行される。この時、スレッド#0のバリアフラグはストアされていない、換言すれば、未設定である。具体的には、スレッド#0のバリアフラグは、例えば「0」である。従って、スレッド#0からバリアフラグをロードした他のスレッド11においても、同期は成立しない。
この後、スレッド#1及び#2がtaskフラグをデクリメントすると、taskフラグの値が「0」とされる。これに応じて、同期待ち(END SINGLEで待機中)であるスレッド#0は、idフラグをリセットする。この結果、スレッド#0は、同期を待つ処理を停止し、同期処理を実行する、換言すれば、バリアフラグをストアする。これにより、前述したように、カスケード方式に従ってバリアフラグのストアとロードを繰り返すことにより、同期が成立する。
なお、スレッド#2のジャンプと共にtaskフラグがインクリメントされる前に、スレッド#1がtaskフラグをデクリメントした場合には、idフラグは、直ちにはリセットされない。換言すれば、スレッド#0が、同期処理部216による同期処理を実行して、同期待ち(END SINGLEで待機中)となった際に、idフラグをリセットする。
更に、スレッド#0が「END SUBROUTINE」を実行する前に、スレッド#1及び#2がtaskフラグをデクリメントした場合にも、idフラグは、直ちにはリセットされない。換言すれば、スレッド#0が、同期処理部216による同期処理を実行して、同期待ち(END SINGLEで待機中)となった際に、idフラグをリセットする。
図4及び図5は、スレッド処理の一例を示す図である。図4及び図5の例は、taskフラグが、デクリメントされることなく、繰り返しインクリメントされた結果、「5」となった場合の例、換言すれば、並列化の程度が高い場合の例である。
図4及び図5において、スレッド11の数Nは5であり、5個のスレッド#0〜#4が用いられるものとする。前述したように、カスケード方式の同期においては、各スレッド11がlog2N回(小数点以下繰上げ)のバリアフラグのロード及び/又はストアを繰り返す。従って、同期には、3個のバリアフラグを1組として5個使用する。5個のスレッド#0〜#4に対応する5個のバリアフラグ領域213を、各々、同期領域#0〜#4とする。5個のバリアフラグ領域213は、各々、3個のバリアフラグを設定することができるようにされる。
図4に示すように、例えば、スレッド#0におけるTASK構文指示行の検出により、taskフラグ領域におけるtaskフラグが「1」とされ、idフラグ領域212におけるidフラグが当該スレッド#0のスレッド番号「0」とされる。一方、TASK構文指示行の検出に応じて、同期(END SINGLE)で待機中のスレッド#1が、当該TASK構文で囲まれた未実行の命令コードの実行にジャンプする。
この後、スレッド#1において、更に、TASK構文指示行が検出されると、taskフラグが「2」とされる一方、idフラグは更新されない。一方、TASK構文指示行の検出に応じて、同期(END SINGLE)で待機中のスレッド#2が、当該TASK構文で囲まれた未実行の命令コードの実行にジャンプする。
更に、スレッド#2〜#4において、TASK構文指示行が検出される。このようなTASK構文の検出の繰り返しの結果、taskフラグは、繰り返しインクリメントされて「5」となる。一方、idフラグは、更新されることなく、「0」のままである。
これにより、idフラグが設定された場合に、「idフラグ=スレッド番号」が成立するスレッド#0は、「taskフラグ=0」が成立するまで、同期処理を実行しない。具体的には、図4に示すように、スレッド#0に対応する同期領域#0にバリアフラグを設定しない。これに対して、「idフラグ=スレッド番号」が成立しないスレッド#1〜#4は、「taskフラグ=0」の成立とは拘わりなく、同期処理を実行する。具体的には、図4に網掛けにより示すように、スレッド#1〜#4に対応する同期領域#1〜#4にバリアフラグを設定する。
以上により、「taskフラグ=0」となるまで、換言すれば、taskフラグがリセットされるまでidフラグを更新できない。これにより、同期からのジャンプで実行される全ての未実行の命令コードが終了するまで、少なくとも1つのスレッド#0は、同期処理を開始しない。これにより、未実行の命令コードが終了するまで、同期が成立しないことが保証される。換言すれば、未実行の命令コードとは、同期の成立までに実行される必要のある命令コードである。従って、同期の成立に必要な全ての命令コードが実行された後に、同期が成立する。
なお、スレッド#1〜#4は、ジャンプしたプログラムの実行から復帰する都度に、taskフラグを「−1」だけデクリメントする。これにより、スレッド#1〜#4が全てジャンプしたプログラムの実行から復帰した場合には、「taskフラグ=0」となるので、その後はidフラグを更新することができる。
また、第1の命令コードが存在しない場合には、いずれのスレッド#0〜#4もidフラグを更新しない。一方、第1の命令コードが存在する場合には、idフラグの更新は禁止される。このため、スレッド#0〜#4がidフラグをメインメモリ2から共有キャッシュ14を介して各々のキャッシュ13にロードする時に、共有キャッシュ14におけるキャッシュ競合、換言すれば、(true sharing)が発生することを防止することができる。
図4の状態に続いて、図5に示すように、スレッド#0〜#4における並列処理が、step#2〜step#6のように実行される。なお、図5において、図4の状態をstep#1として示す。
step#1において、スレッド#Nは、同期領域Nのi番目のバリアフラグを設定する。ここで、変数iは「1」から開始される。例えば、スレッド#1は、同期領域#1の1番目のバリアフラグを設定する(換言すれば、ストアする)。図4においては、同期領域#1の1番目のバリアフラグの設定が、同期領域#1の1番目の領域に網掛けにより示される。他のスレッド#2〜#4においても、同期領域#2〜#4の1番目のバリアフラグが設定される。
一方、スレッド#0は、全ての未実行の命令コードが終了していないので、同期しない。従って、スレッド#0は、同期領域#0の1番目のバリアフラグを設定しない。図5において、同期領域#0の1番目のバリアフラグを設定しないことが、当該領域に網掛けをしないことにより示される。
step#2において、スレッド#Nは、同期領域「(N+Σ2j)%スレッド数」のi番目のバリアフラグをロードする。ここで、変数jは「0」から開始される。この場合、同期領域「(N+1)%5」の1番目のバリアフラグがロードされる。例えば、スレッド#1についての同期領域#1の1番目のバリアフラグは、スレッド#2についての同期領域#2の1番目の領域にロードされる。
スレッド#Nは、step#2においてロードしたバリアフラグが設定済みの場合、変数iをインクリメントしてstep#3に進む。
step#3において、スレッド#Nは、同期領域Nのi番目のバリアフラグを設定する。例えば、スレッド#2は、同期領域#2の2番目のバリアフラグを設定する。他のスレッド#3〜#4においても、同期領域#3〜#4の2番目のバリアフラグが設定される。
一方、スレッド#0は、全ての未実行の命令コードが終了していないので、同期せず、step#3を実行しない。スレッド#1は、スレッド#0からロードしたバリアフラグが未設定なので、step#3を実行しない。図5において、スレッド#0からロードしたバリアフラグが未設定であることが、ロードを示す点線を省略することにより示される。
step#4において、スレッド#Nは、同期領域「(N+Σ2j)%スレッド数」のi番目のバリアフラグをロードする。この場合、同期領域「(N+2)%5」の2番目のバリアフラグがロードされる。例えば、スレッド#2についての同期領域#2の2番目のバリアフラグは、スレッド#4についての同期領域#4の2番目の領域にロードされる。
スレッド#Nは、step#4においてロードしたバリアフラグが設定済みの場合、変数iをインクリメントしてstep#5に進む。
step#5において、スレッド#Nは、同期領域Nの3番目のバリアフラグを設定する。例えば、スレッド#4は、同期領域#4の3番目のバリアフラグを設定する。
一方、スレッド#0は、全ての未実行の命令コードが終了していないので、同期せず、step#5を実行しない。スレッド#1は、step#3を実行しなかったので、同期せず、step#5を実行しない。スレッド#2及び#3は、各々、スレッド#0及び#1からロードしたバリアフラグが未設定なので、step#5を実行しない。図5において、スレッド#0及び#1からロードしたバリアフラグが未設定であることが、ロードを示す点線を省略することにより示される。
step#6において、スレッド#Nは、同期領域「(N+Σ2j)%スレッド数」のi番目のバリアフラグをロードする。この場合、同期領域「(N+4)%5」の3番目のバリアフラグがロードされる。例えば、スレッド#4についての同期領域#4の3番目のバリアフラグは、スレッド#3についての同期領域#3の3番目の領域にロードされる。
以上のように、「idフラグ=スレッド番号」の条件に適合するスレッド#0は、同期処理の実行を開始せず、「taskフラグ=0」を待つ。この間、他のスレッド#1〜#4は、同期の成立までに実行する必要のある処理を、各々における実行の程度は異なるが、かなり実行している。従って、スレッド#0が全ての未実行コードの実行終了後に同期を開始した場合、同期の成立までの処理時間を短縮することができる。
図6は、スレッド処理の一例を示す図である。図6は、図5におけるstep#1及びstep#2を示す。
スレッド#0は、前述したように、TASK構文の検出に応じて、未実行コードにジャンプする。このため、スレッド#0は、taskフラグを、メインメモリ2から共有キャッシュ14を介して、対応するキャッシュ13にロードする。また、スレッド#1〜#4は、同期処理を実行する。このため、スレッド#1〜#4は、各々、対応する同期領域#1〜#4のバリアフラグを、メインメモリ2から共有キャッシュ14を介して、対応するキャッシュ13にロードする。
ここで、taskフラグのロードにおいて、「taskフラグ=0」の場合には、第1の命令コードは存在しない。この場合、taskフラグのロードを実行したために、メインメモリ2及び共有キャッシュ14へのアクセスの機会が1回無駄になる。一方、高速で同期するためには、できるだけ頻繁に同期領域をロードすることが望ましい。
そこで、「taskフラグ=0」の場合には、例えば、1000000回の同期領域のロードに対して、1回のtaskフラグのロードを行う。これにより、実行可能ファイル22に第1の命令コードが存在しない場合でも、高速で同期することができる。
図7は、スレッド処理フローを示し、特に、ソースプログラム41、換言すれば、実行可能ファイル22に第1の命令コードが存在する場合の処理について示す。
ソースプログラム41が、スレッド11において実行されると(ステップS31)、第1の命令コード、例えばTASK構文指示行が検出される。これに応じて、スレッド11は、第1の命令コード「call traverse(P%left)」を他のスレッド11にスケジューリングし、ライブラリ21のTASK構文検出部214を実行する。即ち、TASK構文検出部214は、idフラグが「−1」であるか否かを調べる(ステップS21)。idフラグが「−1」である場合(ステップS21 Yes)、TASK構文検出部214は、idフラグに当該TASK構文指示行を検出したスレッド11のスレッド番号を設定する(ステップS22)。idフラグが「−1」でない場合(ステップS21 No)、ステップS22は省略される。この後、TASK構文検出部214は、taskフラグを「+1」だけインクリメントする(ステップS23)。
この後、ソースプログラム41を実行していたスレッド11は、TASK構文終了行の次の命令コードを実行する(ステップS32)。この後、スレッド11は、全ての命令コードの実行を終了すると、BARRIER構文「!$OMP END SINGLE」による同期待ちとなる。換言すれば、スレッド11は、全ての未実行の命令コードの実行が終了するまで同期処理を待ち、全ての未実行の命令コードの実行が終了した後に、同期処理を実行する。
図8は、スレッド処理フローを示し、特に、ソースプログラム41、換言すれば、実行可能ファイル22に第1の命令コードが存在しない場合の処理について示す。
ソースプログラム41が、スレッド11において実行されるが(ステップS33)、第1の命令コード、例えばTASK構文指示行が検出されない。従って、ライブラリ21のTASK構文検出部214は実行されない。従って、この場合、taskフラグ=0であり、idフラグは「−1」である。
図9は、スレッド処理フローを示し、特に、同期処理及びTASK構文の終了処理について示す。
同期待ち(END SINGLEで待機中)のスレッド11は、idフラグが自己のスレッド番号であるか否かを調べる(ステップS41)。idフラグが自己のスレッド番号である場合(ステップS41 Yes)、スレッド11は、taskフラグ=0であるか否かを調べる(ステップS42)。taskフラグ=0でない場合(ステップS42 No)、スレッド11は、タスクの実行にジャンプし(ステップS43)、タスクの実行の終了後にtaskフラグを「−1」だけデクリメントし(ステップS44)、ステップS42を繰り返す。
ステップS42において、taskフラグ=0である場合(ステップS42 Yes)、スレッド11は、idフラグを「−1」にリセットする(ステップS45)。
ステップS41において、idフラグが自己のスレッド番号でない場合(ステップS41 No)、ステップS42及びS45は省略される。
この後、スレッド11は、同期領域のi番目のバリアフラグを設定する変数iを「1」に設定し(ステップS46)、taskフラグ=0であるか否かを調べる(ステップS47)。taskフラグ=0でない場合(ステップS47 No)、スレッド11は、タスクの実行にジャンプし(ステップS48)、タスクの実行の終了後にtaskフラグを「−1」だけデクリメントし(ステップS49)、ステップS47を繰り返す。
ステップS47において、taskフラグ=0である場合(ステップS47 Yes)、スレッド11は、変数iを「+1」だけインクリメントし(ステップS410)、同期領域にバリアフラグが設定されたか否かを調べる(ステップS411)。
同期領域にバリアフラグが設定されていない場合(ステップS411 No)、スレッド11は、変数iがスレッド11の数Nと等しいか否かを調べる(ステップS412)。変数iがスレッド11の数Nと等しくない場合(ステップS412 No)、スレッド11は、ステップS410を繰り返す。変数iがスレッド11の数Nと等しい場合(ステップS412 Yes)、スレッド11は、ステップS46を繰り返す。
ステップS411において、同期領域にバリアフラグが設定された場合(ステップS411 Yes)、同期が成立しているので、スレッド11は、TASK構文終了行の次の命令コードを実行する。