(58)【調査した分野】(Int.Cl.,DB名)
前記第2の演算式が複数の演算子を含んでおり、前記複数の演算子の実行順序を入れ替えることで前記第2の実行時間が短くなる場合、前記実行順序を入れ替えた第2の演算式に基づいて前記第2のコードを生成する、
請求項1記載のコンパイルプログラム。
【発明を実施するための形態】
【0014】
以下、本実施の形態を図面を参照して説明する。
[第1の実施の形態]
図1は、第1の実施の形態のコンパイル装置の例を示す図である。
【0015】
第1の実施の形態のコンパイル装置10は、最適化として、コード13から並列処理命令を含むコード14に変換することがある。並列処理命令は、異なるデータに対する演算をプロセッサに並列に実行させる単一の命令であり、例えば、SIMD命令である。コード13,14は、プロセッサに実行させたい命令の内容を記述していると言うこともできる。コード13は、高級言語で記述されたソースコードでもよいし、ソースコードから変換された中間コードでもよい。コード14は、最適化された中間コードでもよいし、アセンブリコードや機械可読なオブジェクトコードでもよい。また、コンパイル装置10は、ユーザが操作する端末装置でもよいし、端末装置からアクセスされるサーバ装置でもよい。コンパイル装置10は、コンピュータまたは情報処理装置を用いて実装してもよい。
【0016】
コンパイル装置10は、記憶部11および変換部12を有する。記憶部11は、コード13を記憶する。記憶部11は、RAM(Random Access Memory)などの揮発性の記憶装置でもよいし、HDD(Hard Disk Drive)などの不揮発性の記憶装置でもよい。変換部12は、記憶部11に記憶されたコード13をコード14に変換することがある。変換部12は、CPUやDSP(Digital Signal Processor)などのプロセッサでもよいし、ASIC(Application Specific Integrated Circuit)やFPGA(Field Programmable Gate Array)などの特定用途の電子回路を含んでもよい。プロセッサは、例えば、記憶部11または他の記憶装置に記憶されたプログラムを実行する。なお、複数のプロセッサの集合(マルチプロセッサ)を「プロセッサ」と呼んでもよい。
【0017】
変換部12は、記憶部11に記憶されたコード13から、ある条件を満たす演算式15を含むループを検出する。演算式15は、K回転前(Kは1以上の整数)の演算の結果を参照する変数(第1の変数)を含むものである。例えば、演算式A(J)=A(J−K)+B(J)に含まれる変数A(J−K)は、K回転前の演算で値が定義されるものであり「第1の変数」に該当する。K=1と置くと、上記の演算式はA(J)=A(J−1)+B(J)となり、1回転前の演算の結果を参照していることになる。
【0018】
このような演算式15がループに含まれている場合、ループのJ+K回転目(Jは1以上の整数)の演算は、J回転目の演算に依存するため、このままではJ回転目の演算と並列に実行しないことが好ましい。そこで、変換部12は、演算式15が「第1の変数」を含まないように変形することで、J回転目の演算とJ+K回転目の演算とを並列に実行できるようにする。具体的には、変換部12は、K+1回転以上前の演算の結果を参照する変数(第2の変数)を用いて、演算式15を演算式16に展開する。
【0019】
例えば、変換部12は、演算式A(J)=A(J−K)+B(J)に含まれる変数A(J−K)をその演算式自身を用いて展開することで、演算式A(J)=A(J−2K)+B(J−K)+B(J)を得る。展開後の演算式に含まれる変数A(J−2K)は、2K回転前の演算の結果を参照するものであり「第2の変数」に該当する。2K回転前の演算の結果を参照することで、K回転前の演算との依存関係が切断される。K=1と置くと、2回転前の演算の結果を利用して、1回転前の演算との依存関係を切断していることになる。このような変数の展開を連続的に行うことで、依存関係を過去に遡り、並列実行できる演算の範囲を広げることもできる。例えば、変数の展開を3回行うと、1〜3回転前の演算との依存関係が切断され、4回転分の演算が並列実行可能になる。
【0020】
ただし、展開された演算式16が示す演算量やメモリアクセス量は、展開前の演算式15が示す演算量やメモリアクセス量よりも大きくなることが多い。このため、演算式16に基づいて、少なくともJ回転目の演算とJ+K回転目の演算とを並列化しても、展開および並列化を行わない場合よりコード14が効率化しているとは限らない。演算式16を利用することでコード14が効率化するか否かは、並列度や並列処理命令の実行に要するサイクル数など、ループを実行するプロセッサのアーキテクチャに依存する。また、コード14が効率化するか否かは、展開前の演算式15の内容にも依存し得る。
【0021】
そこで、変換部12は、演算式15に基づいてループを実行する場合の実行時間T1と、演算式16に基づいて少なくともJ回転目の演算とJ+K回転目の演算とを並列化する場合の実行時間T2とを比較する。変換部12は、プロセッサのアーキテクチャを示すプロセッサ情報を参照して、命令スケジューリングを仮実行し、実行時間T1,T2を予測してもよい。実行時間T1,T2の単位には、サイクル数やクロック数を用いてもよい。
【0022】
例えば、変換部12は、演算A(J)=A(J−K)+B(J)と演算A(J+K)=A(J)+B(J+K)を、SIMD命令などの並列処理命令を利用せずに実行する場合について実行時間T1を算出する。また、変換部12は、演算A(J)=A(J−2K)+B(J−K)+B(J)と演算A(J+K)=A(J−K)+B(J)+B(J+K)を、並列処理命令を利用して実行する場合について実行時間T2を算出する。
【0023】
そして、変換部12は、比較結果に応じて、コード13からSIMD命令などの並列処理命令を含むコード14に変換するか決定する。例えば、実行時間T2が実行時間T1より小さい場合(実行時間が短縮する場合)、変換部12は、コード13をコード14に変換すると決定する。また、例えば、実行時間T2が実行時間T1以上の場合(実行時間が短縮しない場合)、変換部12は、コード13をコード14に変換しないと決定する。
【0024】
第1の実施の形態のコンパイル装置10によれば、K回転前の演算の結果を参照する変数を含む演算式15が、その変数を含まない演算式16に展開される。そして、演算式15に基づいてループを実行する場合の実行時間T1と、演算式16に基づいて並列化してループを実行する場合の実行時間T2とが比較され、比較の結果に応じて、コード13から並列処理命令を含むコード14に変換するか否かが決定される。これにより、演算式の展開による演算量やメモリアクセス量の増大と、並列処理命令を利用することによる命令数の減少が総合的に考慮され、ループの実行時間を短縮することができる。
【0025】
[第2の実施の形態]
第2の実施の形態のコンパイル装置100は、高級言語で記述されたソースコードをコンパイルし、機械可読なオブジェクトコードを生成する。コンパイル装置100は、ユーザが操作する端末装置でもよいし、端末装置からアクセスされるサーバ装置でもよい。コンパイル装置100は、例えば、コンピュータを用いて実装される。その場合、コンパイル装置100は、ソフトウェアとしてのコンパイラを実行する。
【0026】
図2は、第2の実施の形態のコンパイル装置のハードウェア例を示す図である。
コンパイル装置100は、CPU101、RAM102、HDD103、画像信号処理部104、入力信号処理部105、媒体リーダ106および通信インタフェース107を有する。これらのユニットはバスに接続されている。CPU101は第1の実施の形態の変換部12の一例であり、RAM102は第1の実施の形態の記憶部11の一例である。
【0027】
CPU101は、プログラムの命令を実行する演算回路を含むプロセッサである。CPU101は、HDD103に記憶されているプログラムやデータの少なくとも一部をRAM102にロードし、プログラムを実行する。なお、CPU101は複数のプロセッサコアを備えてもよく、コンパイル装置100は複数のプロセッサを備えてもよく、以下で説明する処理を複数のプロセッサまたはプロセッサコアを用いて並列に実行してもよい。また、複数のプロセッサの集合(マルチプロセッサ)を「プロセッサ」と呼んでもよい。
【0028】
RAM102は、CPU101が実行するプログラムやCPU101が演算に用いるデータを一時的に記憶する揮発性の半導体メモリである。なお、コンパイル装置100は、RAM以外の種類のメモリを備えてもよく、複数個のメモリを備えてもよい。
【0029】
HDD103は、OS(Operating System)やミドルウェアやアプリケーションソフトウェアなどのソフトウェアのプログラム、および、データを記憶する不揮発性の記憶装置である。HDD103に記憶されるプログラムには、コンパイルプログラムが含まれる。なお、コンパイル装置100は、フラッシュメモリやSSD(Solid State Drive)などの他の種類の記憶装置を備えてもよく、複数の不揮発性の記憶装置を備えてもよい。
【0030】
画像信号処理部104は、CPU101からの命令に従って、コンパイル装置100に接続されたディスプレイ111に画像を出力する。ディスプレイ111としては、CRT(Cathode Ray Tube)ディスプレイ、液晶ディスプレイ(LCD:Liquid Crystal Display)、プラズマディスプレイ(PDP:Plasma Display Panel)、有機EL(OEL:Organic Electro-Luminescence)ディスプレイなどを用いることができる。
【0031】
入力信号処理部105は、コンパイル装置100に接続された入力デバイス112から入力信号を取得し、CPU101に出力する。入力デバイス112としては、マウスやタッチパネルやタッチパッドやトラックボールなどのポインティングデバイス、キーボード、リモートコントローラ、ボタンスイッチなどを用いることができる。また、コンパイル装置100に、複数の種類の入力デバイスが接続されていてもよい。
【0032】
媒体リーダ106は、記録媒体113に記録されたプログラムやデータを読み取る読み取り装置である。記録媒体113として、例えば、フレキシブルディスク(FD:Flexible Disk)やHDDなどの磁気ディスク、CD(Compact Disc)やDVD(Digital Versatile Disc)などの光ディスク、光磁気ディスク(MO:Magneto-Optical disk)、半導体メモリなどを使用できる。媒体リーダ106は、例えば、記録媒体113から読み取ったプログラムやデータをRAM102またはHDD103に格納する。
【0033】
通信インタフェース107は、ネットワーク114に接続され、ネットワーク114を介して他のコンピュータと通信を行うインタフェースである。通信インタフェース107は、スイッチなどの通信装置とケーブルで接続される有線通信インタフェースでもよいし、基地局と無線リンクで接続される無線通信インタフェースでもよい。
【0034】
なお、コンパイル装置100は、媒体リーダ106を備えていなくてもよく、ユーザが操作する端末装置から制御可能である場合には画像信号処理部104や入力信号処理部105を備えていなくてもよい。また、ディスプレイ111や入力デバイス112が、コンパイル装置100の筐体と一体に形成されていてもよい。
【0035】
図3は、第2の実施の形態のコンパイル装置の機能例を示す図である。
コンパイル装置100は、ソースコード記憶部121、中間コード記憶部122、オブジェクトコード記憶部123、ソースコード解析部131、最適化部132、アセンブリコード生成部136およびファイル生成部139を有する。ソースコード記憶部121、中間コード記憶部122およびオブジェクトコード記憶部123は、例えば、RAM102またはHDD103に確保した記憶領域として実現される。ソースコード解析部131、最適化部132、アセンブリコード生成部136およびファイル生成部139は、例えば、CPU101が実行するプログラムのモジュールとして実現される。
【0036】
ソースコード記憶部121は、ユーザにより作成されたコードであって、高級言語で記述されたソースコードを記憶する。高級言語の例としては、FORTRANやC言語などの手続き型言語が挙げられる。ソースコードは、コンパイル装置100で作成されてもよいし、他の装置で作成されてコンパイル装置100に送信されてもよい。
【0037】
中間コード記憶部122は、ソースコードから変換されたコードであって、オブジェクトコードに変換される前の中間コードを記憶する。中間コードは、コンパイルの途中で内部的に用いられるコードであり、コンパイル装置100がその表現形式を任意に決定してもよい。後述するように、最適化は中間コードに対して行われる。
【0038】
オブジェクトコード記憶部123は、ソースコードから中間コードを経て生成されたコードであって、機械語で記述されたオブジェクトコードを記憶する。機械語では、命令やオペランドがビット列(数値)で表現される。オブジェクトコードは、ある種類のCPUをターゲットとして生成される。オブジェクトコードを実行するCPUは、コンパイル装置100が有するCPU101でもよいし、他の装置が有するCPUでもよい。
【0039】
コンパイル装置100は、複数のCPUアーキテクチャのうちユーザが指定したものをターゲットとして、オブジェクトコードを生成してもよい。その場合、例えば、コンパイル装置100は、複数のCPUアーキテクチャそれぞれについて、使用可能な命令、各命令の実行に要するサイクル数、使用可能なレジスタなどを示すCPU情報を保持する。そして、コンパイル装置100は、ユーザが指定したCPUアーキテクチャのCPU情報を参照して、そのCPUアーキテクチャに対応したオブジェクトコードを生成する。
【0040】
ソースコード解析部131は、ソースファイル名などを含むコンパイルコマンドを受け付ける。すると、ソースコード解析部131は、ソースコード記憶部121から、指定されたソースファイルに含まれるソースコードを読み出し、字句解析、構文解析、意味解析などのフロントエンド処理を実行する。そして、ソースコード解析部131は、ソースコードに対応する中間コードを生成し中間コード記憶部122に格納する。また、コンパイルコマンドにコンパイルオプションが含まれている場合、ソースコード解析部131は、コンパイルオプションを最適化部132に通知することがある。コンパイルオプションには、後述するように、演算子の実行順序の入れ替えを許容するオプションが含まれ得る。
【0041】
最適化部132は、中間コード記憶部122に記憶された中間コードを読み出し、処理結果を変えずに処理を効率化する余地のある中間コードを検索する。最適化部132は、検索された中間コードを中間コード記憶部122上で書き換えることで、コンパイル装置100で生成されるオブジェクトコードの最適化を実現する。最適化部132は、並列化部133、SIMD化部134および汎用最適化部135を有する。
【0042】
並列化部133は、ターゲットのCPUが、複数のハードウェアスレッドや複数のCPUコアなど、並列に命令を実行可能な複数のハードウェア単位を備えているか確認する。ターゲットのCPUが複数のハードウェア単位を備えている場合、並列化部133は、中間コードに含まれる命令の間の依存関係を分析し、並列に実行可能な命令の組み合わせを判定する。並列化部133による最適化は、SIMD化部134による最適化の後に行ってもよい。並列実行される命令は、オペランド毎に1つのデータ単位を扱うスカラ命令でもよいし、オペランド毎に2以上のデータ単位を扱うSIMD命令でもよい。
【0043】
SIMD化部134は、ターゲットのCPUがSIMD命令を実行可能であるか確認する。ターゲットのCPUがSIMD命令を実行可能である場合、SIMD化部134は、SIMD命令に変換するスカラ命令の組み合わせを決定し、中間コード記憶部122に格納された中間コードを書き換える。1つのSIMD命令に変換できるスカラ命令の上限数(SIMD幅)は、ターゲットのCPUのアーキテクチャによって異なり得る。SIMD化部134は、SIMD幅以下の数のスカラ命令を選択してSIMD命令に変換する。
【0044】
このとき、SIMD化部134は、実行効率の高いSIMD命令を生成できるよう、ソースコードが示す演算手順を処理結果が変わらない範囲で変更することがある。後述するように、第2の実施の形態では主に、SIMD化部134がループ内の演算をSIMD化することを考える。例えば、ループ内のi回転目(i回目のイテレーション)の演算とi+1回転目(i+1回目のイテレーション)の演算との間に依存関係がない場合、この2つの演算をSIMD命令を用いて実現することで、ループの総回転数を削減できる。
【0045】
汎用最適化部135は、中間コードに対して、ターゲットのCPUに依存しない汎用的な最適化を行う。汎用的な最適化には、ある演算結果を後で参照することがわかっている場合、その演算結果をレジスタから追い出さずに保持しておくことで、メモリアクセスを削減することが含まれ得る。また、汎用的な最適化には、後の演算に影響を与えない不要な演算や変数(例えば、参照されない値を算出する演算やその値を格納する変数)を削除すること、分岐命令が少なくなるように制御構造を変更することなどが含まれ得る。
【0046】
アセンブリコード生成部136は、最適化部132による最適化が行われた後、中間コード記憶部122に記憶された中間コードを読み出し、最適化された中間コードをアセンブリ言語で記述されたアセンブリコードに変換する。アセンブリコード生成部136は、スケジューリング部137およびレジスタ割付部138を有する。
【0047】
スケジューリング部137は、命令の実行効率が向上するように、処理結果が変わらない範囲で命令の順序を入れ替える。例えば、スケジューリング部137は、パイプラインハザードが少なくなるように、パイプラインに投入する命令の順序を決定する。また、ターゲットのCPUが複数のハードウェア単位を備えている場合、スケジューリング部137は、予め命令をそれら複数のハードウェア単位に振り分けておくことがある。
【0048】
レジスタ割付部138は、中間コードに含まれる変数に対して、ターゲットのCPUがもつレジスタを割り当てる。SIMD命令のオペランドとしての変数に対しては、レジスタ割付部138は、2以上のデータ単位を格納できるSIMDレジスタを割り当てる。レジスタ割付部138は、使用するレジスタが最小になるように割当を行ってもよい。
【0049】
ファイル生成部139は、オブジェクトコード記憶部123にオブジェクトファイルを生成する。ファイル生成部139は、アセンブリコード生成部136が生成したアセンブリコードを機械可読なオブジェクトコードに変換し、生成したオブジェクトファイルに対して書き込む。これにより、ソースコードのコンパイルが完了する。
【0050】
図4は、SIMD命令とSIMDレジスタの例を示す図である。
ここでは、コンパイル装置100が生成したオブジェクトコードを、CPU20が実行するものとする。CPU20は、コンパイル装置100が備えるCPU101と同一でもよいし異なってもよい。CPU20は、SIMDレジスタ21〜23(ts1,ts2,ts3)を有する。CPU20が実行可能なSIMD命令のSIMD幅が4であるとすると、SIMDレジスタ21〜23それぞれには4つのデータ単位を格納できる。
【0051】
例えば、ソースコードまたは中間コードに、長さ4以上の配列変数a,bを用いて、a(1)+b(1),a(2)+b(2),a(3)+b(3),a(4)+b(4)という4つの演算が定義されているとする。SIMD化部134がこの4つの演算をSIMD化した場合、例えば、SIMDレジスタ21に、変数a(1),a(2),a(3),a(4)の値がこの順に並んでロードされる。また、SIMDレジスタ22に、変数b(1),b(2),b(3),b(4)の値がこの順に並んでロードされる。
【0052】
そして、ts1+ts2=ts3というSIMD命令が投入されると、CPU20は、SIMDレジスタ21,22の対応する位置にあるデータ単位を組み合わせて、以下の4つの演算を並列に実行する。SIMDレジスタ21のa(1)の値とSIMDレジスタ22のb(1)の値を加算して、SIMDレジスタ23の1番目の位置に格納する。SIMDレジスタ21のa(2)の値とSIMDレジスタ22のb(2)の値を加算して、SIMDレジスタ23の2番目の位置に格納する。SIMDレジスタ21のa(3)の値とSIMDレジスタ22のb(3)の値を加算して、SIMDレジスタ23の3番目の位置に格納する。SIMDレジスタ21のa(4)の値とSIMDレジスタ22のb(4)の値を加算して、SIMDレジスタ23の4番目の位置に格納する。
【0053】
次に、SIMD化部134がループ内の演算をSIMD化する例を説明する。以下では、ターゲットのCPUのSIMD幅が4であり、SIMD命令を用いてループ内の4回転分の演算(i回転目〜i+3回転目の演算)を並列化する場合を考える。これにより、ループの回転数が約4分の1に削減され、実行命令数が削減される。なお、以下に挙げるコード例は、理解が容易になるように高級言語FORTRANで記述している。SIMD化部134は、実際には、中間コードに対してSIMD最適化を実行する。
【0054】
図5は、SIMD最適化による第1のコード変換例を示す図である。
コード201には、倍精度浮動小数点型の長さ10000の配列変数a,bと、ループ変数iと、iを1から10000まで1ずつ増加させるループと、ループ内の演算式a(i)=a(i)+b(i)とが定義されている。配列変数a,bの各要素の初期値は、サブルーチンfooを呼び出すコードよって設定されている。このとき、SIMD化部134は、例えば、コード201を以下のようなコード202に変換する。
【0055】
コード202には、iを1から10000まで4ずつ増加させるループと、ループ内の演算式a(i:i+3)=a(i:i+3)+b(i:i+3)とが定義されている。a(i:i+3)は、配列変数aの中のi〜i+3の範囲を示す長さ4の部分配列である。同様に、b(i:i+3)は、配列変数bの中のi〜i+3の範囲を示す長さ4の部分配列である。コード202で行われるロード、加算、ストアは、SIMD命令(SIMD−LOAD,SIMD−ADD,SIMD−STOREなど)を用いて実現される。これにより、ループの回転数が10000回転から2500回転に減少する。2500回転それぞれでは、元のループの4回転分の演算が並列に実行される。
【0056】
図6は、SIMD化前後の演算の第1の対応例を示す図である。
コード201のループを実行すると、まずi=1に対応する演算301、すなわち、a(1)=a(1)+b(1)が実行される。次に、i=2に対応する演算302、すなわち、a(2)=a(2)+b(2)が実行される。以下、i=3に対応する演算303、i=4に対応する演算304、i=5に対応する演算305、i=6に対応する演算306、i=7に対応する演算307、i=8に対応する演算308が順に実行される。
【0057】
これに対し、コード202のループを実行すると、まずi=1に対応する演算30、すなわち、a(1:4)=a(1:4)+b(1:4)が実行される。演算30は、コード201の演算301〜304に対応する。a(i)を定義する演算以外にa(i)を参照する演算がないため、演算301〜304の間には依存関係がなく、SIMD命令を用いて演算301〜304を並列化することができる。次に、i=5に対応する演算31、すなわち、a(5:8)=a(5:8)+b(5:8)が実行される。演算31は、コード201の演算305〜308に対応する。演算305〜308の間には依存関係がなく、SIMD命令を用いて演算305〜308を並列化することができる。
【0058】
図7は、SIMD最適化による第2のコード変換例を示す図である。
コード211には、ループ変数iを1から10000まで1ずつ増加させるループと、ループ内の演算式a(i)=a(i+1)+b(i)とが定義されている。このとき、SIMD化部134は、例えば、コード211をコード212に変換する。コード212には、iを1から10000まで4ずつ増加させるループと、ループ内の演算式a(i:i+3)=a(i+1:i+4)+b(i:i+3)とが定義されている。
【0059】
図8は、SIMD化前後の演算の第2の対応例を示す図である。
コード211のループを実行すると、まずi=1に対応する演算311、すなわち、a(1)=a(2)+b(1)が実行される。次に、i=2に対応する演算312、すなわち、a(2)=a(3)+b(2)が実行される。以下、i=3に対応する演算313、i=4に対応する演算314、i=5に対応する演算315、i=6に対応する演算316、i=7に対応する演算317、i=8に対応する演算318が実行される。これに対し、コード212のループを実行すると、まずi=1に対応する演算32、すなわち、a(1:4)=a(2:5)+b(1:4)が実行される。次に、i=5に対応する演算33、すなわち、a(5:8)=a(6:9)+b(5:8)が実行される。
【0060】
ここで、a(i)を定義する演算ではa(i+1)を参照しているため、演算311〜314の間には依存関係がある。例えば、a(2)は、演算311で参照され演算312で更新される。a(3)は、演算312で参照され演算313で更新される。しかし、演算311〜314の依存関係は、a(i)を先に参照して後に更新するという参照・定義の順序関係になっており、演算311〜314を並列化してもその順序関係は崩れない。例えば、a(2),a(3)の値がメモリからSIMDレジスタにロードされ(参照)、加算が行われた後、a(2),a(3)の値がメモリにストアされる(定義)。
【0061】
よって、演算311〜314は、依存関係があるものの、演算32のように並列化することが可能である。同様に、演算315〜318は、依存関係があるものの、参照・定義の順序関係が崩れないため演算33のように並列化することが可能である。
【0062】
図9は、SIMD最適化による第3のコード変換例を示す図である。
コード221には、倍精度浮動小数点型の長さ10004の配列変数a,bと、ループ変数iを5から10004まで1ずつ増加させるループと、ループ内の演算式a(i)=a(i−4)+b(i)とが定義されている。このとき、SIMD化部134は、例えば、コード221をコード222に変換する。コード222には、iを5から10004まで4ずつ増加させるループと、ループ内の演算式a(i:i+3)=a(i−4:i−1)+b(i:i+3)とが定義されている。
【0063】
図10は、SIMD化前後の演算の第3の対応例を示す図である。
コード221のループを実行すると、まずi=5に対応する演算321、すなわち、a(5)=a(1)+b(5)が実行される。次に、i=6に対応する演算322、すなわち、a(6)=a(2)+b(6)が実行される。以下、i=7に対応する演算323、i=8に対応する演算324、i=9に対応する演算325、i=10に対応する演算326、i=11に対応する演算327、i=12に対応する演算328が実行される。これに対し、コード222のループを実行すると、まずi=5に対応する演算34、すなわち、a(5:8)=a(1:4)+b(5:8)が実行される。次に、i=9に対応する演算35、すなわち、a(9:12)=a(5:8)+b(9:12)が実行される。
【0064】
ここで、演算321〜328の中には、a(i)を定義した後にa(i)を参照するという定義・参照の依存関係がある。例えば、a(5)は、演算321で定義され演算325で参照される。a(6)は、演算322で定義され演算326で参照される。a(i)を定義する演算とa(i)を参照する演算とを並列化してしまうと、定義・参照の順序関係が崩れるため、元の演算式とは計算方法が異なる不正な変形となる。
【0065】
しかし、演算321〜328では、a(i)を定義する演算とa(i)を参照する演算とは、時系列上でSIMD幅以上離れている。例えば、a(5)を定義する演算321とa(5)を参照する演算325とは、ループ4回転分離れている。a(6)を定義する演算322とa(6)を参照する演算326とは、ループ4回転分離れている。このため、SIMD命令を用いても、a(i)を定義する演算とa(i)を参照する演算とは並列化されず、実質的に定義・参照の順序関係は崩れない。例えば、a(5),a(6)を定義する演算はコード222の演算34に対応し、a(5),a(6)を参照する演算はコード222の演算35に対応する。演算34の後に演算35が実行される限り、a(5),a(6)の定義・参照の順序関係は崩れない。よって、SIMD化が可能である。
【0066】
図11は、SIMD最適化による第4のコード変換例を示す図である。
コード231には、倍精度浮動小数点型の長さ10000の配列変数a,bと、ループ変数iを2から10000まで1ずつ増加させるループと、ループ内の演算式a(i)=a(i−1)+b(i)とが定義されている。このとき、SIMD化部134は、例えば、コード231を、コード232,233を経由してコード234に変換する。
【0067】
コード232には、iを5から10000まで1ずつ増加させるループが定義されている。また、ループの前には、ループから外したi=2〜4に対応する以下の3つの演算が定義されている:a(2)=a(1)+b(2),a(3)=a(2)+b(3),a(4)=a(3)+b(4)。最初の数回転の演算をループの前に出すことは、「ループピーリング」と言うことがある。コード232のループピーリングは、コード233のようにループ内の演算式を変形するための準備として行われる。
【0068】
コード233には、ループ内の演算式a(i)=(((a(i−4)+b(i−3))+b(i−2))+b(i−1))+b(i)が定義されている。コード233の演算式は、配列変数aについてコード231,232の演算式を連続的に展開したものである。すなわち、a(i−1)がa(i−2)+b(i−1)に展開され、a(i−2)がa(i−3)+b(i−2)に展開され、a(i−3)がa(i−4)+b(i−3)に展開される。この展開により、a(i)を定義する演算式から、直前の3回転で定義される変数a(i−1),a(i−2),a(i−3)を除去することができる。
【0069】
配列変数aについての演算式の展開は、定義する変数a(i)と参照する変数a(i−n)の間の時系列上の差(回転数)が、SIMD幅またはそれ以上になるまで行われる。ここでは、SIMD幅が4であるため、参照する変数がa(i−4)になるまで演算式が展開されている。また、ループ変数iの初期値は、参照する変数の添字i−nが配列変数aの添字の最小値になるように決定される。ここでは、i−4=1より、ループ変数iの初期値は5と決定される。また、ループピーリングでは、初期値より小さいループ変数iの値に対応する演算が、ループの前に定義される。ここでは、ループ変数iの初期値が5であるため、i=2〜4に対応する演算がループの前に定義されている。
【0070】
コード234には、iを5から10000まで4ずつ増加させるループと、ループ内の演算式a(i:i+3)=(((a(i−4:i−1)+b(i−3:i))+b(i−2:i+1))+b(i−1:i+2))+b(i:i+3)とが定義されている。コード234の演算式は、コード233の演算式をSIMD化したものである。
【0071】
図12は、SIMD化の阻害要因の例を示す図である。
ここでは、コード232からコード233のような演算式の展開を行わず、仮に
図5,7,9と同様の方法でコード231をSIMD化した場合を考える。
【0072】
コード231のループを実行すると、まずi=2に対応する演算331、すなわち、a(2)=a(1)+b(2)が実行される。次に、i=3に対応する演算332、すなわち、a(3)=a(2)+b(3)が実行される。以下、i=4に対応する演算333、i=5に対応する演算334、i=6に対応する演算335、i=7に対応する演算336、i=8に対応する演算337、i=9に対応する演算338が実行される。
【0073】
ここで、a(i+1)を定義する演算ではa(i)を参照しているため、演算331〜334の間には依存関係がある。例えば、a(2)は、演算331で定義され演算332で参照される。a(3)は、演算332で定義され演算333で参照される。この依存関係は、a(i)を先に定義して後に参照するという定義・参照の順序関係になっており、演算331〜334をそのまま並列化するとその順序関係が崩れる。例えば、演算331〜334をa(2:5)=a(1:4)+b(2:5)という演算36に置き換えると、a(2),a(3)が先に参照されて、その後にa(2),a(3)が更新される。
【0074】
よって、演算331〜334を演算36のようにそのまま並列化すると、元の演算式とは計算方法が異なる不正な変形となる。同様に、演算335〜338の間には定義・参照の順序の依存関係があり、演算335〜338をa(6:9)=a(5:8)+b(6:9)という演算37に置き換えるとその順序関係が崩れる。よって、演算335〜338を演算37のようにそのまま並列化すると、元の演算式とは計算方法が異なる不正な変形となる。
【0075】
図13は、SIMD化前後の演算の第4の対応例を示す図である。
上記問題を解決するため、SIMD化部134は、
図11のように演算式を展開して命令をSIMD化する。コード233のループを実行すると、まずi=5に対応する演算341、すなわち、a(5)=a(1)+b(2)+b(3)+b(4)+b(5)が実行される。次に、i=6に対応する演算342、すなわち、a(6)=a(2)+b(3)+b(4)+b(5)+b(6)が実行される。以下、i=7に対応する演算343、i=8に対応する演算344、i=9に対応する演算345、i=10に対応する演算346、i=11に対応する演算347、i=12に対応する演算348が実行される。
【0076】
これに対し、コード234のループを実行すると、まずi=5に対応する演算38、すなわち、a(5:8)=a(1:4)+b(2:5)+b(3:6)+b(4:7)+b(5:8)が実行される。次に、i=9に対応する演算39、すなわち、a(9:12)=a(5:8)+b(6:9)+b(7:10)+b(8:11)+b(9:12)が実行される。演算39は、a(5:8)を参照しているため演算38に依存する。
【0077】
ここで、演算341〜348の中には、a(i)を定義した後にa(i)を参照するという定義・参照の依存関係がある。例えば、a(5)は、演算341で定義され演算345で参照される。しかし、a(i)を定義する演算とa(i)を参照する演算とは、時系列上でSIMD幅以上離れている。例えば、a(5)を定義する演算341とa(5)を参照する演算345とは、ループ4回転分離れている。演算342〜344の間では、a(5)は参照されない。このため、SIMD命令を用いて、演算341〜344を演算38のように並列化することが可能である。同様に、SIMD命令を用いて、演算345〜348を演算39のように並列化することが可能である。
【0078】
図14は、SIMD最適化による第5のコード変換例を示す図である。
上記のコード234の演算式には、4つの加算の演算子が含まれている。コード234では、これら4つの加算が前方から後方に向かって逐次的に実行される。よって、CPU20が複数のSIMD命令を並列に実行可能な複数の演算器を備えていても、コード234の4つの加算は並列化されない。一方、実数の加算には結合則が成立するため、4つの加算の実行順序を入れ替えることも可能である。そこで、SIMD化部134は、1つの演算式内での並列性が高くなるように、演算子の実行順序を入れ替えてもよい。
【0079】
SIMD化部134は、例えば、コード233をコード235に変換する。コード235には、ループ内の演算式a(i)=(a(i−4)+(b(i−3)+b(i−2)))+(b(i−1)+b(i))が定義されている。これは、4つの加算の実行順序をコード233から変更したものである。コード235では、4つの加算のうち1番目の加算が2番目の加算の後に実行され、3番目の加算が1番目および4番目の加算の後に実行される。一方、1番目または2番目の加算と4番目の加算とは、並列に実行することが可能である。よって、コード235の演算式内の並列性は、コード233よりも高い。
【0080】
コード236には、iを5から10000まで4ずつ増加させるループと、ループ内の演算式a(i:i+3)=(a(i−4:i−1)+(b(i−3:i)+b(i−2:i+1)))+(b(i−1:i+2)+b(i:i+3))とが定義されている。コード236の演算式は、コード235の演算式をSIMD化したものである。CPU20が複数のSIMD命令を並列に実行できる複数の演算器を備えている場合、コード236の1番目または2番目の加算と4番目の加算とを並列に実行することが可能である。
【0081】
ただし、プロセッサによる浮動小数点演算ではレジスタ長が有限であり丸め誤差が発生し得ることから、結合則が成立する場合であっても、複数の演算子の実行順序を入れ替えることで演算結果が変わってしまうことがある。そこで、第2の実施の形態では、コンパイルオプションによって演算子の実行順序の入れ替えが明示的に許可された場合のみ、SIMD化部134が、
図14に示したような演算式の変換を行うこととする。
【0082】
図15は、SIMD最適化による第6のコード変換例を示す図である。
上記では、配列変数a,bの長さやループ変数iの上限値・下限値が固定値であるコードをSIMD最適化する例を示した。ただし、配列変数a,bの長さやループ変数iの上限値・下限値がパラメータになっているコードについてもSIMD最適化を行い得る。
【0083】
コード241には、倍精度浮動小数点型の長さnの配列変数a,bと、ループ変数iと、iを2からmまで1ずつ増加させるループと、ループ内の演算式a(i)=a(i−1)+b(i)とが定義されている。n,mは、コンパイル時にはその値が未定でありループの実行時までにその値が決定されるパラメータである。このとき、SIMD化部134は、例えば、コード241を以下のようなコード242に変換する。
【0084】
コード242には、iを5から(m÷4)×4まで4ずつ増加させる1番目のループと、1番目のループ内のSIMD化した演算式と、ループピーリングされたi=2〜4に対応する3回転分の演算とが定義されている。また、コード242には、1番目のループの後からi=mまでに対応する2番目のループと、2番目のループ内のSIMD化されない演算式とが定義されている。また、コード242には、mが7未満であるか判定しmが7未満の場合に1番目のループをスキップする条件分岐が定義されている。このように、配列変数a,bの長さやループ変数iの上限値・下限値が可変であっても、SIMD化部134は、条件分岐を用いてSIMD最適化を行うことが可能である。
【0085】
以上説明したように、SIMD化部134は、ループ内の演算式が前回転(前イテレーション)の演算結果を参照する変数を含んでいても、演算式を展開することで、ループ内の複数回転分の演算をSIMD化することが可能である。しかし、演算式の展開は、命令数やメモリアクセス回数を増加させ得る。このため、展開されSIMD化された演算式の実行効率が、展開前のSIMD化しない演算式の実行効率よりも高くなるとは限らない。
【0086】
例えば、単純なレジスタ割付方法によれば、
図11のコード231の演算式は、2つのロード命令と1つの加算命令と1つのストア命令とで実現され得る。よって、コード231のループ4回転の演算は、16命令で実現される。一方、
図11のコード234や
図14のコード236の演算式は、5つのロード命令と4つの加算命令と1つのストア命令とで実現され得る。よって、元のループ4回転に相当する演算は、10命令で実現される。このように、多くの場合、上記のSIMD最適化によって命令の実行回数が削減される。しかし、以下のような理由から、実行効率が高くなるとは限らない。
【0087】
(1)CPUのアーキテクチャの中には、SIMD命令の実行時間(サイクル数)がスカラ命令よりも遅いものがある。SIMD命令の実行時間がどの程度遅いかは、CPUのアーキテクチャに依存する。(2)ループ内に並列に実行可能な演算式が多数あるなど、SIMD化しなくても演算器の空き時間が少なくなるように命令スケジューリングが可能である場合、SIMD化による改善の効果が小さい可能性がある。(3)メモリアクセス回数の増加によって、実効効率が低下するおそれがある。実行効率への影響の程度は、CPUのアーキテクチャやコードに依存する。SIMD幅が大きいほど、メモリアクセス回数の増加の影響が大きくなる可能性がある。(4)CPUが備える演算器が少ない場合やSIMD幅が小さい場合には、SIMD化による改善の効果が小さい可能性がある。
【0088】
そこで、SIMD化部134は、ループ内の演算について、SIMD化前のコード(例えば、コード231)とSIMD化後のコード(例えば、コード234やコード236)の実行時間を示すサイクル数を試算する。そして、SIMD化部134は、後者のサイクル数が前者のサイクル数よりも小さい、すなわち、SIMD化により実行効率が高くなっている場合のみ、中間コード記憶部122の中間コードを書き換えるようにする。
【0089】
図16は、プロセッサアーキテクチャの例を示す図である。
ループ実行のサイクル数を算出するにあたり、SIMD化部134は、ターゲットのCPU20のアーキテクチャを示すプロセッサ情報を参照する。プロセッサ情報には、CPU20が備える演算器の数や各種類の命令の実行に要するサイクル数などが記載される。このプロセッサ情報は、例えば、RAM102やHDD103に予め記憶されている。
【0090】
CPU20は、演算器24〜27を有する。演算器24,25はぞれぞれ、スカラ型のロード命令、SIMD幅4のSIMD型のロード命令、スカラ型のストア命令、および、SIMD幅4のSIMD型のストア命令を実行できる。演算器26,27はそれぞれ、スカラ型の加算と、SIMD幅4のSIMD型の加算命令を実行できる。演算器24〜27は、パイプライン処理化されている。よって、演算器24〜27それぞれには、1サイクル毎に1命令(1スカラ命令または1SIMD命令)実行させることができる。
【0091】
命令が投入されてからその命令の実行が完了するまでのパイプライン上でのサイクル数は、命令の種類によって異なる。演算器24,25において、スカラ型のロード命令は3サイクル、SIMD型のロード命令は3サイクル、スカラ型のストア命令は1サイクル、SIMD型のストア命令は1サイクルを要する。演算器26,27において、スカラ型の加算命令は3サイクル、SIMD型の加算命令は3サイクルを要する。
【0092】
演算器24〜27は、並列に命令を実行できる。よって、1サイクル毎に最大で4つのスカラ型またはSIMD型の命令を投入できる。ただし、メモリからの読み出しの最大同時実行数は2である一方、メモリへの書き込みの最大同時実行数は1である。このため、演算器24,25の何れか一方がストア命令に従ってメモリにデータを書き込んでいるときは、他方はロード命令によるデータの読み出しおよびストア命令によるデータの書き込みを行わない。すなわち、CPU20には、1つまたは2つの加算命令と1つまたは2つのロード命令を同時に投入することができる。また、CPU20には、1つまたは2つの加算命令と1つのストア命令を同時に投入することができる。
【0093】
図17は、ループ内のサイクル数の第1の計算例を示す図である。
ここでは、
図16のCPU20にオブジェクトコードを実行させる場合を考える。コード231のループ4回転の演算は、
図17のようにスケジューリングできる。
【0094】
サイクル#1で、b(i)を読み出すロード命令を投入する。
図17において、t20などは、SIMDレジスタでないスカラ型のレジスタを示す。サイクル#4で、a(i−1)とb(i)を足す加算命令と、b(i+1)を読み出すロード命令を投入する。サイクル#6で、b(i+2)を読み出すロード命令を投入する。サイクル#7で、a(i)を書き込むストア命令と、a(i)とb(i+1)を足す加算命令を投入する。この加算命令で参照するa(i)については、前回転で算出したものをレジスタ(レジスタt30)に残しておくことで、メモリから読み出すロード命令を省略している。
【0095】
サイクル#9で、b(i+3)を読み出すロード命令を投入する。サイクル#10で、a(i+1)を書き込むストア命令と、a(i+1)とb(i+2)を足す加算命令を投入する。サイクル#13で、a(i+2)を書き込むストア命令と、a(i+2)とb(i+3)を足す加算命令を投入する。サイクル#16で、a(i+3)を書き込むストア命令を投入する。サイクル#17で、iに4を足す加算命令を投入する。この試算によって、ループ4回転の演算が18サイクルで終了すると予測される。
【0096】
図18は、ループ内のサイクル数の第2の計算例を示す図である。
コード234のループ1回転の演算は、
図18のようにスケジューリングできる。
サイクル#1で、a(i−4:i−1)を読み出すロード命令と、b(i−3:i)を読み出すロード命令を投入する。
図18において、ts1,ts2などは、SIMDレジスタを示す。サイクル#2で、b(i−2:i+1)を読み出すロード命令と、b(i−1:i+2)を読み出すロード命令を投入する。サイクル#3で、b(i:i+3)を読み出すロード命令を投入する。ここで、b(i−2),b(i−1),b(i),b(i+1),b(i+2)は、異なるSIMDレジスタに重複して格納されるため、2以上のSIMD命令によって2回以上メモリから読み出されることになる。
【0097】
サイクル#4で、a(i−4:i−1)とb(i−3:i)を足す加算命令を投入する。この加算命令はSIMD型であり、スカラ型の加算命令4つに相当する。サイクル#7で、サイクル#4の演算結果とb(i−2:i+1)を足す加算命令を投入する。サイクル#10で、サイクル#7の演算結果とb(i−1:i+2)を足す加算命令を投入する。サイクル#13で、サイクル#10の演算結果とb(i:i+3)を足す加算命令を投入する。サイクル#16で、a(i:i+3)を書き込むストア命令を投入する。サイクル#17で、iに4を足す加算命令を投入する。この試算によって、変換後のループ1回転の演算が18サイクルで終了すると予測される。
【0098】
コード234の演算式では、4つの加算が逐次的に実行される。このため、
図18に示すように、サイクル#4〜#15において、CPU20の並列処理能力が活用されず演算器24〜26の空き時間が多くなっている。その結果、コード234の予測サイクル数はコード231と同じになっており、実行効率が改善していない。
【0099】
図19は、ループ内のサイクル数の第3の計算例を示す図である。
コード236のループ1回転の演算は、
図19のようにスケジューリングできる。
サイクル#1で、b(i−3:i)を読み出すロード命令と、b(i−2:i+1)を読み出すロード命令を投入する。サイクル#2で、b(i−1:i+2)を読み出すロード命令と、b(i:i+3)を読み出すロード命令を投入する。サイクル#3で、a(i−4:i−1)を読み出すロード命令を投入する。サイクル#4で、b(i−3:i)とb(i−2:i+1)を足す加算命令を投入する。サイクル#5で、b(i−1:i+2)とb(i:i+3)を足す加算命令を投入する。演算式の2番目と4番目の加算は依存関係がないため、サイクル#4の結果を待たずにサイクル#5の加算命令を投入できる。
【0100】
サイクル#7で、a(i−4:i−1)とサイクル#4の演算結果を足す加算命令を投入する。サイクル#10で、サイクル#7の演算結果とサイクル#5の演算結果を足す加算命令を投入する。サイクル#13で、a(i:i+3)を書き込むストア命令を投入する。サイクル#14で、iに4を足す加算命令を投入する。この試算によって、変換後のループ1回転の演算が15サイクルで終了すると予測される。
【0101】
コード236の演算式は、コード234の演算式よりも並列性が高い。このため、SIMD命令でも、CPU20の並列処理能力を活用することができる。その結果、コード236の予測サイクル数はコード231よりも少なくなり、実行効率が改善する。この場合、SIMD化部134は、コード231をコード236に置き換えると決定する。
【0102】
なお、
図18,19では、CPU20のSIMD幅4の全てを利用して、1つのSIMD命令で4つのスカラ命令相当の演算を実行することとした。これに対し、SIMD幅の一部のみを利用するようにしてもよい。例えば、CPU20のSIMD幅が4であっても、1つのSIMD命令で並列実行する演算を2つに制限してもよい。メモリアクセスの負荷が大きい場合に、1つのSIMD命令で並列実行する演算の数(多重度)を制限することが考えられる。その場合、例えば、SIMD化部134は、異なる多重度について予測サイクル数を算出し、予測サイクル数が最小になる多重度を選択してもよい。
【0103】
次に、CPUのアーキテクチャの違いがサイクル数に与える影響について説明する。
図20は、プロセッサアーキテクチャの他の例を示す図である。
CPU20aは、演算器24a〜27aを有する。演算器24a,25aはぞれぞれ、スカラ型のロード命令、SIMD幅4のSIMD型のロード命令、スカラ型のストア命令、および、SIMD幅4のSIMD型のストア命令を実行できる。演算器26a,27aはそれぞれ、スカラ型の加算と、SIMD幅4のSIMD型の加算命令を実行できる。
【0104】
命令が投入されてからその命令の実行が完了するまでのサイクル数は、
図16に示したCPU20と異なる。演算器24a,25aにおいて、スカラ型のロード命令は3サイクル、SIMD型のロード命令は5サイクル、スカラ型のストア命令は1サイクル、SIMD型のストア命令は3サイクルを要する。演算器26a,27aにおいて、スカラ型の加算命令は3サイクル、SIMD型の加算命令は5サイクルを要する。すなわち、CPU20と異なり、SIMD命令に要するサイクル数がスカラ命令より大きくなっている。
【0105】
図21は、ループ内のサイクル数の第4の計算例を示す図である。
ここでは、
図20のCPU20aにオブジェクトコードを実行させる場合を考える。コード236のループ1回転の演算は、
図21のようにスケジューリングできる。
【0106】
サイクル#1で、b(i−3:i)を読み出すロード命令と、b(i−2:i+1)を読み出すロード命令を投入する。サイクル#2で、b(i−1:i+2)を読み出すロード命令と、b(i:i+3)を読み出すロード命令を投入する。サイクル#3で、a(i−4:i−1)を読み出すロード命令を投入する。サイクル#6で、b(i−3:i)とb(i−2:i+1)を足す加算命令を投入する。サイクル#7で、b(i−1:i+2)とb(i:i+3)を足す加算命令を投入する。
【0107】
サイクル#11で、a(i−4:i−1)とサイクル#6の演算結果を足す加算命令を投入する。サイクル#16で、サイクル#11の演算結果とサイクル#7の演算結果を足す加算命令を投入する。サイクル#21で、a(i:i+3)を書き込むストア命令を投入する。サイクル#22で、iに4を足す加算命令を投入する。この試算によって、変換後のループ1回転の演算が23サイクルで終了すると予測される。
【0108】
ターゲットのCPU20aではSIMD命令の実行時間が長い。このため、
図21に示すように、サイクル#8〜#20において、CPU20aの並列処理能力が活用されず演算器24a〜27aの空き時間が多くなっている。その結果、コード236の予測サイクル数はコード231より大きくなっており、実行効率が悪化している。
【0109】
次に、元のコードの違いがSIMD化の改善効果に与える影響について説明する。
図22は、ループ内のサイクル数の第5の計算例を示す図である。
SIMD化前のコードとして、コード251を考える。コード251には、ループ変数iを2から10000まで1ずつ増加させるループが定義されている。ループ内には、次の3つの演算式が定義されている:a1(i)=a1(i−1)+b1(i),a2(i)=a2(i−1)+b2(i),a3(i)=a3(i−1)+b3(i)。
【0110】
ここでは、
図16のCPU20にオブジェクトコードを実行させる場合を考える。コード251のループ4回転の演算は、
図22のようにスケジューリングできる。
サイクル#1で、b1(i)を読み出すロード命令と、b1(i+2)を読み出すロード命令を投入する。サイクル#2で、b2(i)を読み出すロード命令と、b2(i+2)を読み出すロード命令を投入する。サイクル#3で、b3(i)を読み出すロード命令と、b3(i+2)を読み出すロード命令を投入する。サイクル#4で、b1(i+1)を読み出すロード命令と、b1(i+3)を読み出すロード命令と、a1(i−1)+b1(i)の加算命令を投入する。サイクル#5で、b2(i+1)を読み出すロード命令と、b2(i+3)を読み出すロード命令と、a2(i−1)+b2(i)の加算命令を投入する。サイクル#6で、b3(i+1)を読み出すロード命令と、b3(i+3)を読み出すロード命令と、a3(i−1)+b3(i)の加算命令を投入する。
【0111】
サイクル#7で、a1(i)を書き込むストア命令と、a1(i)+b1(i+1)の加算命令を投入する。サイクル#8で、a2(i)を書き込むストア命令と、a2(i)+b2(i+1)の加算命令を投入する。サイクル#9で、a3(i)を書き込むストア命令と、a3(i)+b3(i+1)の加算命令を投入する。サイクル#10で、a1(i+1)を書き込むストア命令と、a1(i+1)+b1(i+2)の加算命令を投入する。サイクル#11で、a2(i+1)を書き込むストア命令と、a2(i+1)+b2(i+2)の加算命令を投入する。サイクル#12で、a3(i+1)を書き込むストア命令と、a3(i+1)+b3(i+2)の加算命令を投入する。
【0112】
サイクル#13で、a1(i+2)を書き込むストア命令と、a1(i+2)+b1(i+3)の加算命令を投入する。サイクル#14で、a2(i+2)を書き込むストア命令と、a2(i+2)+b2(i+3)の加算命令を投入する。サイクル#15で、a3(i+2)を書き込むストア命令と、a3(i+2)+b3(i+3)の加算命令を投入する。サイクル#16で、a1(i+3)を書き込むストア命令を投入する。サイクル#17で、a2(i+3)を書き込むストア命令を投入する。サイクル#18で、a3(i+3)を書き込むストア命令を投入する。サイクル#19で、i+4の加算命令を投入する。この試算によって、ループ4回転の演算が20サイクルで終了すると予測される。
【0113】
コード251のループには、依存関係のない3つの演算式が含まれている。このため、スカラ命令を密にスケジューリングでき、演算器24〜26の空き時間を少なくできる。
図23は、ループ内のサイクル数の第6の計算例を示す図である。
【0114】
コード252は、コード251をSIMD化したものである。コード252には、ループ変数iを5から10000まで4ずつ増加させるループが定義されている。ループ内には、次の3つの演算式が定義されている。a1(i:i+3)=(a1(i−4:i−1)+(b1(i−3:i)+b1(i−2:i+1)))+(b1(i−1:i+2)+b1(i:i+3))。a2(i:i+3)=(a2(i−4:i−1)+(b2(i−3:i)+b2(i−2:i+1)))+(b2(i−1:i+2)+b2(i:i+3))。a3(i:i+3)=(a3(i−4:i−1)+(b3(i−3:i)+b3(i−2:i+1)))+(b3(i−1:i+2)+b3(i:i+3))。
【0115】
ここでは、
図16のCPU20にオブジェクトコードを実行させる場合を考える。コード252のループ1回転の演算は、
図23のようにスケジューリングできる。
サイクル#1で、b1(i−3:i)を読み出すロード命令と、b1(i−2:i+1)を読み出すロード命令を投入する。サイクル#2で、b2(i−3:i)を読み出すロード命令と、b2(i−2:i+1)を読み出すロード命令を投入する。サイクル#3で、b3(i−3:i)を読み出すロード命令と、b3(i−2:i+1)を読み出すロード命令を投入する。サイクル#4で、b1(i−1:i+2)を読み出すロード命令と、b1(i:i+3)を読み出すロード命令を投入する。サイクル#5で、b2(i−1:i+2)を読み出すロード命令と、b2(i:i+3)を読み出すロード命令を投入する。サイクル#6で、b3(i−1:i+2)を読み出すロード命令と、b3(i:i+3)を読み出すロード命令を投入する。
【0116】
サイクル#7で、a1(i−4:i−1)を読み出すロード命令と、b1(i−3:i)+b1(i−2:i+1)の加算命令と、b1(i−1:i+2)+b1(i:i+3)の加算命令を投入する。サイクル#8で、a2(i−4:i−1)を読み出すロード命令と、b2(i−3:i)+b2(i−2:i+1)の加算命令と、b2(i−1:i+2)+b2(i:i+3)の加算命令を投入する。サイクル#9で、a3(i−4:i−1)を読み出すロード命令と、b3(i−3:i)+b3(i−2:i+1)の加算命令と、b3(i−1:i+2)+b3(i:i+3)の加算命令を投入する。
【0117】
サイクル#10で、a1(i−4:i−1)とサイクル#7の演算結果を足す加算命令を投入する。サイクル#11で、a2(i−4:i−1)とサイクル#8の演算結果を足す加算命令を投入する。サイクル#12で、a3(i−4:i−1)とサイクル#9の演算結果を足す加算命令を投入する。サイクル#13で、サイクル#10の演算結果とサイクル#7の演算結果を足す加算命令を投入する。サイクル#14で、サイクル#11の演算結果とサイクル#8の演算結果を足す加算命令を投入する。サイクル#15で、サイクル#12の演算結果とサイクル#9の演算結果を足す加算命令を投入する。
【0118】
サイクル#16で、a1(i:i+3)を書き込むストア命令を投入する。サイクル#17で、a2(i:i+3)を書き込むストア命令を投入する。サイクル#18で、a3(i:i+3)を書き込むストア命令を投入する。サイクル#19で、i+4の加算命令を投入する。この試算によって、変換後のループ1回転の演算が20サイクルで終了すると予測される。
図23に示すように、コード252のSIMD命令は密にスケジューリングできるものの、変換前のコード251のスケジューリング効率が高い。このため、コード252の予測サイクル数はコード251と同じであり、実行効率が改善しない。
【0119】
次に、SIMD化部134が行うSIMD最適化の手順例を説明する。
図24は、SIMD最適化の手順例を示すフローチャートである。
(S1)SIMD化部134は、中間コード記憶部122から中間コードを読み出し、中間コードの中からループを検出する。ループ内には、1以上の演算式が含まれる。
【0120】
(S2)SIMD化部134は、ターゲットのCPUのアーキテクチャにおけるSIMD幅Nを確認する。例えば、SIMD化部134は、RAM102またはHDD103に予め記憶されたプロセッサ情報を参照して、SIMD幅Nを確認する。ターゲットのCPUの種類は、コンパイル時にコンパイルオプションとして指定されてもよい。
【0121】
SIMD化部134は、ループのN回転分の演算を、演算式を変形せずに並列化可能であるか判断する。N回転分の演算を同時実行しても変数の定義・参照の順序が変わらない場合、並列化可能であると判断される。一方、同時実行すると変数の定義・参照の順序が変わってしまう場合または変わる可能性がある場合、並列化可能でないと判断される。
【0122】
例えば、1〜N−1回転前の演算結果を参照する変数が演算式に含まれている場合、並列化可能でないと判断される。また、何回転前の演算結果を参照するのか静的に決定できない変数が演算式に含まれている場合(インダイレクトアクセス)、並列化可能でないと判断される。また、ループがユーザ関数の呼び出しを含む場合や、ループの途中で制御がループ外に抜ける可能性のある場合も、並列化可能でないと判断される。並列化可能な場合はステップS10に処理が進み、並列化可能でない場合はステップS3に処理が進む。
【0123】
(S3)SIMD化部134は、SIMD化の阻害要因(ループ内の演算が並列化可能でない原因)が、変数の定義・参照の順序であるか判断する。すなわち、1〜N−1回転前の演算結果を参照する変数のみが阻害要因であるか判断する。この条件を満たす場合はステップS4に処理が進み、条件を満たさない場合はSIMD最適化が終了する。
【0124】
(S4)SIMD化部134は、ループピーリングを行う。すなわち、SIMD化部134は、ループ内の最初の数回転の演算がループ外で実行されるように、ループ直前に演算式を挿入する。例えば、SIMD化部134は、定義される配列変数の添字と参照される配列変数の添字とが幾つ離れていれば並列化可能になるかを、SIMD幅Nから算出する。そして、SIMD化部134は、変形後の演算式における参照される配列変数の添字とループ変数iの初期値から、何回転分の演算をループ外に出すか決定する。
【0125】
(S5)SIMD化部134は、演算式に含まれる1〜N−1回転前の演算結果を参照する変数を、その演算式自体を用いて展開する。変数の展開は、演算式に1〜N−1回転前の演算結果を参照する変数が含まれなくなるまで連続的に行われ得る。例えば、元の演算式が1回転前の演算結果を参照する変数を含む場合、変数の展開を連続的にN−1回行うことで、1〜N−1回転前の演算結果を参照する変数を含まない演算式を得る。
【0126】
(S6)SIMD化部134は、コンパイルコマンドに、演算順序の変更を許容するコンパイルオプションが付加されていたか確認する。当該コンパイルオプションが付加されていた場合、SIMD化部134は、展開後の演算式に含まれる演算子の実行順序を最適化する。例えば、SIMD化部134は、プロセッサ情報を参照して、ターゲットのCPUが備える演算器の種類および数を確認する。ターゲットのCPUが2以上のSIMD命令を並列実行可能な場合、並列化が容易になるように演算子の実行順序を入れ替える。
【0127】
(S7)SIMD化部134は、元のループの記述に基づいて命令スケジューリングを仮実行し、SIMD命令を用いずにN回転分の演算を実行した場合の所要サイクル数を予測する。また、SIMD化部134は、ステップS5,S6の変形後のループの記述に基づいて命令スケジューリングを仮実行し、SIMD命令を用いて元のループN回転相当の演算を実行した場合の所要サイクル数を予測する。
【0128】
(S8)SIMD化部134は、SIMD化後の所要サイクル数がSIMD化前の所要サイクル数より減少するか、すなわち、SIMD化によってループの実行効率が向上するか判断する。所要サイクル数が減少すると予測される場合はステップS9に処理が進み、所要サイクル数が減少しないと予測される場合はSIMD最適化が終了する。
【0129】
(S9)SIMD化部134は、ステップS5,S6で変形した演算式を採用する。
(S10)SIMD化部134は、ステップS2で並列化可能と判断された演算式またはステップS9で採用した変形後の演算式から、ループN回転分の演算を1回転で実行するSIMD命令を生成する。そして、SIMD化部134は、中間コード記憶部122に記憶された中間コードを、SIMD命令を含むものに書き換える。すなわち、中間コードに含まれる幾つかのスカラ命令がSIMD命令に変換される。このSIMD最適化によって、SIMD命令を含むオブジェクトコードが生成されることになる。
【0130】
図25は、並列化可能と判断されないコード例を示す図である。
上記のステップS2において並列化可能でないと判断される演算式の一例として、インダイレクトアクセスされる配列変数を含む演算式が挙げられる。コード261には、倍精度浮動小数点型の長さ10000の配列変数a,b,idx1,idx2と、ループ変数iを1から10000まで1ずつ増加させるループが定義されている。ループ内には、演算式a(idx1(i))=a(idx2(i))+b(i)が定義されている。
【0131】
配列変数idx1は、ループi回転目の演算において配列変数aの何番目の要素が定義されるべきかを示している。配列変数idx2は、ループi回転目の演算において配列変数aの何番目の要素が参照されるべきかを示している。コード261では、ループi回転目の演算において、配列変数aの何番目の要素が参照され配列変数aの何番目の要素が定義されるかは、静的に決定されずコード261の実行時に決定される。このため、1〜N−1回転前の演算結果が参照されるか否かを、コンパイル時に判断することができない。そこで、コード261のループは並列化可能でないと判断される。なお、コード261については、上記のステップS3の判断もNOになりSIMD最適化は行われない。
【0132】
第2の実施の形態のコンパイル装置100によれば、SIMD幅をNとすると、1〜N−1回転前の演算結果が参照されないようにループ内の演算式が展開される。これにより、そのままではSIMD化できないループ内の演算をSIMD化することが可能となる。また、SIMD化しない場合の予測サイクル数と演算式を展開してSIMD化する場合の予測サイクル数とが比較され、SIMD化するか否かが決定される。これにより、演算式を展開することによる命令数やメモリアクセス回数の増加と、SIMD化することによる命令数の削減とが総合的に考慮され、ループの実行効率を向上させることができる。
【0133】
また、ターゲットのCPUのアーキテクチャを示すプロセッサ情報を参照して予測サイクル数を算出することで、ターゲットのCPUのアーキテクチャも考慮してSIMD最適化を行うか否か適切に判断できる。また、ユーザが許可する場合には、展開した演算式内での演算子の実行順序を最適化することで、ループの実行サイクル数を短縮できる。
【0134】
なお、前述のように、第1の実施の形態の情報処理は、コンパイル装置10にプログラムを実行させることで実現することができる。第2の実施の形態の情報処理は、コンパイル装置100にプログラムを実行させることで実現することができる。
【0135】
プログラムは、コンピュータ読み取り可能な記録媒体(例えば、記録媒体113)に記録しておくことができる。記録媒体としては、例えば、磁気ディスク、光ディスク、光磁気ディスク、半導体メモリなどを使用できる。磁気ディスクには、FDおよびHDDが含まれる。光ディスクには、CD、CD−R(Recordable)/RW(Rewritable)、DVDおよびDVD−R/RWが含まれる。プログラムは、可搬型の記録媒体に記録されて配布されることがある。その場合、可搬型の記録媒体からHDDなどの他の記録媒体(例えば、HDD103)にプログラムを複製して(インストールして)実行してもよい。