CUDAプログラミング
This feature is not supported on the Wolfram Cloud.

CUDAはNVIDIAが開発した,GPU(グラフィカルプロセッシングユニット)をプログラムするための,Cに似た一般的なプログラム言語である.CUDALink はGPUをプログラムするのに必要な多くのステップを省く使いやすいインターフェースを提供する.コンパイル,リンキング,データの転送等はすべてWolfram言語のCUDALink が行う.これによりユーザはインターフェースやコードを書くのではなく,アルゴリズムを書くことに集中できるようになる.

このセクションではWolfram言語でCUDAプログラミングを開始する方法について述べる.

CUDAFunctionLoadCUDA関数をWolfram言語にロードする

Wolfram言語におけるCUDAプログラミング

このドキュメントでは,GPUアーキテクチャと,CUDAカーネルの書き方について述べる.最後には CUDALink を使って書かれた多くの適用例を示す.

はじめに

Common Unified Device Architecture (CUDA)はNVIDIAによって2007年後半に開発されたGPUをより一般的にする方法である.CUDALink はGPUプログラミングを簡単にしGPUのプログラミングは何年も前存在したが,プログラミングの難しさからその利用は限られていた.CUDALink はGPUプログラミングを簡単にし,より多く採用されることを狙いとしている.

Wolfram言語を使うときには,多くのステップについて考えなければならない.Wolfram言語では,CUDAをカーネルを書くだけである.これはNVIDIAアーキテクチャスタックの全レベルを使って行われる.

1.gif

CUDAアーキテクチャ

CUDAのプログラミングはデータ並列モデルに基づいている.高レベルの観点から見ると,問題はまず計算のために何百から何千ものスレッドに分割される.以下の計算を行うとする:

OutputData = Table[fun[InputData[[i, j]]], {i, 10000}, {j, 10000}]

CUDAのプログラミングパラダイムでは,この計算は以下に等しい:

CUDALaunch[fun, InputData, OutputData, {10000, 10000}]

は計算関数である.上式は10000×10000スレッドを開始し,指標を各スレッドに渡し,に関数を適用し,に結果を置く.CUDARunを参照されたい.CUDALinkに相当するものはCUDAFunctionである.

CUDAが何千ものスレッドを開始できる理由はハードウェアアーキテクチャにある.次のセクションでは,それについてと,スレッドが実行するのにどのように分割されるかについて述べる.

CUDAのグリッドとブロック

CUDAプログラミングはメッセージの受け渡しやスレッドベースの並列計算モデルとは異なり,問題を一,二,あるいは三次元のグリッドにマップする.以下では,一般的な二次元CUDAスレッドの構成を示す.

6.gif

各グリッドには複数のブロックが含まれ,各ブロックには複数のスレッドが含まれる.上の図におけるグリッド,ブロック,スレッドを以下に示す.

7.gif

一,二,あるいは三次元のどのスレッド構成を選ぶかは問題による.例えば画像処理の場合は,次の図のようにして画像をスレッド上にマップし,各画素に関数を適用する.

8.gif

一方,一次元セルオートマトンは一次元問題なので,次のようにマップできる:

9.gif

CUDAプログラムサイクル

CUDAプログラミングの概要は,多く(通常数千)のスレッドを開始してデータをコピーし,GPUの実行が終了するのを待ち(あるいは待機中にCPU計算を行い),最後に結果をデバイスからホストにコピーするというものである.

10.gif

上図はCUDAプログラムの一般的なサイクルの詳細を示している.

1.  GPUのメモリを割り当てる.GPUメモリはCPUメモリとは別のもので,プログラマは割当てコピーを管理しなければならない.

2.  メモリをCPUからGPUにコピーする.

3.  スレッドの構成を設定する.問題に適したブロックとグリッドの次元を選ぶ.

4.  設定したスレッドを開始する.

5.  CUDAスレッドを同期させると,デバイスがGPUメモリ上でその後の操作を行う前にすべてのタスクを確実に完了させるようにできる.

6.  スレッドが完了したら,メモリはGPUからCPUにコピーして返される.

7.  GPUメモリが解放される.

Wolfram言語を使うときには,多くのステップについて考えなければならない.Wolfram言語では,CUDAをカーネルを書くだけである.

11.gif

メモリの階層

CUDAのメモリは複数のレベルに分割され,それぞれが独自の利点と制限を持つ.次の図はCUDAで利用できるすべてのタイプのメモリを示す.

12.gif

大域メモリ

GPU上で利用できる,最も豊富だが最も遅いメモリ.このメモリは128,256,512MBのパッケージングがある.すべてのスレッドは大域メモリの要素にアクセスできるが,パフォーマンス上の理由からこれらのアクセスは最小限に抑えられ,さらに制約がある.

大域メモリにおけるパフォーマンスの制約は,最近のハードウェアでは緩められ,今後さらに緩められる予定である.一般に,大域メモリは複数回アクセスされると,パフォーマンスは劣化する.

テクスチャメモリ

テクスチャメモリは大域メモリと同じ場所にあるが,読取り専用である.テクスチャメモリは大域メモリに見られるパフォーマンスの低下はないが,サポートしている型はcharintfloatのみである.

コンスタントメモリ

グリッド内のどのスレッドからもアクセスできる高速なコンスタントメモリ.メモリはキャッシュされるが,全部で64KBに制限される.

共有メモリ

特定のブロックに局所的な高速メモリ.現在のハードウェア上では共有メモリ量はブロックごとに16KBに制限されている.

局所メモリ

局所メモリは各スレッドに局所的であるが,音波医らが変数をレジスタに置かない限り,大域メモリ内に存在する.パフォーマンスについては,メモリアクセスに時間がかかるため,一般に局所メモリは最小限に抑えるべきであると考えられているが,このメモリについては大域メモリのような問題は生じない.

計算能力

計算能力は,デバイスが行える操作を表す.現段階では計算能力は1.1,1.2,1.3,2.0のみがある.主な違いを以下にリストする.

計算能力追加機能
1.0基本実装
1.1アトミック演算
1.2共有アトミック演算とWarp Vote関数
1.3倍精度演算のサポート
2.0倍精度,L2キャッシュ,並行カーネル

使用中のシステムの計算能力についての情報はCUDAInformationで取り出すことができる.

CUDALink をまだロードしていない場合はロードする.

In[1]:=
Click for copyable input

システム上の全CUDAデバイスとを表示する.

In[2]:=
Click for copyable input
Out[2]=

複数のCUDAデバイス

システムハードウェアがサポートしている場合はCUDAでは計算を行うデバイスを選択することができる.デフォルトでは最も速いものが選ばれるが,ユーザはこれを変更することができる.一旦デバイスを設定したら(自動的に選ばれたものでもユーザが指定したものでも),カーネルセッション中はデバイスを変更することはできない.

CUDAカーネルを書く

CUDAカーネルは何度も呼ばれる原子関数である.これらは通常プログラムのForループの中の数行である.次の例は2つのベクトルを足し合せる.

1つ目のカーネル

CUDAカーネルは入力リストの各要素について計算を行う小さいコードである.この例の1つ目のカーネルは各要素に2を足す.

__global__ void addTwo_kernel(mint * arry, mint len) {
    
    int index = threadIdx.x + blockIdx * blockDim.x;

    if (index >= len) return;

arry[index] += 2;
}

は関数をGPU上で実行するようにコンパイラに指示する関数修飾子である.関数はCから呼び出すことができる.もう一つの関数修飾子はで,他の関数や関数から呼び出せるがCからは呼び出せない関数を表す.

CUDAカーネルは無効の出力を持たなければならない.そのためその結果を得るためにはポインタ入力を私,それらを上書きしなければならない.この例ではを渡し,要素を上書きする(は入力・出力パラメータと考えることができる).

これは指標を取得する.CUDAカーネルは起動グリッドとブロックサイズによって設定される次の変数を提供する.

  • 現在のスレッドの指標.スレッド指標は0との間である.
  • 現在のブロックの指標.ブロック指標は0との間である.
  • ブロックサイズ次元.
    • グリッドサイズ次元.

    これらのパラメータはカーネル起動パラメータ(ブロックとグリッドの次元)に基づき,CUDAによって自動的に設定される.低い次元の計算を起動するとき,より高い次元は自動的に1に設定される.そのため1Dのグリッドを起動すると,は1に設定される.

    1Dの最も一般的な場合では,大域オフセットを求めるのにはを使い,2Dの大域的位置を求めるのにを使う.

スレッドは複数のブロック次元で起動されるので,入力の次元がブロック次元の倍数でない場合は境界を上書きしないようにしなければならない.これはそれを行っている.

これはリストの各要素に適用される関数である.この場合は入力に2を足す.

2つ目のカーネル

2つ目のカーネルは有限差分法(前進差分)を実装する.

__device__ float f(float x) {
    return tanf(x);
}

__global__ void secondKernel(float * diff, float h, mint listSize) {
    mint index = threadIdx.x + blockIdx.x * blockDim.x;
float f_n = f(((float) index) / h);
float f_n1 = f((index + 1.0) / h);
    if( index < listSize) {
        diff[index] = (f_n1 - f_n) / h;
    }
}

これはデバイス関数である.関数は関数から呼び出せるが,Wolfram言語から直接呼び出すことはできない.

浮動小数点入力にTanを適用する.

リストをを返し,2つのスカラ入力を取る関数.

大域スレッドオフセットを得る.

で関数を評価する.は整数なので,切り捨てられないようにするために一つを浮動小数点数にしなければならない.デバイス関数をと呼ぶ.

で関数を評価する.

出力バッファを上書きしないようにする.

前進差分を計算し,結果をに保管する.

次のセクションでは,このCUDAプログラムをWolfram言語にロードする方法を示す.

CUDAカーネルをWolfram言語にロードする

まだCUDALink をロードしていない場合はロードする.

In[1]:=
Click for copyable input

全セクションのコードを文字列に置く.

In[2]:=
Click for copyable input

CUDAFunctionをロードする.第1引数にカーネルコード,第2引数に実行するカーネル名(),第3引数に関数パラメータ,第4引数にブロックサイズを渡す.結果はに保管される.

In[3]:=
Click for copyable input
Out[3]=

56.gif

裏では以下のことが行われている.

  • システムのCUDAが利用できるかどうか確認される.
  • CUDAがコンパイルされて選択されたGPUに最適化されたバイナリファイルにされる.
  • コンパイルされたコードはさらにコンパイルされるのを防ぐためにキャッシュされる.
    • コンパイルされたコードにカーネルが存在するかどうか確認される.

    CUDAFunctionは一旦ロードされたら他のWolfram言語関数と同様用に使える.実行する前に結果を保管するためのバッファが必要である.サイズ1000.のバッファを作成する.

    In[4]:=
    Click for copyable input
    Out[4]=

    を実行する.

    In[5]:=
    Click for copyable input
    Out[5]=

    出力バッファの最初の30要素を取得する.

    In[6]:=
    Click for copyable input
    Out[6]=

    出力バッファをプロットする.

    In[7]:=
    Click for copyable input
    Out[7]=

    バッファはユーザが手動で解放しなければならない.

    In[28]:=
    Click for copyable input

直列的なCからCUDAプログラミングへ

以下に直列CPUバージョンからCUDAバージョンへのプログラムの工程の詳細を示す.

プログラムは半径3の移動平均を実装し,隣接画素に基づく配列の平均値を計算する.

実装は直列的なものから始めるのが一般的である.これにより,問題を正しく読み取って並列化の可能な方法を見付けることができるようになる.直列実装は参照のための実装として使えるだけでなく,CUDAがタスクにフィットするかどうかを確定するためにも使える.

この例の場合,直列実装は次のとおりである.

void xAverage_cpu (mint * in, mint * out, mint width, mint height, mint pitch) {
    int xIndex, yIndex, idx;
    mint left, curr, right;
    
    for (yIndex = 0; yIndex < height; yIndex++) {
        for (xIndex = 0; xIndex < width; xIndex++) {
            idx = x + y*pitch;
            
            left = idx > 0 ? in[idx - 1] : in[yIndex*pitch];
            curr = in[idx];
            right = idx < width - 1 ? in[idx + 1] : in[width + yIndex*pitch];
            
            out[x + y*pitch] = (left + curr + right)/3;
        }
    }
}

最適化の可能な範囲を見付けるのはよい考えである.この場合は,要素が右端であるかどうかを確認するifと,左端であるかどうかを確認するifの2つのif文を削除する.

void xAverage_cpu(mint * in, mint * out, mint width, mint height, mint pitch) {
    int xIndex, yIndex, idx;
    
    for (yIndex = 0; yIndex < height; yIndex++) {
        out[yIndex*pitch] = (2*in[yIndex*pitch] + in[yIndex*pitch + 1])/3;
        
        for (xIndex = 1; xIndex < width-1; xIndex++) {
            idx = x + y*pitch;
            out[idx] = (in[idx-1] + in[idx] + in[idx+1])/3;
        }
        out[width - 1 + yIndex*pitch] = (in[width - 2 + yIndex*pitch] +
                                         2*in[width - 1 + yIndex*pitch])/3;
    }
}

もう一つ考えるべきことは隣接画素に依存するコードを避けることである.

void xAverage_cpu(mint * in, mint * out, mint width, mint height, mint pitch) {
    mint accum;
    mint left, curr, right;
    
    for (yIndex = 0; yIndex < height; yIndex++) {
         accum = 2*in[yIndex*pitch] + in[yIndex*pitch + 1]
        out[yIndex*pitch] = accum/3;
        
        accum -= in[yIndex*pitch];
        
        for (xIndex = 0; xIndex < width-1; xIndex++) {
            idx = x + y*pitch;
            
            accum += in[index+1];
            out[idx] = accum/3;
            accum -= in[idx-1];
        }
        accum += in[width - 2 + yIndex*pitch];
        out[width - 1 + yIndex*pitch] = accum/3;
    }
}

最後に最も最適化されたCPU実装に基づくプログラムを書く.

__global__ void xAverage_kernel(mint * in, mint * out, mint width, mint height, mint pitch) {
    int xIndex = blockDim.x*blockIdx.x + threadIdx.x;
    int yIndex = blockDim.y*blockIdx.y + threadIdx.y;
    int index = xIndex + yIndex*pitch;
    
    if (xIndex >= width || yIndex >= height) return;

    if (xIndex > 0) {
        out[index] = (2*in[index] + in[index + 1])/3;
    } else if (xIndex < width) {
        out[index] = (in[index - 1] + in[index] + in[index + 1])/3;
    } else {
        out[index] = (in[index - 1] + 2*in[index])/3;
    }
}

大域メモリコピーが同じデータについて3回行われることに注目されたい.これは共有メモリに一時的にデータを置くことで避けられる.

__global__ void xAverage_improved_kernel(mint * in, mint * out, mint width, mint height, mint pitch) {
    extern __shared__ smem[];
    
    int tx = threadIdx.x, dx = blockDim.x;
    int xIndex = dx*blockIdx.x + tx;
    int yIndex = blockDim.y*blockIdx.y + threadIdx.y;
    int index = xIndex + yIndex*pitch;
    
    if (yIndex >= height) return;
    
    smem[threadIdx.x+1] = index < width ? in[index] : in[yIndex*pitch + width - 1];
    
    if (tx == 0) {
        smem[0] = index > 0 ? in[index-1] : in[yIndex * pitch];
        smem[dx+1] = index+bx < width ? in[index+dx] : in[yIndex*pitch + width - 1];
    }
    
    __syncthreads();
    
    if (xIndex < width)
        out[index] = (smem[tx] + smem[tx+1] + smem[tx+2])/3;
}

このCUDAプログラムはCUDAFunctionLoadを使って保存,ロードできる.

CUDAのためのコンパイル

CUDALink はCUDAコードのためのポータブルなコンパイルメカニズムを提供する.これはCCompilersでシステム上で見付かったNVCCコンパイラを登録し,DLLと実行ファイルをコンパイルするNVCCCompilerによって行われる.

CUDALinkNVCCCompilerをロードする前に,システム上で利用できるコンパイラを調べる.

In[1]:=
Click for copyable input
Out[1]=

上記出力は,NVCCCompilerをロードしていない場合のものである.これが当てはまる場合は CUDALink アプリケーションをロードする.

In[2]:=
Click for copyable input

もう一度コンパイラを確認する.

In[3]:=
Click for copyable input
Out[3]=

これでNVCCCompilerが使えるようになった.

テストファイルの例をロードする.

In[4]:=
Click for copyable input
Out[4]=

ファイルの内容が見られる.

コードをコンパイルしてライブラリにする.

In[6]:=
Click for copyable input
Out[6]=

ライブラリは上記の場所に置かれる.

PTXファイルの生成

PTXは別のCUDAカードで実行できるCUDAのバイトコードである.バイトコードはその後オンザフライで(JITメカニズムを使って)コンパイルされる.

簡単なCUDAカーネルを定義する.

In[1]:=
Click for copyable input

実行ファイルの作成時に"CreatePTX"->Trueと設定すると,コンパイル後にPTXになる.

In[2]:=
Click for copyable input
Out[2]=

コンパイルされたコードの最初の500文字を表示する.

In[3]:=
Click for copyable input
Out[3]=

CUBINファイルの生成

CUBINファイルは特定のCUDAアーキテクチャのためにコンパイルされるCUDAバイナリファイルである.例えばCUDAFunctionLoadは入力コードをコンパイルしてCUBINファイルにする.

簡単なCUDA関数を定義する.

In[4]:=
Click for copyable input

実行ファイルの作成時に"CreateCUBIN"->Trueと設定すると,コンパイル後にCUBINになる.

In[5]:=
Click for copyable input
Out[5]=

コンパイルされたコードの最初の100文字を表示する.

In[6]:=
Click for copyable input
Out[6]=

ASCII命令集合を含むPTXファイルとは違い,CUBINファイルはELFバイナリである.

デバッグ情報とレジスタ情報の表示

CUDAコードをデバッグしているときまたは最適化しているときは,コンパイラ出力が見られると便利である.例えばCUDA関数が使用するレジスタ数を求めるのは定番である.

簡単なCUDA関数を定義する.

In[7]:=
Click for copyable input

"Debug"->Trueを設定すると,デバッグ情報を表示し,バイナリを生成するときに冗長にするようにコンパイラに指示する.

In[8]:=
Click for copyable input
Out[8]=

この簡単なカーネルは上で示したように2つのレジスタを使う.

CUDALink 用のカーネルを書く

CUDAプログラミングに詳しいユーザは,CUDALink 用にプログラミングするときに以下の方法を考えるかもしれない.この方法はCUDAコードをよりポータブルで,正しく,デバッグしやすく,効率的にする.

実数型を使う

多くのCUDAカードで倍精度はサポートされていないので,CUDA Cプログラマは浮動小数点数を表すのにfloatを使わなければならない.CUDALink では,CUDAカードで使える最高精度を使う浮動小数点数を定義するのに型が使える.

そのため,以下のようなコードの使用が推奨される.

__device __ Real_t f(Real_t x) {
    return tanf(x);
}

__global __ void secondKernel(Real_t * diff, Real_t h, mint listSize) {
    int index = threadIdx.x + blockIdx.x * blockDim.x;
Real_t f_n = f(index) / h);
Real_t f_n1 = f((index + 1.0) / h);
    if( index < listSize) {
        diff[index] = (f_n1 - f_n) / h;
    }
}

以下のようなコードは推奨されない.

__device __ float f(float x) {
    return tanf(x);
}

__global __ void secondKernel(float * diff, float h, mint listSize) {
    int index = threadIdx.x + blockIdx.x * blockDim.x;
float f_n = f(((double) index) / h);
float f_n1 = f((index + 1.0) / h);
    if( index < listSize) {
        diff[index] = (f_n1 - f_n) / h;
    }
}

これにより能力を最大限に引き出し,利用できる最高精度が使えるようになる.

最適化

ループパラメータを定数にすると,コンパイラはループを最適化できることがある.次の例は,コンパイラがさらにコード最適化を行うことができるため,チャンネルを渡すものよりも少し最適化される.

__global __ void colorNegate(unsigned char * img, mint width, mint height) {
    int xIndex = threadIdx.x + blockIdx.x * blockDim.x;
    int yIndex = threadIdx.y + blockIdx.y * blockDim.y;
    int index = (xIndex + yIndex*width)*CHANNELS;
if (xIndex < width && yIndex < height) {
#pragma unroll
        for (mint ii = 0; ii < CHANNELS; ii++)
            img[index + ii] = 255 - img[index + ii];
    }
}

コードの中でを定数にするのではなく,CUDAFunctionLoadを使ってそれをとして渡すことができる.

CUDAFunctionLoad[...,"Defines"{"CHANNELS"3}]

オフラインコンパイル

ソースコードをCUDAFunctionLoadに渡すと,コードは裏でコンパイルされる.毎回のパフォーマンスヒットを避けるために,ユーザはNVCCCompilerCreateExecutableを使ってCUDAコードをコンパイルしてバイナリにし,CUDAバイナリからCUDAFunctionをロードすることができる.

OpenCLをCUDAにポートする

CUDALinkOpenCLLink は両方ともCUDAとOpenCLの潜在的なメモリ管理をすべて扱うので,ユーザはカーネルコードのみに集中ればよい.このセクションではCUDAからOpenCLへの変換について述べる.ユーザは関数の開発に OpenCLLink を使ったことが想定されている.

OpenCLプログラムをCUDAにポートするときに必要なカーネルコードの一般的な変化を以下の表にまとめる.

入力画像を取り,色を反転する簡単なOpenCL関数である.

In[1]:=
Click for copyable input

OpenCL関数を開始するには,次の式を実行しなければならない.

In[2]:=
Click for copyable input
Out[5]=

CUDAにポートするために,次のものが変更される.

  • に変更された.
  • に変更された.
    • に変更された.についても同じである.

    多くの場合,Wolfram言語におけるOpenCLからCUDAへ4のポートの際に必要な変更は上記のものだけである.OpenCLまたはCUDAのプログラミングガイドに他の一対一の関数変換がいくつか記載されている.

    次はポートされたCUDAコードである.

    In[6]:=
    Click for copyable input

    次はOpenCL用に行われた,Wolfram言語関数そのままであるが,OpenCLFunctionLoadのロードをCUDAFunctionLoadで置き換える.

    In[7]:=
    Click for copyable input
    Out[10]=

    上で示したメカニズムを検索・置換する方法の代替として,カーネルコードを記号的に生成し,それをWolfram言語で操作するというものがある.以下のセクションでその方法を示す.

記号的なコードの生成

Wolfram言語は,Cコードの階層的な表示をWolfram言語独自の言語として見ることを可能にするSymbolicCをサポートする.これにより,Wolfram言語はCコードの作成,操作,最適化に適したものとなる.

この機能に加え,CUDAカーネルコードをいくつかの別々の目的のために生成することができるため,よりよいポータブル性,より少ないプラットフォーム依存性,よりよいコード最適化等が可能となる.

SymbolicCを使うことの利点としては,Cコードが確実に文法エラーを含んでいないようにできることや,SymbolicCを使ってCUDAプログラムを生成し,CUDAFunctionLoadを使ってそれらを実行できること,そしてCUDAからOpenCLへの(またはその反対の)ポートが簡単になるということが挙げられる.

このセクションは CUDALink の記号コード生成機能を使って「RGB」から「HSV」,および「RGB」から「HSB」への色変換を書く.この変換は画像・映像処理において非常に一般的である.

このチュートリアルでは,まず最初に CUDALink アプリケーションをロードしなければならない.

In[1]:=
Click for copyable input

はじめに

CUDALink には,SymbolicCを使ってカーネルを書く手助けとなる記号ツールが含まれている

SymbolicCUDAFunctionCUDA関数の記号表現
SymbolicCUDABlockIndexCUDAのブロックインデックス呼出しの記号表現
SymbolicCUDAThreadIndexCUDAのスレッドインデックス呼出しの記号表現
SymbolicCUDABlockDimensionCUDAのブロック次元呼出しの記号表現
SymbolicCUDACalculateKernelIndexCUDAのインデックス計算の記号表現
SymbolicCUDADeclareIndexBlockCUDAのインデックス宣言の記号表現

CUDAプログラムの記号表現

次は記号的に書かれたカーネルの例である.

In[2]:=
Click for copyable input
Out[2]=

ToCCodeStringで生成されたCコードを表示する.

In[3]:=
Click for copyable input
Out[3]=

コードを記号的に書くもう一つの理由は,に変更するだけで,上記のカーネルをOpenCL形式に書き換えることができるというものである.

In[4]:=
Click for copyable input
In[5]:=
Click for copyable input
Out[5]=

ToCCodeStringを使って再びCコードを表示する.

In[6]:=
Click for copyable input
Out[6]=

簡単なカーネル

CUDALink には上述のようなよくあるGPUカーネルプログラミングタスクを行うユーティリティ関数がある.それらを使って最初のカーネル関数を書いてみる.

まず,CUDALink アプリケーションをロードする.

In[1]:=
Click for copyable input

最初の関数は整数配列を取り,その全要素を0に設定する.まず関数のプロトタイプを定義する必要がある.関数はで,入力リストとリストサイズを取る.以下のようにユーティリティ関数を使う.

In[2]:=
Click for copyable input
Out[2]=

ここではこの例を1徐々に構築するため,また,上記を毎回書くことを避けるため,この例のためのヘルパー関数を定義する.

In[3]:=
Click for copyable input

それを検証する.

In[4]:=
Click for copyable input
Out[4]=

インデックスの取得

SymbolicCUDADeclareIndexBlockを使い,スレッドインデックス,ブロックインデックス,ブロック次元に基づいてリスト要素のインデックスを求める.

In[5]:=
Click for copyable input
Out[5]=

本体の宣言

要素の数より多いスレッドを起動することができるので,関数本体をif文の中に置く必要がある.これを行わないと,プログラムが予測不可能となる可能性がある.

In[6]:=
Click for copyable input
Out[6]=

これでカーネル本体が定義できる.本体は簡単なもので,リスト内の各要素を0に設定する.

In[7]:=
Click for copyable input
Out[7]=

最初のカーネルの検証

CUDALink をまだインポートしていない場合はインポートする.

In[1]:=
Click for copyable input

入力を定義する.

In[2]:=
Click for copyable input

ソースコードを生成する.

In[4]:=
Click for copyable input
Out[4]=

関数をロードする.

In[5]:=
Click for copyable input
Out[5]=

コードを実行する.

In[6]:=
Click for copyable input
Out[6]=

高度なカーネル

このセクションではRGBからHSV,RGBからHSBへの変換器を書く.これは画像・映像処理において一般的な変換である.

ヘルパー関数

SymbolicCはCの構文のみを含んでいる.コードをよりWolfram言語風にするために,以下を定義する

In[7]:=
Click for copyable input

を使って式を書き直す.

In[9]:=
Click for copyable input
Out[9]=

上の式をCコードに変換する.

In[10]:=
Click for copyable input
Out[10]=

これを使ってFileNameJoin[{$CUDALinkPath,"SupportFiles","colorConvert.cu"}]のコードを再実装する.

色の変換

問題を部分要素に分割する.まずRGBからHueに変換する関数を定義する.この関数はRGBからHSV,RGBからHSBに共通である.

以下でRGBからHに変換する関数を書く.

In[11]:=
Click for copyable input

式をCに変換する.

In[12]:=
Click for copyable input
Out[12]=

HSBの場合の彩度を書く.

In[13]:=
Click for copyable input
Out[13]=

明るさを求める式を定義する.

In[14]:=
Click for copyable input
Out[14]=

同じことをについても行う.

In[15]:=
Click for copyable input
Out[15]=
In[16]:=
Click for copyable input
Out[16]=

が何を表すのかを定義する必要がある.

In[17]:=
Click for copyable input
In[18]:=
Click for copyable input
In[19]:=
Click for copyable input
In[20]:=
Click for copyable input

はすべてをまとめて次のように定義される.

In[21]:=
Click for copyable input
Out[21]=

同様には次のように定義される.

In[22]:=
Click for copyable input
Out[22]=

カーネル関数を生成するためにWolfram言語の関数型機能を利用する.ヘルパー関数を定義する.

In[27]:=
Click for copyable input

上記関数を使い,RGBからHSBへの操作が単精度演算を使うように定義する.

In[28]:=
Click for copyable input
Out[28]=

倍精度のためのコードを生成する.

In[29]:=
Click for copyable input
Out[29]=

RGBからHSVへの操作についても同様にする.

In[30]:=
Click for copyable input
Out[30]=

プログラムの実行

上記コードはCUDAFunctionLoadを使って実行できる.まず単精度でソースを生成する.

In[27]:=
Click for copyable input

GPU上でコードを実行する前に,必要なパラメータを定義する必要がある.以下は入出力画像を定義する.HSVとHSBは実数値なので,出力もReal型であることに注意.

In[28]:=
Click for copyable input

関数をWolfram言語にロードする.

In[29]:=
Click for copyable input
Out[29]=

関数を実行する.

In[30]:=
Click for copyable input

結果を表示する.

In[31]:=
Click for copyable input
Out[31]=