最近、方々で『GPUイイよ!GPU!』と言って回っている訳ですが、今現在、PG-Stromの開発がどんなもんじゃいというのをまとめておこうと思います。
振り返ってみると、2012年1月、最初にPG-Stromのプロトタイプを作ってみた時は、まさにPG-Strom管理下の外部テーブル(Foreign Scan)に対して非常に複雑な条件句を与えた場合、という非常に限られた条件下でGPUオフロードが効果を発揮するものでした。しかも、プログラミングに使ったのはCUDAなので、NVIDIA専用だったし。
その後、紆余曲折を経て
- FDW(Foreign Data Wrapper)を使うのはやめ、新たに Custom-Plan APIを設計した。
- CUDAを使うのはやめ、OpenCLライブラリを使用するようにした。
- 列指向データ構造を捨て、PostgreSQLに合った行指向データを扱えるように
- 全件スキャンだけでなく、表結合(Join)と集約演算(Aggregate)に対応した
アーキテクチャ的にはこんな感じ。
Custom-Plan APIを介して、全件スキャン(GpuScan)、表結合(GpuHashJoin)、集約演算(GpuPreAgg)を実装するCustom-Nodeがプランナ・エグゼキュータから呼び出される。その裏でOpenCLデバイスを管理するバックグランドワーカが立ち上がり、これらのCustom-Nodeから自動生成されたGPUコードと処理すべきデータの組がメッセージキューを介して送信されるので、OpenCL Serverはこれを適宜ディスパッチしてGPUで処理する事ができる。
ま、以前の実装だとかなり適用可能なワークロードが限られていて、こんな感じの手厳しいコメントを頂くこともあったが、特にJoinをGPUで高速化できるってのは結構インパクトがあると思う。
GPUで速くなるSQLってかなり限定的だよねぇ、多分。 ... フルパワーで加速するGPUの咆哮を聞け!最先端GPUでPostgreSQLを20倍高速化!? http://t.co/cBZpWoGKfg #blomaga
— idiotton (@idiotton) June 16, 2014
以下の図はCPUによるHash-Joinのロジックと、PG-StromのGpuHashJoinを比較したもの。
CPUであれば、①Hash表を作り ②Outer側から一行取り出してHash表を探索 ③Projectionを行って左右の表の内容をマージした行を作り、次の処理へ回す。この②と③のプロセスをひたすらループする事になるワケだ。
一方、GpuHashJoinの場合、①Hash表を作る 部分は同じだけども、②と③の部分をGPUのコアに並列実行させる。つまり、数百~数千コア分の並列度が期待できる訳である。
以下のグラフは、2億件 × 10万件 × 10万件 × .... と、結合すべき表の数を増やしていった時の処理時間を、標準PostgreSQL(9.5devel)と、PG-Strom有効化の場合で比較したもの。
一番得意なワークロードで比較しているので、多少、恣意的な部分はあるにせよ、3個のテーブルを結合する時の処理時間で11倍、9個のテーブルを結合する時の処理時間で28倍もの違いが出ている。
ま、この辺は実際に世の中で使われているワークロードと、GPUにオフロードできる部分をどういう風に近づけるかで、コストパフォーマンスが決まってくるんだろうけども。
こんな感じでプロファイルを取ってみると、どの処理にどれくらい時間を要しているかが判る。
(この例では 2000万件 × 4万件 × 4万件 を結合してみた)
postgres=# SET pg_strom.perfmon = on; SET postgres=# EXPLAIN (ANALYZE, COSTS OFF) SELECT * FROM t0 NATURAL JOIN t1 NATURAL JOIN t2; QUERY PLAN --------------------------------------------------------------------------------- Custom (GpuHashJoin) (actual time=105.020..4153.333 rows=20000000 loops=1) hash clause 1: (t0.aid = t1.aid) hash clause 2: (t0.bid = t2.bid) Bulkload: On number of requests: 145 total time for inner load: 29.73ms total time for outer load: 825.77ms total time to materialize: 1359.44ms average time in send-mq: 62us average time in recv-mq: 1372us max time to build kernel: 13us DMA send: 5759.42MB/sec, len: 4459.39MB, time: 774.28ms, count: 722 DMA recv: 5661.55MB/sec, len: 2182.02MB, time: 385.41ms, count: 290 proj kernel exec: total: 197.28ms, avg: 1360us, count: 145 main kernel exec: total: 250.03ms, avg: 1724us, count: 145 -> Custom (GpuScan) on t0 (actual time=6.226..825.598 rows=20000000 loops=1) number of requests: 145 total time to load: 787.28ms -> Custom (MultiHash) (actual time=29.717..29.718 rows=80000 loops=1) hash keys: aid Buckets: 46000 Batches: 1 Memory Usage: 99.99% -> Seq Scan on t1 (actual time=0.007..5.204 rows=40000 loops=1) -> Custom (MultiHash) (actual time=16.106..16.106 rows=40000 loops=1) hash keys: bid Buckets: 46000 Batches: 1 Memory Usage: 49.99% -> Seq Scan on t2 (actual time=0.018..6.000 rows=40000 loops=1) Execution time: 5017.388 ms (27 rows)
GPUでの処理時間は、Joinそれ自身(main kernel exec)と結果の生成(proj kernel exec)で計450msくらい。
つまり計算能力のポテンシャルはこれ位はあるので、あとは、いかに効率的にCPU/GPUでデータを受渡しするかという所がこれからのテーマになってくるのかな。
次のエントリで、PG-Stromのデプロイについて紹介します。