実行環境、コンパイル済みファイルのダウンロード、配置
今日はPostgreSQLがポータブル版なので、起動する前に作業します。起動中でも可能かもしれませんが念のため。ファイルの配置は一昨日、昨日と全く同じで、PostgreSQL Online Journalの記事 ↓ からZIPをダウンロードし、中のbin、libおよびshareフォルダをPostgreSQLのプログラムフォルダにコピーするだけ。
↓ ZIPとコピー先フォルダ。bin内の2ファイル(libgcc_s_sjlj-1.dll、libstdc++-6.dll)が既存なのも一昨日・昨日と同じで(三つ目・四つ目の画像)、同様に上書きしました。
PostgreSQL起動、データベースへインストール
ファイルコピー後、PostgreSQL Portableのバッチファイルを起動し(自作版は5月3日の記事を参照)psqlで接続。後は一昨日・昨日と全く同じで、最初にテスト用データベース作成、続いて当該DB上でCREATE EXTENSIONをクエリするだけ。pg_extensionでPL/v8のバージョンを見たら1.4.2でした。(最後のコマンド。昨日の9.3用は1.4.0、一昨日の9.2用は1.3.0だった)
CREATE DATABASE plv8_test
TEMPLATE = template0
LC_COLLATE = 'C'
LC_CTYPE = 'C';
# \c plv8_test
データベース "plv8_test" にユーザ"postgres"として接続しました。
CREATE EXTENSION plv8;
SELECT extname, extversion FROM pg_extension;
extname | extversion
---------+------------
plpgsql | 1.0
plv8 | 1.4.2
簡単なテスト(Typed array)
一昨日・昨日と違うテストとして、PL/v8ならではの高速な配列操作(Typed array)を試してみます。詳細は下記ドキュメントを参照。
日本語の解説記事と同様にして、INT4配列を受け取りFORループで合計して返すストアド ↓ を作成。ループ部分の処理時間をメッセージ出力します。比較用に配列を後ろから見る(lengthプロパティを1回だけ参照)バージョンと、PL/pgSQL版も作りました。
CREATE OR REPLACE FUNCTION int4sum_plv8_1
(ary plv8_int4array)
RETURNS int8 LANGUAGE plv8 IMMUTABLE STRICT AS $$
var sum = 0,
start = (new Date).getTime();
for (var i = 0; i < ary.length; i++) {
sum += ary[i];
}
plv8.elog(INFO, ((new Date).getTime() - start) + ' ms');
return sum;
$$;
CREATE OR REPLACE FUNCTION int4sum_plv8_2
(ary plv8_int4array)
RETURNS int8 LANGUAGE plv8 IMMUTABLE STRICT AS $$
var sum = 0,
start = (new Date).getTime();
for (var i = ary.length - 1; 0 <= i; i--) {
sum += ary[i];
}
plv8.elog(INFO, ((new Date).getTime() - start) + ' ms');
return sum;
$$;
CREATE OR REPLACE FUNCTION int4sum_plpgsql
(ary int4[])
RETURNS int8 LANGUAGE plpgsql IMMUTABLE STRICT AS $$
DECLARE
sum int8 = 0;
ts time = clock_timestamp();
val int4;
BEGIN
FOR val IN SELECT unnest(ary) LOOP
sum = sum + val;
END LOOP;
RAISE INFO '% ms',
extract(milliseconds FROM clock_timestamp() - ts);
RETURN sum;
END;
$$;
処理時間を測る時、PL/pgSQLではnow()でなくclock_timestamp()を使います。now()はトランザクション中で不変(開始時刻のまま)なので。PL/v8の方は、普通にnew Dateを生成すればその時刻になりました。確認に使った、スリープを挿入した無名コードブロックが ↓ これ。setTimeout()はwindowオブジェクトのメソッドなので使えず、代わりにpg_sleepを呼び出し。むしろこの方が簡単だったりして。
DO LANGUAGE plv8 $$
plv8.elog(INFO, new Date);
plv8.execute('SELECT pg_sleep(1)');
plv8.elog(INFO, new Date);
$$;
INFO: Thu Sep 24 2015 19:03:05 GMT+0900 (東京 (標準時))
INFO: Thu Sep 24 2015 19:03:06 GMT+0900 (東京 (標準時))
DO
時間: 1002.159 ms
テスト用関数に渡す配列(1から100万)をテーブルに保存して準備完了。以下、PCはCore i5-4300Mのノートパソコン。試しに普通のSELECT文で集約してみると約100ミリ秒でした。
CREATE TABLE test_int4array AS
SELECT array_agg(gs) AS ary
FROM cast(pow(10, 6) AS int) AS n,
generate_series(1, n) AS gs;
SELECT array_length(ary, 1)
FROM test_int4array;
array_length
--------------
1000000
(1 行)
\timing
SELECT sum(val) FROM (
SELECT unnest(ary) AS val
FROM test_int4array
) foo;
sum
--------------
500000500000
(1 行)
時間: 107.397 ms
↓ 本題のテスト結果。PL/pgSQLストアドでは400ミリ秒余と更に遅い一方、PL/v8はミリ秒単位では測れないほど高速。配列の長さの参照回数で2通り作ったけど、今回は意味なかったです。
-- PL/pgSQL
SELECT int4sum_plpgsql(ary)
FROM test_int4array;
INFO: 437 ms
int4sum_plpgsql
-----------------
500000500000
(1 行)
時間: 448.173 ms
-- PL/v8 (1)
SELECT int4sum_plv8_1(ary)
FROM test_int4array;
INFO: 0 ms
int4sum_plv8_1
----------------
500000500000
(1 行)
時間: 4.703 ms
-- PL/v8 (2)
SELECT int4sum_plv8_2(ary)
FROM test_int4array;
INFO: 0 ms
int4sum_plv8_2
----------------
500000500000
(1 行)
時間: 4.784 ms
と言うわけでPL/v8のTyped arrayは非常に使えそうな予感。PostgreSQLの配列型だけでなくbytea型も変換できるらしいので、そのうちテストしたいです。
ところで、R 3.1.2で同じ1~100万のベクトルを単純にsum()したら ↓ オーバーフローしました。でもRには珍しく「as.numericを使え」と親切なメッセージ。処理時間はPL/v8と同様に一瞬です。
> n = 10^6
> v = seq(n)
> length(v)
[1] 1000000
> sum(v)
[1] NA
Warning message:
In sum(v) : integer overflow - use sum(as.numeric(.))
> sum(as.numeric(v))
[1] 500000500000