Em qui., 29 de jun. de 2023 às 02:50, Alena Rybakina <
lena.riback...@yandex.ru> escreveu:

> Hi!
> On 29.06.2023 04:36, Ranier Vilela wrote:
>
> I don't want to be rude, but this doesn't seem very helpful.
>>
> Sorry, It was not my intention to cause interruptions.
>
>
>> - You made some changes, but you don't even attempt to explain what you
>> changed or why you changed it.
>>
> 1. Reduce scope
> 2. Eliminate unnecessary variables
> 3. Eliminate unnecessary expressions
>
>
>>
>> - You haven't even tried to compile the code, nor tested it. If it
>> happens to compile, wow could others even know it actually behaves the
>> way you wanted?
>
> Sorry I didn't answer right away. I will try not to do this in the future
> thank you for your participation and help.
>
There's no need to apologize.


> Yes, the scope of this patch may be small, but I am sure that it will
> solve the worst case of memory consumption with large numbers of "or"
> expressions or reduce execution and planning time.
>
Yeah, I also believe it will help performance.

> As I have already said, I conducted a launch on a database with 20 billion
> data generated using a benchmark. Unfortunately, at that time I sent a not
> quite correct picture: the execution time, not the planning time, increases
> with the number of "or" expressions (execution_time.png). x is the number
> of or expressions, y is the execution/scheduling time.I also throw memory
> consumption at 50,000 "or" expressions collected by HeapTrack (where memory
> consumption was recorded already at the initialization stage of the 1.27GB
> pic3.png). I think such a transformation will allow just the same to avoid
> such a worst case, since in comparison with ANY memory is much less and
> takes little time.
>
SELECT FORMAT('prepare x %s AS SELECT * FROM pgbench_accounts a WHERE %s',
>  '(' || string_agg('int', ',') || ')',
>  string_agg(FORMAT('aid = $%s', g.id), ' or ')
>  ) AS cmd
>  FROM generate_series(1, 50000) AS g(id)
> \gexec
>
> SELECT FORMAT('execute x %s;', '(' || string_agg(g.id::text, ',') || ')') AS 
> cmd
>  FROM generate_series(1, 50000) AS g(id)
> \gexec
>
> I tried to add a transformation at the path formation stage before we form
> indexes (set_plain_rel_pathlist function) and at the stage when we have
> preprocessing of "or" expressions (getting rid of duplicates or useless
> conditions), but everywhere there was a problem of incorrect selectivity
> estimation.
>
> CREATE TABLE tenk1 (unique1 int, unique2 int, ten int, hundred int);
> insert into tenk1 SELECT x,x,x,x FROM generate_series(1,50000) as x;
> CREATE INDEX a_idx1 ON tenk1(unique1);
> CREATE INDEX a_idx2 ON tenk1(unique2);
> CREATE INDEX a_hundred ON tenk1(hundred);
>
> postgres=# explain analyze
> select * from tenk1 a join tenk1 b on
>   ((a.unique2 = 3 or a.unique2 = 7)) or (a.unique1 = 1);
>                                                       QUERY PLAN
> ----------------------------------------------------------------------------------------------------------------------
>  Nested Loop  (cost=0.00..140632434.50 rows=11250150000 width=32) (actual 
> time=0.077..373.279 rows=1350000 loops=1)
>    ->  Seq Scan on tenk1 b  (cost=0.00..2311.00 rows=150000 width=16) (actual 
> time=0.037..13.941 rows=150000 loops=1)
>    ->  Materialize  (cost=0.00..3436.01 rows=75001 width=16) (actual 
> time=0.000..0.001 rows=9 loops=150000)
>          ->  Seq Scan on tenk1 a  (cost=0.00..3061.00 rows=75001 width=16) 
> (actual time=0.027..59.174 rows=9 loops=1)
>                Filter: ((unique2 = ANY (ARRAY[3, 7])) OR (unique1 = 1))
>                Rows Removed by Filter: 149991
>  Planning Time: 0.438 ms
>  Execution Time: 407.144 ms
> (8 rows)
>
> Only by converting the expression at this stage, we do not encounter this
> problem.
>
> postgres=# set enable_bitmapscan ='off';
> SET
> postgres=# explain analyze
> select * from tenk1 a join tenk1 b on
>   a.unique2 = 3 or a.unique2 = 7 or a.unique1 = 1;
>                                                       QUERY PLAN
> ----------------------------------------------------------------------------------------------------------------------
>  Nested Loop  (cost=0.00..22247.02 rows=1350000 width=32) (actual 
> time=0.094..373.627 rows=1350000 loops=1)
>    ->  Seq Scan on tenk1 b  (cost=0.00..2311.00 rows=150000 width=16) (actual 
> time=0.051..14.667 rows=150000 loops=1)
>    ->  Materialize  (cost=0.00..3061.05 rows=9 width=16) (actual 
> time=0.000..0.001 rows=9 loops=150000)
>          ->  Seq Scan on tenk1 a  (cost=0.00..3061.00 rows=9 width=16) 
> (actual time=0.026..42.389 rows=9 loops=1)
>                Filter: ((unique2 = ANY ('{3,7}'::integer[])) OR (unique1 = 1))
>                Rows Removed by Filter: 149991
>  Planning Time: 0.414 ms
>  Execution Time: 409.154 ms
> (8 rows)
>
> I compiled my original patch and there were no problems with regression
> tests. The only time there was a problem when I set the
> const_transform_or_limit variable to 0 (I have 15), as you have in the
> patch. To be honest, diff appears there because you had a different plan,
> specifically the expressions "or" are replaced by ANY (see
> regression.diffs).
>
You are right. The v3 attached shows the same diff.

Unfortunately, your patch version did not apply immediately, I did not
> understand the reasons, I applied it manually.
>
Sorry.


> At the moment, I'm not sure that the constant is the right number for
> applying transformations, so I'm in search of it, to be honest. I will post
> my observations on this issue later. If you don't mind, I'll leave the
> constant equal to 15 for now.
>
It's hard to predict. Perhaps accounting for time on each benchmark could
help decide.


> Sorry, I don't understand well enough what is meant by points "Eliminate
> unnecessary variables" and "Eliminate unnecessary expressions". Can you
> explain in more detail?
>
One example is array_type.
As you can see in v2 and v3 it no longer exists.


>
> Regarding the patch, there was a Warning at the compilation stage.
>
> In file included from ../../../src/include/nodes/bitmapset.h:21,
>
>                  from ../../../src/include/nodes/parsenodes.h:26,
>
>                  from ../../../src/include/catalog/objectaddress.h:17,
>
>                  from ../../../src/include/catalog/pg_aggregate.h:24,
>
>                  from parse_expr.c:18:
>
> parse_expr.c: In function ‘transformBoolExprOr’:
>
> ../../../src/include/nodes/nodes.h:133:66: warning: ‘expr’ is used 
> uninitialized [-Wuninitialized]
>
>   133 | #define nodeTag(nodeptr)                (((const 
> Node*)(nodeptr))->type)
>
>       |                                                                  ^~
>
> parse_expr.c:116:29: note: ‘expr’ was declared here
>
>   116 |         BoolExpr           *expr;
>
>       |                             ^~~~
>
> Sorry, this error did not appear in my builds

> I couldn't figure out how to fix it and went back to my original version.
> To be honest, I don't think anything needs to be changed here.
>
> Unfortunately, I didn't understand the reasons why, with the available or
> expressions, you don't even try to convert to ANY by calling
> transformBoolExpr, as I saw. I went back to my version.
>
> I think it's worth checking whether the or_statement variable is positive.
>
> I think it's worth leaving the use of the or_statement variable in its
> original form.
>
>   switch (expr->boolop)
>   {
>     case AND_EXPR:
>       opname = "AND";
>       break;
>     case OR_EXPR:
>       opname = "OR";
>       or_statement = true;
>       break;
>     case NOT_EXPR:
>       opname = "NOT";
>       break;
>     default:
>       elog(ERROR, "unrecognized boolop: %d", (int) expr->boolop);
>       opname = NULL;    /* keep compiler quiet */
>       break;
>   }
>
>   if (!or_statement || list_length(expr->args) < const_transform_or_limit)
>     return transformBoolExpr(pstate, (BoolExpr *)expr_orig);
>
You are right, the v3 this way.

As I said earlier, these are just suggestions.
But thinking about it now, I think they can be classified as bad early
optimizations.

regards,
Ranier Vilela
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 346fd272b6..99b12d4037 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -95,6 +95,294 @@ static Expr *make_distinct_op(ParseState *pstate, List 
*opname,
 static Node *make_nulltest_from_distinct(ParseState *pstate,
                                                                                
 A_Expr *distincta, Node *arg);
 
+typedef struct OrClauseGroupEntry
+{
+       Node               *node;
+       List               *consts;
+       Oid                             scalar_type;
+       Oid                             opno;
+       Expr               *expr;
+} OrClauseGroupEntry;
+
+static int const_transform_or_limit = 0;
+
+static Node *
+transformBoolExprOr(ParseState *pstate, Expr *expr_orig)
+{
+       List               *or_list = NIL;
+       List               *groups_list = NIL;
+       ListCell           *lc_eargs;
+       Node               *result;
+       BoolExpr           *expr;
+       const char         *opname;
+       bool                    change_apply = false;
+       bool                    or_statement = false;
+
+       Assert(IsA(expr, BoolExpr));
+
+       expr = (BoolExpr *) expr_orig;
+       if (list_length(expr->args) < const_transform_or_limit)
+               return transformBoolExpr(pstate, (BoolExpr *)expr_orig);
+
+       /* If this is not expression "Or", then will do it the old way. */
+       switch (expr->boolop)
+       {
+               case AND_EXPR:
+                       opname = "AND";
+                       break;
+               case OR_EXPR:
+                       opname = "OR";
+                       or_statement = true;
+                       break;
+               case NOT_EXPR:
+                       opname = "NOT";
+                       break;
+               default:
+                       elog(ERROR, "unrecognized boolop: %d", (int) 
expr->boolop);
+                       opname = NULL;          /* keep compiler quiet */
+                       break;
+       }
+
+       /*
+               * NOTE:
+               * It is an OR-clause. So, rinfo->orclause is a BoolExpr node, 
contains
+               * a list of sub-restrictinfo args, and rinfo->clause - which is 
the
+               * same expression, made from bare clauses. To not break 
selectivity
+               * caches and other optimizations, use both:
+               * - use rinfos from orclause if no transformation needed
+               * - use  bare quals from rinfo->clause in the case of 
transformation,
+               * to create new RestrictInfo: in this case we have no options 
to avoid
+               * selectivity estimation procedure.
+               */
+       if (!or_statement)
+               return transformBoolExpr(pstate, (BoolExpr *)expr_orig);
+               
+       expr = (BoolExpr *)copyObject(expr_orig);
+       foreach(lc_eargs, expr->args)
+       {
+               A_Expr                     *arg = (A_Expr *) lfirst(lc_eargs);
+               Node                       *bare_orarg;
+               Node                       *const_expr;
+               Node                       *non_const_expr;
+               ListCell                   *lc_groups;
+               OrClauseGroupEntry *gentry;
+               bool                            allow_transformation;
+
+               /*
+                * The first step: checking that the expression consists only 
of equality.
+                * We can only do this here, while arg is still row data type, 
namely A_Expr.
+                * After applying transformExprRecurce, we already know that it 
will be OpExr type,
+                * but checking the expression for equality is already becoming 
impossible for us.
+                * Sometimes we have the chance to devide expression into the 
groups on
+                * equality and inequality. This is possible if all list items 
are not at the
+                * same level of a single BoolExpr expression, otherwise all of 
them cannot be converted.
+                */
+
+               if (!arg)
+                       break;
+
+               allow_transformation = (
+                                       arg->type == T_A_Expr && (arg)->kind == 
AEXPR_OP &&
+                                                           
list_length((arg)->name) >=1 && strcmp(strVal(linitial((arg)->name)), "=") == 0
+                                                          );
+
+
+               bare_orarg = transformExprRecurse(pstate, (Node *)arg);
+               bare_orarg = coerce_to_boolean(pstate, bare_orarg, opname);
+
+               /*
+                * The next step: transform all the inputs, and detect whether 
any contain
+                * Vars.
+                */
+               if (!allow_transformation || !bare_orarg || !IsA(bare_orarg, 
OpExpr) || !contain_vars_of_level(bare_orarg, 0))
+               {
+                       /* Again, it's not the expr we can transform */
+                       or_list = lappend(or_list, bare_orarg);
+                       continue;
+               }
+
+               /*
+                * Get pointers to constant and expression sides of the clause
+                */
+               non_const_expr = get_leftop(bare_orarg);
+               const_expr = get_rightop(bare_orarg);
+
+               /*
+                       * At this point we definitely have a transformable 
clause.
+                       * Classify it and add into specific group of clauses, 
or create new
+                       * group.
+                       * TODO: to manage complexity in the case of many 
different clauses
+                       * (X1=C1) OR (X2=C2 OR) ... (XN = CN) we could invent 
something
+                       * like a hash table (htab key ???).
+                       */
+               foreach(lc_groups, groups_list)
+               {
+                       OrClauseGroupEntry *v = (OrClauseGroupEntry *) 
lfirst(lc_groups);
+
+                       Assert(v->node != NULL);
+
+                       if (equal(v->node, non_const_expr))
+                       {
+                               v->consts = lappend(v->consts, const_expr);
+                               non_const_expr = NULL;
+                               break;
+                       }
+               }
+
+               if (non_const_expr == NULL)
+                       /*
+                               * The clause classified successfully and added 
into existed
+                               * clause group.
+                               */
+                       continue;
+
+               /* New clause group needed */
+               gentry = palloc(sizeof(OrClauseGroupEntry));
+               gentry->node = non_const_expr;
+               gentry->consts = list_make1(const_expr);
+               gentry->opno = ((OpExpr *)bare_orarg)->opno;
+               gentry->expr = (Expr *)bare_orarg;
+               groups_list = lappend(groups_list,  (void *) gentry);
+       }
+
+       if (groups_list == NIL)
+       {
+               /*
+               * No any transformations possible with this rinfo, just add 
itself
+               * to the list and go further.
+               */
+               list_free(or_list);
+
+               return transformBoolExpr(pstate, (BoolExpr *)expr_orig);
+       }
+       else
+       {
+               ListCell           *lc_args;
+
+               /* Let's convert each group of clauses to an IN operation. */
+
+               /*
+               * Go through the list of groups and convert each, where number 
of
+               * consts more than 1. trivial groups move to OR-list again
+               */
+
+               foreach(lc_args, groups_list)
+               {
+                       OrClauseGroupEntry *gentry = (OrClauseGroupEntry *) 
lfirst(lc_args);
+                       List                       *allexprs;
+                       Oid                                 scalar_type;
+
+                       Assert(list_length(gentry->consts) > 0);
+
+                       if (list_length(gentry->consts) == 1)
+                       {
+                               /*
+                                * Only one element in the class. Return rinfo 
into the BoolExpr
+                                * args list unchanged.
+                                */
+                               list_free(gentry->consts);
+                               or_list = lappend(or_list, gentry->expr);
+                               continue;
+                       }
+
+                       /*
+                        * Do the transformation. It's been a long way ;)
+                        *
+                        * First of all, try to select a common type for the 
array elements.  Note that
+                        * since the LHS' type is first in the list, it will be 
preferred when
+                        * there is doubt (eg, when all the RHS items are 
unknown literals).
+                        *
+                        * Note: use list_concat here not lcons, to avoid 
damaging rnonvars.
+                        *
+                        * As a source of insides, use make_scalar_array_op()
+                        */
+                       allexprs = list_concat(list_make1(gentry->node), 
gentry->consts);
+                       scalar_type = select_common_type(NULL, allexprs, NULL, 
NULL);
+
+                       if (OidIsValid(scalar_type) &&
+                               scalar_type != RECORDOID &&
+                               verify_common_type(scalar_type, allexprs))
+                       {
+                               /*
+                                * OK: coerce all the right-hand non-Var inputs 
to the common type
+                                * and build an ArrayExpr for them.
+                                */
+                               List       *aexprs;
+                               ArrayExpr  *newa;
+                               ScalarArrayOpExpr *saopexpr;
+                               ListCell *l;
+
+                               aexprs = NIL;
+
+                               foreach(l, gentry->consts)
+                               {
+                                       Node       *rexpr = (Node *) lfirst(l);
+
+                                       rexpr = coerce_to_common_type(pstate, 
rexpr,
+                                                                               
                scalar_type,
+                                                                               
                "IN");
+                                       aexprs = lappend(aexprs, rexpr);
+                               }
+
+                               newa = makeNode(ArrayExpr);
+                               /* array_collid will be set by parse_collate.c 
*/
+                               newa->element_typeid = scalar_type;
+                               newa->array_typeid = 
get_array_type(scalar_type);
+                               newa->multidims = false;
+                               newa->elements = aexprs;
+                               newa->location = -1; /* Position of the new 
clause is undefined */
+
+                               saopexpr = (ScalarArrayOpExpr 
*)make_scalar_array_op(pstate,
+                                                                               
                   list_make1(makeString((char *) "=")),
+                                                                               
                   true,
+                                                                               
                   gentry->node,
+                                                                               
                   (Node *) newa,
+                                                                               
                   -1); /* Position of the new clause is undefined */
+
+                               /*
+                               * TODO: here we can try to coerce the array to 
a Const and find
+                               * hash func instead of linear search (see 
50e17ad281b).
+                               * convert_saop_to_hashed_saop((Node *) 
saopexpr);
+                               * We don't have to do this anymore, do we?
+                               */
+
+                               or_list = lappend(or_list, (void *) saopexpr);
+                               change_apply = true;
+                       }
+                       else
+                       {
+                               list_free(gentry->consts);
+                               or_list = lappend(or_list, gentry->expr);
+                               continue;
+                       }
+               }
+
+               if (!change_apply)
+               {
+                       or_statement = false;
+               }
+               list_free_deep(groups_list);
+       }
+
+       if (or_statement)
+       {
+               /* One more trick: assemble correct clause */
+               expr = list_length(or_list) > 1 ? makeBoolExpr(OR_EXPR, 
list_copy(or_list), -1) : linitial(or_list);
+               result = (Node *)expr;
+       }
+       /*
+        * There was no reasons to create a new expresion, so
+        * run the original BoolExpr conversion with using
+        * transformBoolExpr function
+        */
+       else
+       {
+               result = transformBoolExpr(pstate, (BoolExpr *)expr_orig);
+       }
+       list_free(or_list);
+
+       return result;
+}
 
 /*
  * transformExpr -
@@ -208,7 +496,7 @@ transformExprRecurse(ParseState *pstate, Node *expr)
                        }
 
                case T_BoolExpr:
-                       result = transformBoolExpr(pstate, (BoolExpr *) expr);
+                       result = (Node *)transformBoolExprOr(pstate, (Expr 
*)expr);
                        break;
 
                case T_FuncCall:
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 260854747b..01918e6aea 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1630,6 +1630,7 @@ NumericSumAccum
 NumericVar
 OM_uint32
 OP
+OrClauseGroupEntry
 OSAPerGroupState
 OSAPerQueryState
 OSInfo

Attachment: v3-regression.diffs
Description: Binary data

Reply via email to