Mercurial > hg > Papers > 2015 > yuhi-master
view paper/chapter5.tex @ 39:05add77d93b2
thanx
author | Yuhi TOMARI <yuhi@cr.ie.u-ryukyu.ac.jp> |
---|---|
date | Mon, 16 Feb 2015 03:49:19 +0900 |
parents | 7956856211c5 |
children | d4be7f4b9a73 |
line wrap: on
line source
\chapter{GPGPU への対応} Cerium の新たな演算資源として GPU の使用を可能にした。 現在、GPU のような異なる種類のアーキテクチャを搭載した CPU 、つまりヘテロジニアスな CPU が増えている。 特定の計算に特化した Task の生成やスケジューリングを行い、 GPGPU により高い並列度を出す研究は様々な分野で行われている。 本研究では Cerium を特定の計算に限らず、 GPU を用いて汎用計算を行えるフレームワークに改良する。 \section{OpenCL および CUDA による実装} OpenCL 、CUDA による GPGPU 対応を行った。 Scheduler と CpuThreads に対応させる形で OpenCL を用いた GpuScheduler と GpuThreads、 CUDA を用いた CudaScheduler と CudaThreads を実装した。 それぞれの Scheduler 内で各フレームワークの API を用いて GPU の制御を行っている。 TaskManager から受け取った TaskList をもとに Device 上のメモリバッファを作成する。 その後 CommandQueue、 Stream といったそれぞれの Queue に Device 制御用の Command を Queueing していく。 Command は Queueing した順に実行されるので、以下のように Command を Queueing する。 \begin{enumerate} \item Host から Device へのデータ転送 \item kernel の実行 \item Device から Host へのデータ転送 \end{enumerate} データの転送や kernel の実行は非同期 API を用いることで並列に行うことができる。 通常、フレームワークが依存関係を解決して実行するが、 非同期 API を用いる場合はユーザが依存関係を考慮する必要がある。 しかし Task の依存関係は TaskManager が既に解決した状態で送ってくるので、 Scheduler は依存関係を考慮せずに実行して問題ない。 GPGPU 用の Scheduler は CommandQueue を2つ持っており、Task をパイプライン的に実行する。 転送されてきた Task が全て終了すると、 TaskManager 間の通信を担当する同期キューである mail を通して TaskManager に Task の終了を通知する。 終了が通知されると TaskManager でその TaskList に関する依存関係が解消され、 GPGPU の Scheduler 内で Platform や Device ID の取得、 Context の生成、 Kernel の Build と Load等も行っており、OD 並列処理したい計算のみに集中できる。 \section{データ並列} 並列プログラミングにおいて、明示的な並列化部分はループ部分である。 GPU は数百個のコアを有しており、ループ部分に対してデータ並列で処理を行うことで CPU より高速に演算を行う事ができる。 プログラムの大部分がループであれば、データ並列による実行だけでプログラムの性能は向上する。 OpenCL 、 CUDA ともにデータ並列をサポートしている。 OpenCL と CUDA はTask を実行する際にデータをどう分割するか指定し、 kernel にデータ並列用の処理を加えることで可能となる。 \ref{sec:multicore_dataparallel}節で Cerium でマルチコア CPU におけるデータ並列を可能にした。 GPGPU においてもデータ並列実行をサポートする。 GPU 上でのデータ並列実行もマルチコア CPU と変わらず、iterate API によりデータ並列用の Task を生成することができる。 iterate で Task を生成することで Scheduler が OpenCL 及び CUDA の API に適切なパラメタを渡している。 Task の生成部分は マルチコア CPU と GPU で完全に同じ形式で記述できる。 データ並列実行の際、Task は以下のように記述する。 なお、例題は multiply を用いている。 \begin{lstlisting}[frame=lrbt,label=src:multiply_opencl,caption=Multiply(OpenCL),numbers=left] __kernel void multiply(__global const long *params, __global const float *input1, __global const float *input2, __global const float *output) { long id = get_global_id(0); output[id] = input1[id] * input2[id]; } \end{lstlisting} \begin{lstlisting}[frame=lrbt,label=src:multiply_cuda,caption=Multiply(CUDA),numbers=left] __global__ void multiply(__global const long *params, __global const float *input1, __global const float *input2, __global const float *output) { int id = blockIdx.x * blockDim.x + threadIdx.x; output[id] = input1[id] * input2[id]; } \end{lstlisting} このような Task を分割数分生成する。 分割数は Task それぞれのフレームワークが用意している API を用いて指定する。 \begin{itemize} \item 自分の計算する範囲を取得(ソースコード\ref{src:multiply_opencl}、ソースコード\ref{src:multiply_cuda}の7行目) \item 取得した範囲を計算(ソースコード\ref{src:multiply_opencl}、ソースコード\ref{src:multiply_cuda}の9行目) \end{itemize} いずれの Task も上記の手順で処理を行っている。 計算する範囲について、 OpenCL では取得用の API を用い、 CUDA では kernel の持つ組み込み変数から算出する。 マルチコア CPU では引数としてデータを直接渡していたが、OpenCL 、CUDA では上記の方法でメモリバッファから Load し、計算を行う。 値渡しや修飾子等若干の違いはあるが、OpenCL 、CUDA ともにマルチコア CPU(ソースコード:\ref{src:multicore_cpu}) とほぼ同じ形式で kernel を記述することができる。 CPU、 OpenCL、 CUDA いずれか1つの記述から残りのコードも生成できるようにする事が望ましい。 データ並列で実行する場合、 Input と Output を各 Task 間で共有するため、少ないコピーに抑えられる。 CPU ではメモリ領域を節約する事はできるが、 Task と Manager でメモリ領域が同じ(\ref{sec:shared_memory}節)なため、 コピーによるオーバーヘッドは少ない。 しかし GPU は SharedMemory ではなく、データの転送がオーバーヘッドとなるため、コピーを減らす事で並列度の向上が見込める。