(1)WindowsPostgreSQL 9.2(一昨日) (2)WindowsPostgreSQL 9.3(昨日) (4)CentOS 6.6 + PostgreSQL 9.3924日) (5)CentOS 7 + PostgreSQL 9.5 Alpha 1925日) (6)Scientific Linux 7 + PostgreSQL 9.5 Alpha 2926日) (7・終)Mac OS X 10.8.5 + PostgreSQL 9.5 Alpha 1927日)
Contents


実行環境、コンパイル済みファイルのダウンロード、配置



今日はPostgreSQLがポータブル版なので、起動する前に作業します。起動中でも可能かもしれませんが念のため。ファイルの配置は
一昨日昨日と全く同じで、PostgreSQL Online Journalの記事 ↓ からZIPをダウンロードし、中のbin、libおよびshareフォルダをPostgreSQLのプログラムフォルダにコピーするだけ。


ZIPとコピー先フォルダ。bin内の2ファイル(libgcc_s_sjlj-1.dll、libstdc++-6.dll)が既存なのも一昨日・昨日と同じで(三つ目・四つ目の画像)、同様に上書きしました。


PostgreSQL起動、データベースへインストール

ファイルコピー後、PostgreSQL Portableのバッチファイルを起動し(自作版は53日の記事を参照)psqlで接続。後は一昨日昨日と全く同じで、最初にテスト用データベース作成、続いて当該DB上でCREATE EXTENSIONをクエリするだけ。pg_extensionPL/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万)をテーブルに保存して準備完了。以下、PCCore 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/v8Typed arrayは非常に使えそうな予感。PostgreSQLの配列型だけでなくbytea型も変換できるらしいので、そのうちテストしたいです。

ところで、R 3.1.2で同じ1100万のベクトルを単純に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