RubyでGPUを使おう

※この記事はRuby Advent Calendar jp 2011の28日目の記事です。

こんにちは、r7kamuraです。耳を澄ませば2011年の崩れていく音がしますね。今回は年末用にとっておいた残り少ない意識を使って、RubyGPUを使う方法を紹介しようと思います。

GPU?

皆さんGPUはご存知でしょうか。3Dのゲームとかで綺麗なグラフィックを表示してくれるアレです。FF14とかと抱き合わせでハイエンドPC()に付いてたりするアレです。近頃だとノートPCにも搭載されるようになっていて、MacbookPro等にも搭載されています。*2

GPUを販売している会社はNVIDIAAMDの二社が有名です。NVIDIA社の販売しているGPUには主にGeForceという名前が付いていて、AMD社の販売しているGPUには主にRadeonという名前が付いています。MacはこれまでNVIDIA社製のものが搭載されていることが多かったものの、最近になってAMD社製のものが搭載されるようになっています。

GPU

GPUはその並列処理能力の高さから、最近では数値計算などの目的でも応用されるようになってきました。このように汎用計算目的でGPGPUを利用することを、その筋の人はGPGPU*3と呼んでいます。詳しくはWikipediaでググッてください。さて、PCにGPUが繋がっているというだけではそのまま演算資源として利用することはできません。GPUを用いて汎用計算を行うための方法(=環境/フレームワーク)は主に2つあります。

1つはOpenCLというフレームワークを利用すること。OpenCLはApple社が仕様を提案したフレームワークで、これを利用すればNVIDIA社やAMD社のGPU上で動作するプログラムを書く/動かすことができます。去年発売されたAdobeCS5はOpenCLベースで開発されていて、いろんなところでGPGPUの恩恵を受けることができます。
もう1つはCUDAというフレームワークを利用すること。CUDAではOpenCLとは異なりNVIDIA社製のGPUしか扱えないのですが、その分提供されているAPIが豊富な印象があります*4。CUDAだってPhotoshopCS4とかAdobe Premiereの動画エンコードとかで活躍してます。実行環境や提供されるAPIの違いなど、どちらにも良し悪しがありますが、C言語(を少しだけ拡張した言語)を利用してコードを記述するという点で共通しています。やや話が長くなりましたが、次節ではこれらのフレームワークRubyバインディング(Rubyラッパー)を利用してGPUでプログラムを動かす方法を紹介します。

Ruby

RubyGPUを使う方法、つまりRubyGPGPUなプログラムを書いて動かす方法を紹介します。OpenCLとCUDA、どちらにもRubyバインディングが存在しますが、今回はOpenCLのRubyバインディングであるBarracudaを利用する方法を紹介します。最近のMac(OS 10.6以降)ではOpenCLの環境が元からインストールされているので、Barracudaを入れるだけOKです。

$ git clone https://github.com/lsegal/barracuda.git
$ cd barracuda
$ rake install

# 本家のRakefileではsudo権限で入れるようになっています。
# RVM等を利用している場合は、Rakefileの6行目を書き換えて
# sudo を外すなどしましょう
# - 本家 https://github.com/lsegal/barracuda/blob/master/Rakefile#L6
# - 修正 https://gist.github.com/1524778

ではBarracudaを利用した簡単なコードを動かしてみます。
素数の大きな適当な配列を用意して掛けて足すだけの単純な内容です。

#!/usr/bin/env ruby
require "barracuda"

# GPU用の関数を定義(= OpenCL実行プログラムの作成とコンパイル)
program = Barracuda::Program.new <<EOF
__kernel void calculate(
  __global int *a,
  __global int *b,
  __global int *c,
  __global int *d) {

  // 0〜n-1のthreadIDを得る
  int i = get_global_id(0);

  // a = b * c + d
  a[i] = b[i] * c[i] + d[i];
}
EOF

# calculate関数を実行するスレッド数
n = 1_000_000

# calculate関数の引数を用意
a = Array.new(n)
b = Array.new(n) { rand(123456) }
c = Array.new(n) { rand(123456) }
d = Array.new(n) { rand(123456) }

# calculate関数の実行 / Barracuda::Program#calculateが呼べる
result = program.calculate(a, b, c, d, :times => n)

Barracudaでは、上記のように文字列としてOpenCL用のコードを渡します。OpenCLとして妥当なコードならここでコンパイルが成功し、コードの中で定義した関数が実行できるようになります。案外簡単ですね!普通にRubyでやろうとすると逐次実行するのでイテレータで時間を取りますが、GPUを利用すれば単純に並列実行が可能なため実行時間短縮が期待できます。

まとめ

今回はRubyGPUを扱う方法の1つとして、OpenCLを利用するBarracudaを紹介しました。まだまだ発展途中な感じではありますが、意外と簡単に利用することができるため、処理の重い部分だけを局所的にGPUに任せるなどの使い方が期待できますね。明日の記事はDaic_hさんの予定です。