Proč?
Proč je verze C o tolik rychlejší?
Pole PostgreSQL je samo o sobě dost neefektivní datová struktura. Může obsahovat libovolné datový typ a může být vícerozměrný, takže spousta optimalizací prostě není možná. Jak jste však viděli, v C je možné pracovat se stejným polem mnohem rychleji.
Je to proto, že přístup k poli v C se může vyhnout mnoha opakované práci spojené s přístupem k poli PL/PgSQL. Stačí se podívat na src/backend/utils/adt/arrayfuncs.c
, array_ref
. Nyní se podívejte, jak se vyvolává z src/backend/executor/execQual.c
v ExecEvalArrayRef
. Který běží pro každý jednotlivý přístup k poli z PL/PgSQL, jak můžete vidět připojením gdb k pid nalezenému z select pg_backend_pid()
, nastavením bodu přerušení na ExecEvalArrayRef
, pokračování a spuštění vaší funkce.
Ještě důležitější je, že v PL/PgSQL je každý příkaz, který provedete, spuštěn prostřednictvím stroje pro provádění dotazů. Díky tomu jsou malé, levné příkazy poměrně pomalé, i když vezmeme v úvahu skutečnost, že jsou předem připravené. Něco jako:
a := b + c
je ve skutečnosti prováděno PL/PgSQL spíše jako:
SELECT b + c INTO a;
Můžete to pozorovat, pokud nastavíte úrovně ladění dostatečně vysoko, připojíte ladicí program a přerušíte se na vhodném místě nebo použijete auto_explain
modul s vnořenou analýzou příkazů. Abyste měli představu o tom, jakou režii to přináší, když spouštíte spoustu malých jednoduchých příkazů (jako jsou přístupy k poli), podívejte se na tento příklad backtrace a moje poznámky k němu.
Je zde také značná rozjezdová režie ke každému vyvolání funkce PL/PgSQL. Není to obrovské, ale stačí to sečíst, když se to používá jako agregát.
Rychlejší přístup v C
Ve vašem případě bych to pravděpodobně udělal v C, jako jste to udělal vy, ale vyhnul bych se kopírování pole, když je voláno jako agregát. Můžete zkontrolovat, zda je vyvolána v agregovaném kontextu:
if (AggCheckCallContext(fcinfo, NULL))
a pokud ano, použijte původní hodnotu jako proměnlivý zástupný symbol, upravte ji a poté ji vraťte namísto přidělení nové. Za chvíli napíšu demo, abych si ověřil, že je to možné s poli... (aktualizace) nebo ne tak brzy, zapomněl jsem, jak je práce s poli PostgreSQL v C naprosto hrozná. Tady to je:
// 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);
}
a připojte toto k 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);
(správněji byste vytvořili intarray--1.1.sql
a intarray--1.0--1.1.sql
a aktualizujte intarray.control
. Toto je jen rychlý hack.)
Použijte:
make USE_PGXS=1
make USE_PGXS=1 install
zkompilovat a nainstalovat.
Nyní DROP EXTENSION intarray;
(pokud jej již máte) a CREATE EXTENSION intarray;
.
Nyní budete mít agregační funkci sum_intarray_cols
máte k dispozici (jako váš sum(int4[])
a také dvouoperand add_intarray_cols
(jako vaše array_add
).
Specializací na celočíselná pole odpadá celá řada složitostí. V agregovaném případě se vyhneme spoustě kopírování, protože můžeme bezpečně upravit pole "stav" (první argument) na místě. Aby byly věci konzistentní, v případě neagregovaného vyvolání dostaneme kopii prvního argumentu, takže s ním můžeme stále pracovat na místě a vrátit jej.
Tento přístup by se dal zobecnit tak, aby podporoval jakýkoli typ dat pomocí mezipaměti fmgr k vyhledání funkce přidání pro typy, které vás zajímají, atd. Nemám o to žádný zvláštní zájem, takže pokud to potřebujete (řekněme, k součtu sloupců NUMERIC
pole) pak ... bavte se.
Podobně, pokud potřebujete zvládnout různé délky polí, pravděpodobně můžete zjistit, co dělat z výše uvedeného.