Thread
-
deep copy with mutation?
Robert Haas <robertmhaas@gmail.com> — 2026-04-29T21:22:05Z
I spent a lot of time today being stupid before finally understanding that expression_tree_mutator() and query_tree_mutator() can't be used to substitute for copyObject() because they deep copy structure only if it's Node structure; non-nodes like plain old C strings are not deep-copied. But then I wondered why we don't have a thing that works that way, because we have stuff like this in a number of places: parsetree->returningList = copyObject(parsetree->returningList); ChangeVarNodes((Node *) parsetree->returningList, rt_index, parsetree->resultRelation, 0); If we had a fully-deeply-copying version of copyObject(), it could just adjust new Var nodes as it created them, instead of needing a separate tree walk. In CreateTriggerFiringOn, we have: qual = copyObject(whenClause); qual = (Node *) map_partition_varattnos((List *) qual, PRS2_OLD_VARNO, childTbl, rel); qual = (Node *) map_partition_varattnos((List *) qual, PRS2_NEW_VARNO, childTbl, rel); So a copy and then two consecutive mutation passes. Or similarly in rewriteTargetView: tmp_tlist = copyObject(view_targetlist); ChangeVarNodes((Node *) tmp_tlist, new_rt_index, new_exclRelIndex, 0); parsetree->onConflict = (OnConflictExpr *) ReplaceVarsFromTargetList((Node *) parsetree->onConflict, old_exclRelIndex, 0, view_rte, tmp_tlist, new_rt_index, REPLACEVARS_REPORT_ERROR, 0, &parsetree->hasSubLinks); I'm not entirely certain how much this kind of thing matters -- maybe it's better to have the copy routine be as simple as possible and accept the cost of walking the whole tree immediately afterwards to make changes? Perhaps this kind of thing is so cache-friendly that the mutation pass is really cheap? But it certainly kinda *looks* inefficient... -- Robert Haas EDB: http://www.enterprisedb.com -
Re: deep copy with mutation?
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com> — 2026-04-30T10:33:54Z
On Thu, Apr 30, 2026 at 2:52 AM Robert Haas <robertmhaas@gmail.com> wrote: > > I spent a lot of time today being stupid before finally understanding > that expression_tree_mutator() and query_tree_mutator() can't be used > to substitute for copyObject() because they deep copy structure only > if it's Node structure; non-nodes like plain old C strings are not > deep-copied. But then I wondered why we don't have a thing that works > that way, because we have stuff like this in a number of places: > > parsetree->returningList = copyObject(parsetree->returningList); > ChangeVarNodes((Node *) parsetree->returningList, rt_index, > parsetree->resultRelation, 0); > > If we had a fully-deeply-copying version of copyObject(), it could > just adjust new Var nodes as it created them, instead of needing a > separate tree walk. > > In CreateTriggerFiringOn, we have: > > qual = copyObject(whenClause); > qual = (Node *) > map_partition_varattnos((List *) qual, PRS2_OLD_VARNO, > childTbl, rel); > qual = (Node *) > map_partition_varattnos((List *) qual, PRS2_NEW_VARNO, > childTbl, rel); > > So a copy and then two consecutive mutation passes. > > Or similarly in rewriteTargetView: > > tmp_tlist = copyObject(view_targetlist); > > ChangeVarNodes((Node *) tmp_tlist, new_rt_index, > new_exclRelIndex, 0); > > parsetree->onConflict = (OnConflictExpr *) > ReplaceVarsFromTargetList((Node *) parsetree->onConflict, > old_exclRelIndex, > 0, > view_rte, > tmp_tlist, > new_rt_index, > REPLACEVARS_REPORT_ERROR, > 0, > &parsetree->hasSubLinks); > > I'm not entirely certain how much this kind of thing matters -- maybe > it's better to have the copy routine be as simple as possible and > accept the cost of walking the whole tree immediately afterwards to > make changes? Perhaps this kind of thing is so cache-friendly that the > mutation pass is really cheap? But it certainly kinda *looks* > inefficient... It looks inefficient in terms of CPU as well as memory since FLATCOPY itself does palloc_object() and I see some mutators using copyObject(). So we are copying the whole tree node by node and then parts of the tree are copied by mutators. I think what's needed is instead of FLATCOPY we need a version of copyObject which just creates a copy of the node without copying the subtrees but copying the structures like C strings and arrays. copyObject() already copies arrays and C strings. Add a flag to copyObject to indicate whether we want copy subtree or not and then replace FLATCOPY with copyObject(node, false) and all existing copyObject as copyObject(node, true). When generating _copy* functions, we define COPY_NODE_FIELD as COPY_SCALAR_FIELD. Would that work? -- Best Wishes, Ashutosh Bapat
-
Re: deep copy with mutation?
David Rowley <dgrowleyml@gmail.com> — 2026-06-02T23:10:22Z
On Thu, 30 Apr 2026 at 09:22, Robert Haas <robertmhaas@gmail.com> wrote: > I'm not entirely certain how much this kind of thing matters -- maybe > it's better to have the copy routine be as simple as possible and > accept the cost of walking the whole tree immediately afterwards to > make changes? Perhaps this kind of thing is so cache-friendly that the > mutation pass is really cheap? But it certainly kinda *looks* > inefficient... These tree traversals are often pretty expensive in terms of the overhead of stack manipulation and function call overhead. Having something to reduce those, I expect, could be made to demonstrate speedups. We also shouldn't forget about the reduction in pallocs and wasted memory from the to-be-mutated-later nodes having a redundant copy made by copyObject(). There's also the memory fragmentation issue of that, which would increase the likelihood of cache misses later due to non-consecutive memory access in later traversals. Whereas if the copy/mutate was done in one pass, and palloc() didn't reuse any chunks from the freelists, then the memory would be consecutive in tree traversal order. I think the main deciding factor whether we should do this is the complexity of doing it in terms of code changes vs. observed speedup. In the first example you gave, map_partition_varattnos() is called twice, so if that were to perform a full copy during that traversal, then you'd need to make that function do both replacements at the same time to avoid copying again on the next call. Maybe you could quickly figure out if this is worth the trouble by adding some additional copyObjects in the places you think could be made better to see if you can see a slowdown. If you can't measure that, then you could probably assume that removing a traversal wouldn't result in much of a speedup. David