単体型GPUへのDMA転送コストを考える

PostgreSQLの検索処理(特にスキャン周り)をGPUにオフロードするにあたって、できるだけCPU処理の負荷を軽減したい。というのも、PostgreSQLは基本シングルスレッドなので、GPUがバリバリ並列計算しようとしてもデバイスへのデータ供給が追い付かなければ計算機を遊ばせてしまう事になる。

大雑把に言うと、PG-Stromは以下のような構造を持ち、PostgreSQLバックエンドがデータベースファイルから読み込んだデータを、共有メモリを介してOpenCL Serverが見える所に置いてあげる。OpenCL ServerはデータをDMA転送してGPU Kernel関数を起動、計算結果をまたPostgreSQLバックエンドに戻してやる。
f:id:kaigai:20130825174718p:plain

もう少し細かく仕事を分割すると、以下の通り。
f:id:kaigai:20130825174734p:plain

  1. ストレージからデータをロード(共有バッファに載ってなければ)
  2. 共有バッファからタプルを取り出す。この時、Snapshotとの比較でタプルの可視不可視チェックも。
  3. タプルを展開し、長大な配列としてデータを並べ替える。元々カラム指向なデータ構造で持っているので、配列への展開は"すごく重い"という訳ではない。
  4. DMA転送でこの配列をGPUデバイスへコピー。
  5. 転送されたデータを使って、GPU側で計算を実行。

現状の実装だと、(1)~(3)までがPostgreSQLバックエンドの仕事、(4)がOpenCL Serverで、(5)がGPUデバイスとなる。

ただ、これだとCPU側での処理がかさんでしまうので、理想としては以下のようにPostgreSQLの共有バッファから直接GPUデバイスにDMA転送、そこでタプルからのデータ取り出しを行えれば、CPU負荷は下がる事になる。
f:id:kaigai:20130825181145p:plain

しかし、問題が2つ。

  1. タプルの可視性判定はIFブロックの鬼なのでGPUで実行したくない。その上、不可視なタプルをGPUに転送するコストは完全に無駄
  2. PostgreSQLの共有バッファのブロック長は8KB(設定により最大32KB)なので、細切れのDMAを大量に発行する必要が出てくる。

後者に関しては、簡単に性能を計測できるのでやってみた。

128MBのブロックを1回のDMAで転送した場合。理論上、これが最も性能の出るパターン。

[kaigai@iwashi gpuinfo]$ ./gpudma -m async
DMA send/recv test result
device:         GeForce GT 640
size:           128MB
chunks:         128MB x 1
ntrials:        100
total_size:     12800MB
time:           4.18s
speed:          3063.97MB/s
mode:           async

一方、128MBのブロックを8KBx16384個のチャンクに分けて転送した場合だと、転送性能は1/3程度に。うーん、これはちょっとヨロシクナイ…。

[kaigai@iwashi gpuinfo]$ ./gpudma -m async -c 8
DMA send/recv test result
device:         GeForce GT 640
size:           128MB
chunks:         8KB x 16384
ntrials:        100
total_size:     12800MB
time:           12.36s
speed:          1035.97MB/s
mode:           async

当然、ブロック長が長くなればなるほどDMA発行の手間が減るので性能的には有利。PostgreSQLのコンフィグで変更可能な32KB相当のDMA単位にすると、本来の性能の85%程度は出てくれる。それでも結構痛いが。

[kaigai@iwashi gpuinfo]$ ./gpudma -m async -c 32
DMA send/recv test result
device:         GeForce GT 640
size:           128MB
chunks:         32KB x 4096
ntrials:        100
total_size:     12800MB
time:           5.06s
speed:          2531.70MB/s
mode:           async

なかなか悩ましいところではあるが、8KBのチャンクを直接GPUにDMA転送するコストの高さを考えれば、CPU側である程度まとまった大きさに集約してからガツンとDMAを発行した方が効率はよさそう。その一方で、シングルスレッドのPostgreSQLバックエンドを酷使するのも考え物なので、ベストミックスとしては、マルチスレッドで動作可能なOpenCL Serverにタプルの取り出しとDMAバッファへの展開を行わせても良さそう。
あと、現状ではDMAバッファへの展開時に圧縮データの展開を行っているけども、CPUの処理負荷を下げるだけでなく、PCI-Eの帯域を節約するという観点からも、GPU側で圧縮データの伸長を行った方が良いのかもしれない。