sql >> Database >  >> RDS >> PostgreSQL

Waarom is PostgreSQL-arraytoegang zo veel sneller in C dan in PL/pgSQL?

Waarom?

waarom is de C-versie zo veel sneller?

Een PostgreSQL-array is zelf een behoorlijk inefficiënte gegevensstructuur. Het kan elke . bevatten gegevenstype en het kan multidimensionaal zijn, dus veel optimalisaties zijn gewoon niet mogelijk. Zoals je hebt gezien is het echter mogelijk om veel sneller met dezelfde array te werken in C.

Dat komt omdat arraytoegang in C veel van het herhaalde werk dat gepaard gaat met PL/PgSQL-arraytoegang kan voorkomen. Kijk maar eens naar src/backend/utils/adt/arrayfuncs.c , array_ref . Kijk nu hoe het wordt aangeroepen vanuit src/backend/executor/execQual.c in ExecEvalArrayRef . Welke wordt uitgevoerd voor elke individuele array-toegang van PL/PgSQL, zoals je kunt zien door gdb toe te voegen aan de pid gevonden van select pg_backend_pid() , een breekpunt instellen op ExecEvalArrayRef , doorgaan en uw functie uitvoeren.

Wat nog belangrijker is, is dat in PL/PgSQL elke instructie die u uitvoert, door de machine van de query-uitvoerder wordt geleid. Dit maakt kleine, goedkope verklaringen vrij traag, zelfs als rekening wordt gehouden met het feit dat ze vooraf zijn voorbereid. Iets als:

a := b + c

wordt eigenlijk uitgevoerd door PL/PgSQL meer als:

SELECT b + c INTO a;

Je kunt dit zien als je debug-niveaus hoog genoeg zet, een debugger toevoegt en op een geschikt punt breekt, of de auto_explain gebruikt module met geneste verklaringsanalyse. Om je een idee te geven van hoeveel overhead dit met zich meebrengt als je veel kleine eenvoudige instructies uitvoert (zoals array-toegangen), bekijk je deze voorbeeldbacktrace en mijn aantekeningen erover.

Er is ook een aanzienlijke opstartoverhead aan elke aanroep van de PL/PgSQL-functie. Het is niet enorm, maar het is genoeg om op te tellen als het als aggregaat wordt gebruikt.

Een snellere nadering in C

In jouw geval zou ik het waarschijnlijk in C doen, zoals jij hebt gedaan, maar ik zou het kopiëren van de array vermijden als ik het als een aggregaat aanroep. U kunt controleren of het in geaggregeerde context wordt aangeroepen:

if (AggCheckCallContext(fcinfo, NULL))

en als dat zo is, gebruik dan de oorspronkelijke waarde als een veranderlijke tijdelijke aanduiding, wijzig deze en retourneer deze in plaats van een nieuwe toe te wijzen. Ik zal binnenkort een demo schrijven om te verifiëren dat dit mogelijk is met arrays... (update) of niet zo snel, ik was vergeten hoe absoluut verschrikkelijk het is om met PostgreSQL-arrays in C te werken. Hier gaan we:

// 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);
}

en voeg dit toe aan 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);

(juist zou je intarray--1.1.sql maken en intarray--1.0--1.1.sql en update intarray.control . Dit is slechts een snelle hack.)

Gebruik:

make USE_PGXS=1
make USE_PGXS=1 install

te compileren en te installeren.

Nu DROP EXTENSION intarray; (als je het al hebt) en CREATE EXTENSION intarray; .

Je hebt nu de verzamelfunctie sum_intarray_cols beschikbaar voor u (zoals uw sum(int4[]) , evenals de twee-operand add_intarray_cols (zoals uw array_add ).

Door te specialiseren in integer arrays verdwijnt een heleboel complexiteit. Een hoop kopiëren wordt vermeden in het geaggregeerde geval, omdat we de "state" -array (het eerste argument) veilig ter plaatse kunnen wijzigen. Om de zaken consistent te houden, krijgen we in het geval van niet-geaggregeerde aanroep een kopie van het eerste argument zodat we er nog steeds mee kunnen werken en het terug kunnen sturen.

Deze benadering kan worden gegeneraliseerd om elk gegevenstype te ondersteunen door de fmgr-cache te gebruiken om de add-functie op te zoeken voor het type/de typen van belang, enz. Ik ben daar niet echt in geïnteresseerd, dus als je het nodig hebt (zeg, om kolommen van NUMERIC . op te tellen arrays) dan ... veel plezier.

Evenzo, als u ongelijke arraylengtes moet verwerken, kunt u waarschijnlijk uit het bovenstaande bepalen wat u moet doen.



  1. Postgresql enum wat zijn de voor- en nadelen?

  2. Databaseontwerp voor gebruikersinstellingen

  3. Wat is de beste manier om mediabestanden op te slaan in een database?

  4. Wat is het equivalent van bigint in C#?