Commit fd333bc7 authored by Tom Lane's avatar Tom Lane

Speed up plpgsql trigger startup by introducing "promises".

Over the years we've accreted quite a few special variables that are
predefined in plpgsql trigger functions.  The cost of initializing these
variables to their defined values turns out to be a significant part of
the runtime of simple triggers; but, undoubtedly, most real-world triggers
never examine the values of most of these variables.

To improve matters, invent the notion of a variable that has a "promise"
attached to it, specifying which of the predetermined values should be
assigned to the variable if anything ever reads it.  This eliminates all
the unneeded startup overhead, in return for a small penalty on accesses
to these variables.

Tom Lane, reviewed by Pavel Stehule

Discussion: https://postgr.es/m/11986.1514407114@sss.pgh.pa.us
parent 40301c1c
......@@ -607,7 +607,9 @@ do_compile(FunctionCallInfo fcinfo,
-1,
InvalidOid),
true);
function->tg_name_varno = var->dno;
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
var->dtype = PLPGSQL_DTYPE_PROMISE;
((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_NAME;
/* Add the variable tg_when */
var = plpgsql_build_variable("tg_when", 0,
......@@ -615,7 +617,9 @@ do_compile(FunctionCallInfo fcinfo,
-1,
function->fn_input_collation),
true);
function->tg_when_varno = var->dno;
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
var->dtype = PLPGSQL_DTYPE_PROMISE;
((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_WHEN;
/* Add the variable tg_level */
var = plpgsql_build_variable("tg_level", 0,
......@@ -623,7 +627,9 @@ do_compile(FunctionCallInfo fcinfo,
-1,
function->fn_input_collation),
true);
function->tg_level_varno = var->dno;
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
var->dtype = PLPGSQL_DTYPE_PROMISE;
((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_LEVEL;
/* Add the variable tg_op */
var = plpgsql_build_variable("tg_op", 0,
......@@ -631,7 +637,9 @@ do_compile(FunctionCallInfo fcinfo,
-1,
function->fn_input_collation),
true);
function->tg_op_varno = var->dno;
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
var->dtype = PLPGSQL_DTYPE_PROMISE;
((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_OP;
/* Add the variable tg_relid */
var = plpgsql_build_variable("tg_relid", 0,
......@@ -639,7 +647,9 @@ do_compile(FunctionCallInfo fcinfo,
-1,
InvalidOid),
true);
function->tg_relid_varno = var->dno;
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
var->dtype = PLPGSQL_DTYPE_PROMISE;
((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_RELID;
/* Add the variable tg_relname */
var = plpgsql_build_variable("tg_relname", 0,
......@@ -647,7 +657,9 @@ do_compile(FunctionCallInfo fcinfo,
-1,
InvalidOid),
true);
function->tg_relname_varno = var->dno;
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
var->dtype = PLPGSQL_DTYPE_PROMISE;
((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_TABLE_NAME;
/* tg_table_name is now preferred to tg_relname */
var = plpgsql_build_variable("tg_table_name", 0,
......@@ -655,7 +667,9 @@ do_compile(FunctionCallInfo fcinfo,
-1,
InvalidOid),
true);
function->tg_table_name_varno = var->dno;
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
var->dtype = PLPGSQL_DTYPE_PROMISE;
((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_TABLE_NAME;
/* add the variable tg_table_schema */
var = plpgsql_build_variable("tg_table_schema", 0,
......@@ -663,7 +677,9 @@ do_compile(FunctionCallInfo fcinfo,
-1,
InvalidOid),
true);
function->tg_table_schema_varno = var->dno;
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
var->dtype = PLPGSQL_DTYPE_PROMISE;
((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_TABLE_SCHEMA;
/* Add the variable tg_nargs */
var = plpgsql_build_variable("tg_nargs", 0,
......@@ -671,7 +687,9 @@ do_compile(FunctionCallInfo fcinfo,
-1,
InvalidOid),
true);
function->tg_nargs_varno = var->dno;
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
var->dtype = PLPGSQL_DTYPE_PROMISE;
((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_NARGS;
/* Add the variable tg_argv */
var = plpgsql_build_variable("tg_argv", 0,
......@@ -679,7 +697,9 @@ do_compile(FunctionCallInfo fcinfo,
-1,
function->fn_input_collation),
true);
function->tg_argv_varno = var->dno;
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
var->dtype = PLPGSQL_DTYPE_PROMISE;
((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_ARGV;
break;
......@@ -701,7 +721,9 @@ do_compile(FunctionCallInfo fcinfo,
-1,
function->fn_input_collation),
true);
function->tg_event_varno = var->dno;
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
var->dtype = PLPGSQL_DTYPE_PROMISE;
((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_EVENT;
/* Add the variable tg_tag */
var = plpgsql_build_variable("tg_tag", 0,
......@@ -709,7 +731,9 @@ do_compile(FunctionCallInfo fcinfo,
-1,
function->fn_input_collation),
true);
function->tg_tag_varno = var->dno;
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
var->dtype = PLPGSQL_DTYPE_PROMISE;
((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_TAG;
break;
......@@ -1878,6 +1902,7 @@ build_row_from_vars(PLpgSQL_variable **vars, int numvars)
switch (var->dtype)
{
case PLPGSQL_DTYPE_VAR:
case PLPGSQL_DTYPE_PROMISE:
typoid = ((PLpgSQL_var *) var)->datatype->typoid;
typmod = ((PLpgSQL_var *) var)->datatype->atttypmod;
typcoll = ((PLpgSQL_var *) var)->datatype->collation;
......@@ -2196,6 +2221,7 @@ plpgsql_finish_datums(PLpgSQL_function *function)
switch (function->datums[i]->dtype)
{
case PLPGSQL_DTYPE_VAR:
case PLPGSQL_DTYPE_PROMISE:
copiable_size += MAXALIGN(sizeof(PLpgSQL_var));
break;
case PLPGSQL_DTYPE_REC:
......
This diff is collapsed.
......@@ -729,6 +729,7 @@ plpgsql_free_function_memory(PLpgSQL_function *func)
switch (d->dtype)
{
case PLPGSQL_DTYPE_VAR:
case PLPGSQL_DTYPE_PROMISE:
{
PLpgSQL_var *var = (PLpgSQL_var *) d;
......@@ -1582,6 +1583,7 @@ plpgsql_dumptree(PLpgSQL_function *func)
switch (d->dtype)
{
case PLPGSQL_DTYPE_VAR:
case PLPGSQL_DTYPE_PROMISE:
{
PLpgSQL_var *var = (PLpgSQL_var *) d;
......@@ -1608,6 +1610,9 @@ plpgsql_dumptree(PLpgSQL_function *func)
dump_expr(var->cursor_explicit_expr);
printf("\n");
}
if (var->promise != PLPGSQL_PROMISE_NONE)
printf(" PROMISE %d\n",
(int) var->promise);
}
break;
case PLPGSQL_DTYPE_ROW:
......
......@@ -3170,6 +3170,7 @@ make_return_stmt(int location)
if (tok == T_DATUM && plpgsql_peek() == ';' &&
(yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_VAR ||
yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_PROMISE ||
yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_ROW ||
yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC))
{
......@@ -3231,6 +3232,7 @@ make_return_next_stmt(int location)
if (tok == T_DATUM && plpgsql_peek() == ';' &&
(yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_VAR ||
yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_PROMISE ||
yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_ROW ||
yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC))
{
......@@ -3318,6 +3320,7 @@ check_assignable(PLpgSQL_datum *datum, int location)
switch (datum->dtype)
{
case PLPGSQL_DTYPE_VAR:
case PLPGSQL_DTYPE_PROMISE:
if (((PLpgSQL_var *) datum)->isconst)
ereport(ERROR,
(errcode(ERRCODE_ERROR_IN_ASSIGNMENT),
......
......@@ -63,9 +63,29 @@ typedef enum PLpgSQL_datum_type
PLPGSQL_DTYPE_ROW,
PLPGSQL_DTYPE_REC,
PLPGSQL_DTYPE_RECFIELD,
PLPGSQL_DTYPE_ARRAYELEM
PLPGSQL_DTYPE_ARRAYELEM,
PLPGSQL_DTYPE_PROMISE
} PLpgSQL_datum_type;
/*
* DTYPE_PROMISE datums have these possible ways of computing the promise
*/
typedef enum PLpgSQL_promise_type
{
PLPGSQL_PROMISE_NONE = 0, /* not a promise, or promise satisfied */
PLPGSQL_PROMISE_TG_NAME,
PLPGSQL_PROMISE_TG_WHEN,
PLPGSQL_PROMISE_TG_LEVEL,
PLPGSQL_PROMISE_TG_OP,
PLPGSQL_PROMISE_TG_RELID,
PLPGSQL_PROMISE_TG_TABLE_NAME,
PLPGSQL_PROMISE_TG_TABLE_SCHEMA,
PLPGSQL_PROMISE_TG_NARGS,
PLPGSQL_PROMISE_TG_ARGV,
PLPGSQL_PROMISE_TG_EVENT,
PLPGSQL_PROMISE_TG_TAG
} PLpgSQL_promise_type;
/*
* Variants distinguished in PLpgSQL_type structs
*/
......@@ -248,6 +268,14 @@ typedef struct PLpgSQL_variable
/*
* Scalar variable
*
* DTYPE_VAR and DTYPE_PROMISE datums both use this struct type.
* A PROMISE datum works exactly like a VAR datum for most purposes,
* but if it is read without having previously been assigned to, then
* a special "promised" value is computed and assigned to the datum
* before the read is performed. This technique avoids the overhead of
* computing the variable's value in cases where we expect that many
* functions will never read it.
*/
typedef struct PLpgSQL_var
{
......@@ -271,9 +299,18 @@ typedef struct PLpgSQL_var
int cursor_explicit_argrow;
int cursor_options;
/* Fields below here can change at runtime */
Datum value;
bool isnull;
bool freeval;
/*
* The promise field records which "promised" value to assign if the
* promise must be honored. If it's a normal variable, or the promise has
* been fulfilled, this is PLPGSQL_PROMISE_NONE.
*/
PLpgSQL_promise_type promise;
} PLpgSQL_var;
/*
......@@ -869,20 +906,6 @@ typedef struct PLpgSQL_function
int found_varno;
int new_varno;
int old_varno;
int tg_name_varno;
int tg_when_varno;
int tg_level_varno;
int tg_op_varno;
int tg_relid_varno;
int tg_relname_varno;
int tg_table_name_varno;
int tg_table_schema_varno;
int tg_nargs_varno;
int tg_argv_varno;
/* for event triggers */
int tg_event_varno;
int tg_tag_varno;
PLpgSQL_resolve_option resolve_option;
......@@ -912,6 +935,9 @@ typedef struct PLpgSQL_execstate
{
PLpgSQL_function *func; /* function being executed */
TriggerData *trigdata; /* if regular trigger, data about firing */
EventTriggerData *evtrigdata; /* if event trigger, data about firing */
Datum retval;
bool retisnull;
Oid rettype; /* type of current retval */
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment