【BigQuery】UDFでWebAssemblyを試す

Hiroki Kuribayashi
JDSC Tech Blog
Published in
5 min readJan 10, 2020

こんにちは。JDSCの栗林です。

BigQueryを使用されている方であれば、JavaScriptでユーザ定義関数(UDF)を作成できることはご存知かもしれません[1]。JavaScriptはSQLに比べてかなり自由度は高いのですが、実行速度の面ではどうしても不利になってしまいます。

そこでJavaScriptの高速化といえばWebAssembly![2]ということで、BiqQueryのUDFでWebAssemblyを試してみたところ、一定の効果が見られたのでご紹介します。

まずは結果からみてみましょう。100個のデータそれぞれに対して、ループ回数を数える単純な処理を実行したときの実行時間を計測しました。BigQueryの実行時間はバラツキが大きいため、5回測定した中央値を採っています。

JavaScriptではループ回数が10の6乗を超えると極端に実行時間が増加しますが、WebAssemblyは緩やかであることがわかりますね!

また、今回の実験で数秒から数分かかる処理を実行しましたが、BigQueryのスロット消費量はほとんどが1個、タイムアウトが発生するような処理を連続で実行した時も200個程度でした。

100個のデータに対する処理時間の比較

それでは実装方法についてみていきましょう。まずはJavaScriptで実装してみます。CREATE TEMP FUNCTIONを使用してユーザ定義関数を作成しています。内容は単純でN回ループしてその回数を返すだけの処理です。なお、コード中の__N__はループ回数、__M__はデータ数に置き換えてください。このコードをGCPコンソールのBigQueryのクエリエディタに貼り付けると実行することができます。

JavaScript版

今度はWebAssembly版です。ユーザ定義関数を作成するところまでは同じなのですが、関数の中でWebAssemblyのセットアップを行なっています。Uint8ArrayがWebAssemblyのバイトコードで作成方法は後述します。

WebAssembly版

以下はバイトコードの元となるカウントアップ関数です。この関数はC言語で実装しました。WebAssemblyはgoやrust用のコンパイラも存在するので試してみても良いかもしれません。EMSCRIPTEN_KEEPALIVEはC言語の関数をWebAssmblyから呼び出すために必要です。

C言語のカウント処理

コンパイルしてバイトコードを生成します。emccコンパイラ[3]はビルドするのに非常に時間がかかる(数時間待って諦めました)ため、dockerであらかじめビルド済みのイメージを使用しました。これでコンパイルは一瞬で実行できるようになります(初回はdocker imageのダウンロードで数分かかります)。

dockerを実行するとcount.wasmというバイナリファイルが生成されるため、Uint8Arrayにセットできるようにテキストファイル(カンマ区切りの1バイト整数)に変換しています。今回はodコマンドを使用しましたが何を使用しても構いません。このテキストをUint8Arrayのところに貼り付ければ完成です!

WebAssemblyバイトコードの生成

今回は非常に単純な処理なので、コンパイラの最適化によってループ処理が消されてしまっていないか(念のため)確認してみましょう。

逆アセンブル

LISPのS式のようなコードが生成されます。ループ処理が残っているので大丈夫そうですね。

emccでのコンパイル環境を用意するのが少し手間ですし、配列の渡し方などまだ不明点はありますが、BigQueryの超並列計算能力を色々な言語で使用できると楽しそうですね!

--

--