はじめてのOpenCL～C言語プログラマが陥りやすい実装のポイント～

#1.はじめに
この記事は、インテル<SUP>®</SUP> FPGA Advent Calendarの18日目の記事です。

C言語やJavaといったプログラミング言語を使ってシステム開発に従事していたのですが、最近ではFPGAを使った開発も行っています。本記事では、C言語でのアプリケーション開発の経験はあるが、OpenCLを使ってFPGA上で動作するアプリケーション開発はしたことないプログラマが実際の経験を踏まえてOpenCL実装のポイントをご紹介致します。

本記事は以下の環境で動作確認を行っています。

* 動作確認環境
  * OS:CentOS 7.6-1810
  * SDK:インテル<SUP>®</SUP> FPGA SDK for OpenCL<SUP>TM</SUP> 19.4.0
  * FPGA:インテル<SUP>®</SUP>FPGAプログラマブルアクセラレーションカード

#2.FPGA上にアプリを実装する方法
FPGA上で動作するアプリケーションを開発するには、以下の2つの方法があります。
プログラムを記述するという点ではどちらも同じですが、<font color="red">**「何を」**</font>プログラミングするのかが異なります。HDLでの開発は論理回路設計を実施する必要があるため、ソフトウェアエンジニアにはハードルが高いですが、HLSであれば、コンパイラが論理回路設計部分を実施してくれるので、C言語などで開発するのと同様にFPGA上で動作するアプリケーションを実装することが可能です。今回はOpenCLというC言語に似た言語での実装について言及していきたいと思います。

1. HDL(Hardware Description Language)<br>
  →論理回路をプログラミング
1. HLS(High Level Synthesis)<br>
  →処理自体をプログラミング
![HDLとHLSの違い](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/700953/f4ed347b-d43a-54a3-da94-3942d277b732.png "HDLとHLSの違い")


#3.OpenCLの実装のポイント
プログラムを記述するときに、「良いプログラム」を書こうと誰もが考えると思います。では、この「良いプログラム」とはなんでしょうか？
例えば以下のようなものが該当すると思います。

* やりたいことが漏れなく実装されている
* 可読性が高い(読みやすい)
* コード規約やデザインパターンを準拠している
* 保守性が高い(変更したときに、他に影響を与えない)<br>
などなど

C言語などでは、上記のような観点を考慮して実装すればよいのですが、OpenCLではこれらに加えて<font color="red">**「良い回路」**</font>が作れているという観点が必要になります。
OpenCLで記述したプログラムをもとにコンパイラが論理回路設計を実施しますので、OpenCLの書き方が悪いと非効率な回路が生成されてしまい、FPGAの性能を十分に発揮することが困難になります。この良い回路を作るというところが、C言語などとは違いOpenCLで求められる実装のポイントになります。

#4.良い回路とは？
では、良い回路というのはどういうものなのでしょうか？
これについてはいろいろな観点があると思いますが、今回はii(initiation interval)に着目してご紹介したいと思います。
iiとは、次にデータが回路（処理）に入るまでのクロックサイクル数のことになります。
以下の例では、例1は良い回路が生成できていますが、例2は悪い回路が生成されているということになります。例1では、毎クロックデータが回路に入る(これをii=1という)が、例2では4クロック目で次のデータが入っており、回路に待ちが発生しています。常に回路にデータが入るようになっていることが、良い回路を生成するポイントの1つになります。
![良い回路](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/700953/f392f6c9-e275-ffb8-9cab-24760611ed92.png "良い回路")


#5.OpenCLでの実装のポイント
浮動小数点演算を例にOpenCLで良い回路を生成するポイントをご紹介します。このような計算はプログラムを作っているとよく使うので、ぜひいろいろなところで使って頂ければと思います。

##5.1浮動小数演算のサンプルプログラム
OpenCLで以下のプログラムを記述した場合iiはどのような値になるでしょうか？

```C++:修正前
kernel void calc(int N) {
    double sum = 0.0;    
    for (int i=0;i<N;i++) {
        sum += i * 0.01;
    }
}
```

インテル<SUP>®</SUP> FPGA SDK for OpenCL<SUP>TM</SUP>でコンパイルを実行するとレポートファイルが生成されます。このレポートファイルの「Loops Analysis」を確認することで、iiの値を確認することができます。修正前のコードでは、ii=12になっていることが確認できます。また、レポートのDetailsを確認すると、sum変数にデータ依存があり、浮動小数点演算に10クロックかかってることがわかります。
どのような回路が生成されているのかを確認するには、まずはこのレポートを確認することが重要になります。
![修正前レポート](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/700953/e5ba79c1-9805-45f0-2961-d7e4ed5b32bb.png "修正前レポート")



##5.2シフトレジスタによるiiの改善
このコードを今回はシフトレジスタ実装に変更することでiiの改善を行います。シフトレジスタでは、計算順序を入れ替えるので、計算精度が落ちる可能性がありますが、これを許容して計算性能を向上させる実装になります。
シフトレジスタは以下のような回路でデータが順次隣へ移動(シフト)する回路になります。
![シフトレジスタイメージ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/700953/acc8ef34-675f-d5fb-5f1c-1f327ad29636.png "シフトレジスタイメージ")

ii=12ということは、12サイクル後にしか計算結果を利用できないので、計算結果を使うのを12サイクル後になるように、計算をグルーピングすればよいことになります。シフトレジスタを使った処理イメージは以下のようになります。
![修正版実装のイメージ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/700953/c7d65fca-b25a-0440-f229-2ecec0427732.png "修正版実装のイメージ")

上記イメージを取り込んだ実装は以下になります。

```C++:修正後
#define SIZE 12
kernel void calc(int N) {
    double sreg[SIZE+1] = {0.0};
    double sum = 0.0;
    
    for (int i=0;i<N;i++) {
        double tmp = sreg[0] + i * 0.01;
        sreg[SIZE] = tmp;
        #pragma unroll
        for (int j=0;j<SIZE;j++) {
            sreg[j] = sreg[j+1];
        }
    }

    #pragma unroll
    for (int i=0;i<SIZE;i++) {
        sum += sreg[i];
    }
}
```

では、このプログラムを上記のように修正するとレポートはどのように変化するでしょうか？
修正後のコードをコンパイルして得られた以下のレポートを確認すると「ii=1」を達成できています。
シフトレジスタを利用することで、iiを改善できることが確認できました。

![修正後レポート](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/700953/38aaf063-eccd-e1ee-f341-44cdff0354e2.png "修正後レポート")

##5.3実行時間の比較
修正前後でどの程度性能に差が出るのか、実際に動作させて確認したいと思います。
ループ回数であるNを100,000,000に設定して処理を実行した結果が以下になります。
コードを修正することで、大幅な性能向上を確認することができます。このように、コードの書き方を変更することでFPGAの性能を十分に発揮できる「良い回路」が生成できるようになります。

|修正前|修正後|
|---|---|
|4.373秒|<font size="20" color="red">**0.481**</font>秒|

#6.まとめ
FPGA上で動作するアプリケーション実装はHLSを使って開発することで、ソフトウェアエンジニアでも十分対応することができます。一方でC言語やJavaなどと違い、OpenCLでは<font size="20" color="red">**「良い回路」**</font>を作成するにはどうすればよいのかを考慮しなければ、FPGAの性能を十分に発揮できません。ポイントをしっかり押さえれば、良い回路は作成できますので、みなさんも、高性能なFPGAアプリケーション開発にチャレンジしてみては如何でしょうか？



以上