没有STRICT修饰符,函数执行速度更快?

时间:2011-12-10 07:43:08

标签: performance postgresql strict sql-function

我想知道在声明一个简单的SQL函数STRICT时性能下降。我在answering a question here时偶然发现了这种现象。

为了演示效果,我创建了一个简单SQL函数的两个变体,它们按升序对数组中的两个元素进行排序。

测试设置

-- temporary table with 10000 random pairs of integer
CREATE TEMP TABLE arr (i int[]);

INSERT INTO arr 
SELECT ARRAY[(random() * 1000)::int, (random() * 1000)::int]
FROM   generate_series(1,10000);

STRICT修饰符的功能:

CREATE OR REPLACE FUNCTION f_sort_array1(int[])  RETURNS int[] AS
$$
SELECT CASE WHEN $1[1] > $1[2] THEN ARRAY[$1[2], $1[1]] ELSE $1 END;
$$ LANGUAGE sql STRICT IMMUTABLE;

没有STRICT修饰符的函数(否则相同):

CREATE OR REPLACE FUNCTION f_sort_array2(int[])  RETURNS int[] AS
$$
SELECT CASE WHEN $1[1] > $1[2] THEN ARRAY[$1[2], $1[1]] ELSE $1 END;
$$ LANGUAGE sql IMMUTABLE;

结果

我执行了大约20次,并从EXPLAIN ANALYZE获得了最佳结果。

SELECT f_sort_array1(i) FROM arr;  -- Total runtime: 103 ms
SELECT f_sort_array2(i) FROM arr;  -- Total runtime:  43 ms (!!!)

这些是来自Debian Squeeze上的v9.0.5服务器的结果。关于v8.4的类似结果。没有在9.1上测试,现在没有我可支配的集群。 (有人可以为v9.1提供额外的结果吗?)

编辑: 在具有10000个NULL值的测试中,两个函数在相同的测试环境中执行相同的操作:~37 ms。

我做了一些研究,发现了一个有趣的问题。在大多数情况下,声明SQL函数 STRICT会禁用函数内联。有关PostgreSQL Online Journalpgsql-performance mailing listPostgres Wiki中的更多信息。

但我不太确定这是怎么解释的。如何在这个简单的场景中内联函数导致性能下降?没有索引,没有光盘读取,没有排序。也许是通过内联函数简化了重复函数调用的开销?你能解释一下吗?或者我错过了什么?


使用Postgres 9.1重新测试

在与PostgreSQL 9.1相同的硬件上进行的相同测试发现了更大的差异:

SELECT f_sort_array1(i) FROM arr;  -- Total runtime: 107 ms
SELECT f_sort_array2(i) FROM arr;  -- Total runtime:  27 ms (!!!)

使用Postgres 9.6

重新测试

使用PostgreSQL 9.6在不同硬件上进行相同的测试。差距更大,但是:

SELECT f_sort_array1(i) FROM arr;  -- Total runtime: 60 ms
SELECT f_sort_array2(i) FROM arr;  -- Total runtime: 10 ms (!!!)

2 个答案:

答案 0 :(得分:3)

  

重复函数调用的开销可能是通过内联函数来简化的吗?

这就是我猜的。你在那里有一个非常简单的表达。实际的函数调用可能涉及堆栈设置,传递参数等。

下面的测试给出了内联的5ms的运行时间和严格的50ms的运行时间。

BEGIN;

CREATE SCHEMA f;

SET search_path = f;

CREATE FUNCTION f1(int) RETURNS int AS $$SELECT 1$$ LANGUAGE SQL;
CREATE FUNCTION f2(int) RETURNS int AS $$SELECT 1$$ LANGUAGE SQL STRICT;

\timing on
SELECT sum(f1(i)) FROM generate_series(1,10000) i;
SELECT sum(f2(i)) FROM generate_series(1,10000) i;
\timing off

ROLLBACK;

答案 1 :(得分:0)

这是关于函数内联,就像理查德的测试所怀疑和证实的一样。

要清楚Postgres Wiki 列出了标​​量函数内联的要求(如我的示例):

<块引用>
  • 如果函数声明为STRICT,那么规划器必须能够证明如果任何参数为空,则主体表达式必然返回NULL。目前,此条件仅满足以下条件:每个参数至少被引用一次,并且主体中使用的所有函数、运算符和其他结构本身都是 STRICT

示例函数显然不合格。根据我的测试,CASE 构造函数和 ARRAY 构造函数都应受到指责。

表函数(返回一组行)更加挑剔:

<块引用>
  • 函数未声明 STRICT

如果函数不能内联,重复执行会重复收集函数开销。在后期的 Postgres 版本中,性能差异变得更大。

在当前笔记本电脑上使用 PostgreSQL 13 重新测试。更大的差异,但:

SELECT f_sort_array(arr)        FROM tbl;  -- Total runtime:   4 ms
SELECT f_sort_array_strict(arr) FROM tbl;  -- Total runtime:  32 ms

在 dbfiddle.com 上进行了相同的测试,PostgreSQL 13。更大的差异,但:

SELECT f_sort_array(arr)        FROM tbl;  -- Total runtime:   4 ms
SELECT f_sort_tblay_strict(arr) FROM tbl;  -- Total runtime: 137 ms (!)

综合测试,包括一半和全部 NULL 值的测试:

db<>fiddle here