sql >> Databáze >  >> RDS >> PostgreSQL

Proč je přístup k poli PostgreSQL mnohem rychlejší v C než v PL/pgSQL?

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.



  1. Jaký je přesně rozdíl mezi primárním indexem a sekundárním indexem?

  2. Efektivní převod dat mezi UTC a místním (tj. PST) časem v SQL 2005

  3. Aktualizujte hodnotu primárního klíče pomocí entity framework

  4. Výběr hodnot z proměnné tabulky Oracle / pole?