なぜですか?
Cバージョンがこれほど高速なのはなぜですか?
PostgreSQL配列は、それ自体がかなり非効率的なデータ構造です。 任意のを含めることができます データ型であり、多次元である可能性があるため、多くの最適化は不可能です。ただし、これまで見てきたように、Cでは同じ配列をはるかに高速に操作できます。
これは、Cでの配列アクセスにより、PL/PgSQL配列アクセスに伴う多くの繰り返し作業を回避できるためです。 src/backend/utils/adt/arrayfuncs.c
をご覧ください。 、array_ref
。次に、src/backend/executor/execQual.c
からどのように呼び出されるかを見てみましょう。 ExecEvalArrayRef
で 。 個々のアレイアクセスごとに実行されます PL / PgSQLから、select pg_backend_pid()
から見つかったpidにgdbをアタッチすることでわかるように 、ExecEvalArrayRef
にブレークポイントを設定します 、続行し、関数を実行します。
さらに重要なことに、PL / PgSQLでは、実行するすべてのステートメントがクエリエグゼキュータ機構を介して実行されます。これにより、小さくて安価なステートメントは、事前に準備されているという事実を考慮しても、かなり遅くなります。次のようなもの:
a := b + c
実際には、PL/PgSQLによって次のように実行されます。
SELECT b + c INTO a;
これは、デバッグレベルを十分に高くするか、デバッガーを接続して適切なポイントで中断するか、auto_explain
を使用する場合に確認できます。 ネストされたステートメント分析を備えたモジュール。小さな単純なステートメント(配列アクセスなど)を多数実行しているときにこれがどの程度のオーバーヘッドを課すかを知るために、この例のバックトレースとそのメモを見てください。
かなりの起動オーバーヘッドもあります 各PL/PgSQL関数の呼び出しに。それほど大きくはありませんが、骨材として使用する場合は合計するだけで十分です。
Cでのより高速なアプローチ
あなたの場合、あなたが行ったように、私はおそらくCでそれを行いますが、集合体として呼び出されたときに配列をコピーすることは避けます。集約コンテキストで呼び出されているかどうかを確認できます:
if (AggCheckCallContext(fcinfo, NULL))
その場合は、元の値を変更可能なプレースホルダーとして使用し、新しい値を割り当てる代わりに、元の値を変更してから返します。すぐに配列でこれが可能であることを確認するためのデモを作成します...(更新)またはそれほど短くはありませんが、CでPostgreSQL配列を操作することがどれほど恐ろしいことか忘れました。どうぞ:
// append to contrib/intarray/_int_op.c
PG_FUNCTION_INFO_V1(add_intarray_cols);
Datum add_intarray_cols(PG_FUNCTION_ARGS);
Datum
add_intarray_cols(PG_FUNCTION_ARGS)
{
ArrayType *a,
*b;
int i, n;
int *da,
*db;
if (PG_ARGISNULL(1))
ereport(ERROR, (errmsg("Second operand must be non-null")));
b = PG_GETARG_ARRAYTYPE_P(1);
CHECKARRVALID(b);
if (AggCheckCallContext(fcinfo, NULL))
{
// Called in aggregate context...
if (PG_ARGISNULL(0))
// ... for the first time in a run, so the state in the 1st
// argument is null. Create a state-holder array by copying the
// second input array and return it.
PG_RETURN_POINTER(copy_intArrayType(b));
else
// ... for a later invocation in the same run, so we'll modify
// the state array directly.
a = PG_GETARG_ARRAYTYPE_P(0);
}
else
{
// Not in aggregate context
if (PG_ARGISNULL(0))
ereport(ERROR, (errmsg("First operand must be non-null")));
// Copy 'a' for our result. We'll then add 'b' to it.
a = PG_GETARG_ARRAYTYPE_P_COPY(0);
CHECKARRVALID(a);
}
// This requirement could probably be lifted pretty easily:
if (ARR_NDIM(a) != 1 || ARR_NDIM(b) != 1)
ereport(ERROR, (errmsg("One-dimesional arrays are required")));
// ... as could this by assuming the un-even ends are zero, but it'd be a
// little ickier.
n = (ARR_DIMS(a))[0];
if (n != (ARR_DIMS(b))[0])
ereport(ERROR, (errmsg("Arrays are of different lengths")));
da = ARRPTR(a);
db = ARRPTR(b);
for (i = 0; i < n; i++)
{
// Fails to check for integer overflow. You should add that.
*da = *da + *db;
da++;
db++;
}
PG_RETURN_POINTER(a);
}
これをcontrib/intarray/intarray--1.0.sql
に追加します :
CREATE FUNCTION add_intarray_cols(_int4, _int4) RETURNS _int4
AS 'MODULE_PATHNAME'
LANGUAGE C IMMUTABLE;
CREATE AGGREGATE sum_intarray_cols(_int4) (sfunc = add_intarray_cols, stype=_int4);
(より正確には、intarray--1.1.sql
を作成します およびintarray--1.0--1.1.sql
intarray.control
を更新します 。これは簡単なハックです。)
使用:
make USE_PGXS=1
make USE_PGXS=1 install
コンパイルしてインストールします。
次に、DROP EXTENSION intarray;
(すでにお持ちの場合)およびCREATE EXTENSION intarray;
。
これで、集計関数sum_intarray_cols
が作成されます。 利用可能(sum(int4[])
など 、および2つのオペランドのadd_intarray_cols
(array_add
のように 。
整数配列に特化することで、複雑さが解消されます。 「状態」配列(最初の引数)をインプレースで安全に変更できるため、集約の場合は大量のコピーが回避されます。一貫性を保つために、非集計呼び出しの場合は、最初の引数のコピーを取得して、その場で作業して返すことができるようにします。
このアプローチは、fmgrキャッシュを使用して、目的のタイプなどのadd関数を検索することにより、任意のデータタイプをサポートするように一般化できます。私は特に興味がないので、必要に応じて(たとえば、 NUMERIC
の列を合計するには アレイ)次に...楽しんでください。
同様に、異なる配列の長さを処理する必要がある場合は、おそらく上記から何をすべきかを理解することができます。