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

Posted

技术标签:

【中文标题】没有 STRICT 修饰符,函数执行得更快?【英文标题】:Function executes faster without STRICT modifier? 【发布时间】:2012-01-17 07:36:51 【问题描述】:

当一个简单的 SQL 函数声明为 STRICT 而 answering this question 时,我偶然发现了性能下降。

为了演示,我创建了一个函数的两个变体,按升序对数组的两个元素进行排序。

测试设置

包含 10000 个随机整数对的表(

CREATE TABLE tbl (arr int[]);

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

没有STRICT修饰符的函数:

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

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

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

结果

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

SELECT f_sort_array(arr)        FROM tbl;  -- Total runtime:  43 ms
SELECT f_sort_array_strict(arr) FROM tbl;  -- Total runtime: 103 ms

这些是 Debian Squeeze 上 Postgres 9.0.5 的结果。 8.4 上的类似结果。

在所有 NULL 值的测试中,两个函数执行相同:~37 ms。

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

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

重新测试

相同的测试,相同的硬件,Postgres 9.1。更大的差异:

SELECT f_sort_array(arr)        FROM tbl;  -- Total runtime:  27 ms
SELECT f_sort_array_strict(arr) FROM tbl;  -- Total runtime: 107 ms

相同的测试,新硬件,Postgres 9.6。差距更大,然而:

SELECT f_sort_array(arr)        FROM tbl;  -- Total runtime:  10 ms
SELECT f_sort_array_strict(arr) FROM tbl;  -- Total runtime:  60 ms

【问题讨论】:

这两个函数不是等价的。 STRICT 不是提示而是指令,“不要使用空参数调用它”。这将导致您没有明确要求的非空检查,因此评论不回答。然而,令我惊讶的是,当我在带有 NOT NULL 修饰符的表上测试它时,它仍然具有相同的效果。 @couling:无论有没有 STRICT,示例函数都会产生相同的结果。如果涉及 NULL 值,“常识”会告诉我 STRICT 更快,但事实并非如此。我在我的问题中添加了一个带有 NULL 的快速测试。 不涉及空值并不意味着 postgres 知道它们不涉及。可能还需要检查。 很好且经过充分研究的问题,为什么投反对票!? Pg 开发者必须阅读这篇文章作为错误报告。 BigBig 性能损失破坏了对STRICT 用户的任何期望。 【参考方案1】:

这是关于函数内联,就像 Richard 的测试所怀疑和证实的那样。

明确,Postgres Wiki 列出了内联标量函数的要求(如我的示例):

如果函数声明为STRICT,那么规划器必须能够证明如果任何参数为空,主体表达式必然返回NULL。目前,这个条件只有在以下情况下才满足:每个参数至少被引用一次,并且body中使用的所有函数、运算符和其他构造本身都是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小提琴here

【讨论】:

【参考方案2】:

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

这就是我的猜测。你有一个非常简单的表达式。一个实际的函数调用大概涉及堆栈设置、传递参数等。

下面的测试给出了 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;

【讨论】:

是的,STRICT 函数不能内联,因此可能会慢很多,尤其是对于简单的表达式。就我个人而言,我有点惊讶 Pg 没有像 CASE WHEN input IS NULL THEN NULL ELSE func(input) END 那样有效地内联它们(或一些更易于评估的类似函数的等价物),但我确信它不可能那么简单,否则他们会做到的很久以前。 @CraigRinger:Richard 的函数和我上面的函数有一个重要区别。 STRICT 更改 当使用 NULL 调用此函数时的结果。因此,它应该执行得更慢是远程可以理解的。我们在这里学到的教训:除非您需要,否则不要将 STRICT 用于简单的函数。就像你说的那样,这里肯定有优化的潜力,但它很小而且只适用于简单的情况,所以我们可能永远看不到它发生。这应该被记录在案。我很确定大多数人都对这种效果感到惊讶。 这个 错误报告 是在 2011 年...而如今,(2019 年!)错误仍然存​​在... 如何对 PostgreSQL 开发团队说这是一个相关的 错误。没有投票系统? 什么错误报告?未在极端情况下应用的优化不是错误。功能请求,但不是错误。如果您愿意,有一个有据可查(但自然而然地相当严格)的方法来提供补丁。

以上是关于没有 STRICT 修饰符,函数执行得更快?的主要内容,如果未能解决你的问题,请参考以下文章

智能合约的函数与函数修饰符

智能合约的函数与函数修饰符

Python 修饰符, 装饰符

Python @修饰符示例理解

静态修饰符static,类中的常量定义修饰符

OpenGL ES着色器语言----------------储存修饰符