【问题标题】:Slow handling of 2-dimensional arrays in plpgsqlplpgsql中二维数组的缓慢处理
【发布时间】:2013-09-30 08:02:25
【问题描述】:

我正在 PostgreSQL 中编写一个存储过程。该算法应处理double precision 数字的二维数组。

据我调查,Postgres 中的数组操作是通用的且相当繁重。我试图证明的简单示例具有过高的计算成本。

例子:

CREATE OR REPLACE FUNCTION fill_2d_array( rows integer, cols integer) 
  RETURNS integer AS
$BODY$

DECLARE

img double precision[][];

i integer; j integer;
cont integer;

BEGIN

img  := ARRAY( SELECT 0 FROM generate_series(1, filas * columnas) ) ; 
cont:= 0;
For i IN 1..rows LOOP
    For j IN 1..cols LOOP
        img[i * cols + j] := (i * cols + j)::double precision;
        cont := cont + 1;
    END LOOP;
END LOOP;

return cont;
END;
$BODY$
  LANGUAGE plpgsql;

有人可以帮我找到处理二维数组的替代路径或改进方法吗?

【问题讨论】:

  • 我无法破译你想要完成什么......为什么你不能只使用 arrayagg() 而不是循环?
  • filascolumnas 未定义。我想应该是rowscols?并请描述试图做什么。由于您只返回一个整数,因此整个操作似乎毫无意义?

标签: performance postgresql plpgsql dynamic-arrays type-2-dimension


【解决方案1】:

程序函数

基本问题

  • 声明数组变量的维度,如二维数组的float8[][],仅用于文档。考虑此相关答案中的详细信息:
    mapping postgresql text[][] type and Java type

  • 您混淆了一维数组和二维数组。在声明一个二维数组(无效)时,您只能将其设为一维数组。

  • 要初始化一个数组,请使用array_fill():

    img := array_fill(0, ARRAY[rows,cols])
    

    此示例生成一个二维数组 - 与您的错误语句相反,生成一个一维数组:

    <strike>img  := ARRAY( SELECT 0 FROM generate_series(1, rows* cols) );</strike>
  • 显示的数组下标img[i * cols + j] 几乎没有意义。最大值将是您初始化的两倍,从而导致“越界”错误。我想你的意思是img[i][j]

工作版

所有东西放在一起它可以像这样工作:

CREATE OR REPLACE FUNCTION f_array_fill(rows integer, cols integer
                                                    , OUT img float8[][]) AS
$func$
DECLARE
   i  int;
   j  int;
BEGIN

img := array_fill(0, ARRAY[rows,cols]);

FOR i IN 1 .. rows LOOP
    FOR j IN 1 .. cols LOOP
        img[i][j] := (i * cols + j)::float8;
    END LOOP;
END LOOP;

END
$func$ LANGUAGE plpgsql;

呼叫:

SELECT f_array_fill(2,3);

结果:

{{4,5,6},{7,8,9}}

为了使函数有用,返回生成的数组。为此使用OUT parameter

卓越的基于集合的版本

Looping and individual assignments are comparatively slow in plpgsql。正如@Craig 在此相关答案中所解释的那样,数组处理的性能特别差:
Why is PostgreSQL array access so much faster in C than in PL/pgSQL?

我会改用基于集合的操作,数字越大速度越快。

多维数组的聚合函数

要生成多维数组,我们需要一个自定义聚合函数。 array_agg()array constructor 仅生成一维数组。这很简单,正如我们在这个相关答案中得出的那样:
Initial array in function to aggregate multi-dimensional array

CREATE AGGREGATE array_agg_mult (anyarray)  (
    SFUNC     = array_cat
   ,STYPE     = anyarray
   ,INITCOND  = '{}'
);

替代功能

利用这种美感,我们可以构建一个与上述相同的简单 SQL 函数:

CREATE OR REPLACE FUNCTION f_array_fill_sql(_rows integer, _cols integer)
  RETURNS float8[][] AS
$func$
SELECT array_agg_mult(ARRAY[arr1]) AS arr2
FROM  (
   SELECT array_agg((i * $2 + j)::float8) AS arr1
   FROM   generate_series(1, $1) i
   CROSS  JOIN generate_series(1, $2) j
   GROUP  BY i
   ORDER  BY i
   ) sub
$func$ LANGUAGE sql

呼叫:

SELECT f_array_fill_sql(3,4);

结果:

{{4,5,6},{7,8,9}}

比较

对于小数字,性能差异可以忽略不计。但是第一个变体(即使现在已经优化)随着数量的增加而迅速恶化。试试:

EXPLAIN ANALYZE SELECT f_array_fill(100,100)
EXPLAIN ANALYZE SELECT f_array_fill_sql(100,100)  -- ~ 50x faster!

【讨论】:

  • 对数组字段的访问相对较快(肯定不如 C 快),但实际上数组更新大数组非常慢,所以(此时)一个数组(以及 Postgres 中的所有对象)是不可变的结构。任何更新都意味着创建新的。此功能将被修复(可能) - 补丁在 commitfest postgresql.org/message-id/…
  • @PavelStehule:我已经读过它了。令人振奋的消息。做得好! :)
猜你喜欢
  • 2014-07-13
  • 1970-01-01
  • 1970-01-01
  • 2020-08-23
  • 2023-03-25
  • 2017-09-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多