24

int 配列列を含むテーブル スキーマと、配列の内容を合計するカスタム集計関数があります。つまり、次のようになります。

CREATE TABLE foo (stuff INT[]);

INSERT INTO foo VALUES ({ 1, 2, 3 });
INSERT INTO foo VALUES ({ 4, 5, 6 });

を返す「合計」関数が必要{ 5, 7, 9 }です。正しく動作する PL/pgSQL のバージョンは次のとおりです。

CREATE OR REPLACE FUNCTION array_add(array1 int[], array2 int[]) RETURNS int[] AS $$
DECLARE
    result int[] := ARRAY[]::integer[];
    l int;
BEGIN
  ---
  --- First check if either input is NULL, and return the other if it is
  ---
  IF array1 IS NULL OR array1 = '{}' THEN
    RETURN array2;
  ELSEIF array2 IS NULL OR array2 = '{}' THEN
    RETURN array1;
  END IF;

  l := array_upper(array2, 1);

  SELECT array_agg(array1[i] + array2[i]) FROM generate_series(1, l) i INTO result;

  RETURN result;
END;
$$ LANGUAGE plpgsql;

と相まって:

CREATE AGGREGATE sum (int[])
(
    sfunc = array_add,
    stype = int[]
);

約 150,000 行のデータ セットの場合、SELECT SUM(stuff)完了するまでに 15 秒以上かかります。

次に、この関数を C で次のように書き直しました。

#include <postgres.h>
#include <fmgr.h>
#include <utils/array.h>

Datum array_add(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(array_add);

/**
 * Returns the sum of two int arrays.
 */
Datum
array_add(PG_FUNCTION_ARGS)
{
  // The formal PostgreSQL array objects:
  ArrayType *array1, *array2;

  // The array element types (should always be INT4OID):
  Oid arrayElementType1, arrayElementType2;

  // The array element type widths (should always be 4):
  int16 arrayElementTypeWidth1, arrayElementTypeWidth2;

  // The array element type "is passed by value" flags (not used, should always be true):
  bool arrayElementTypeByValue1, arrayElementTypeByValue2;

  // The array element type alignment codes (not used):
  char arrayElementTypeAlignmentCode1, arrayElementTypeAlignmentCode2;

  // The array contents, as PostgreSQL "datum" objects:
  Datum *arrayContent1, *arrayContent2;

  // List of "is null" flags for the array contents:
  bool *arrayNullFlags1, *arrayNullFlags2;

  // The size of each array:
  int arrayLength1, arrayLength2;

  Datum* sumContent;
  int i;
  ArrayType* resultArray;


  // Extract the PostgreSQL arrays from the parameters passed to this function call.
  array1 = PG_GETARG_ARRAYTYPE_P(0);
  array2 = PG_GETARG_ARRAYTYPE_P(1);

  // Determine the array element types.
  arrayElementType1 = ARR_ELEMTYPE(array1);
  get_typlenbyvalalign(arrayElementType1, &arrayElementTypeWidth1, &arrayElementTypeByValue1, &arrayElementTypeAlignmentCode1);
  arrayElementType2 = ARR_ELEMTYPE(array2);
  get_typlenbyvalalign(arrayElementType2, &arrayElementTypeWidth2, &arrayElementTypeByValue2, &arrayElementTypeAlignmentCode2);

  // Extract the array contents (as Datum objects).
  deconstruct_array(array1, arrayElementType1, arrayElementTypeWidth1, arrayElementTypeByValue1, arrayElementTypeAlignmentCode1,
&arrayContent1, &arrayNullFlags1, &arrayLength1);
  deconstruct_array(array2, arrayElementType2, arrayElementTypeWidth2, arrayElementTypeByValue2, arrayElementTypeAlignmentCode2,
&arrayContent2, &arrayNullFlags2, &arrayLength2);

  // Create a new array of sum results (as Datum objects).
  sumContent = palloc(sizeof(Datum) * arrayLength1);

  // Generate the sums.
  for (i = 0; i < arrayLength1; i++)
  {
    sumContent[i] = arrayContent1[i] + arrayContent2[i];
  }

  // Wrap the sums in a new PostgreSQL array object.
  resultArray = construct_array(sumContent, arrayLength1, arrayElementType1, arrayElementTypeWidth1, arrayElementTypeByValue1, arrayElementTypeAlignmentCode1);

  // Return the final PostgreSQL array object.
  PG_RETURN_ARRAYTYPE_P(resultArray);
}

このバージョンは完了するまでにわずか 800 ミリ秒かかります。これは....はるかに優れています。

(ここでスタンドアロン拡張に変換: https://github.com/ringerc/scrapcode/tree/master/postgresql/array_sum )

私の質問は、なぜ C バージョンの方がはるかに高速なのですか? 改善を期待していましたが、20倍は少し多いようです。どうしたの?PL/pgSQL で配列にアクセスする際に本質的に遅いものはありますか?

Fedora Core 8 64 ビットで PostgreSQL 9.0.2 を実行しています。マシンは、ハイメモリ クアドラプル エクストララージ EC2 インスタンスです。

4

2 に答える 2

13

PL/pgSQL は、SQL 要素のサーバー側の接着剤として優れています。手続き的な要素と多くの割り当ては、その強みではありません。代入、テスト、またはループは比較的高価であり、SQL だけでは達成できないショートカットを取るのに役立つ場合にのみ保証されます。Cで実装された同じロジックは常に高速になりますが、それをよく知っているようです...

通常、純粋な SQLソリューションの方が高速です。テスト セットアップで、この単純な同等のソリューションを比較してください。

SELECT array_agg(a + b)
FROM  (
   SELECT unnest('{1, 2, 3 }'::int[]) AS a
        , unnest('{4, 5, 6 }'::int[]) AS b
   ) x;

これを単純な SQL 関数にラップできます。または、最高のパフォーマンスを得るには、大きなクエリに直接統合します

SELECT tbl_id, array_agg(a + b)
FROM  (
   SELECT tbl_id
        , unnest(array1) AS a
        , unnest(array2) AS b
   FROM   tbl
   ORDER  BY tbl_id
   ) x
GROUP  BY tbl_id;

SELECTセットを返す関数は、返される行の数が同じ場合にのみ並列で動作します。つまり、同じ長さの配列に対してのみ機能します。この動作は最終的に Postgres 10 でサニタイズされました。以下を参照してください。

通常、最新バージョンの Postgres でテストするのが最善です。少なくとも最新のポイント リリース (執筆時点では 9.0.15) に更新してください。パフォーマンスの大きな違いの説明の一部かもしれません。

ポストグル 9.4

現在、並行してネストを解除するためのよりクリーンなソリューションがあります。

于 2013-06-07T23:15:37.613 に答える