choose_plan_name() v6 -- measurements Builds: master at 76e9e44b03a (2026-11-04), v5 and v6 applied to it, and 18.6; all -O2, without assertions. "v6 A" is the smaller variant (the hash table only replaces the list searches); "v6" is the patch attached. Planning time is EXPLAIN (SUMMARY)'s, in ms, median of 5 samples, the builds taking turns. At N = 4000 no two samples of a cell differ by more than 9% of its median; in the cells of a few ms (N = 1000, 2000) single samples stray by up to 27%, with no second mode. join_collapse_limit = from_collapse_limit = 1, so join order search is not what is measured. 1. Generated names, all from one base name: the 4000-arm UNION SELECT 1 UNION SELECT 2 ... UNION SELECT N (setop_1 .. setop_N) N 18.6 master v5 v6 A v6 100 0.3 0.7 0.3 0.6 0.3 500 1.3 37.1 2.0 7.8 1.7 1000 3.2 269.4 3.3 27.8 3.5 2000 7.4 2084.3 8.4 111.7 8.6 4000 18.8 16615.6 21.8 440.5 22.1 2. Different names given by the user: N CTEs WITH c1 AS MATERIALIZED (SELECT 1 AS a), ..., cN AS ... SELECT count(*) FROM (SELECT a FROM c1 UNION ALL ... SELECT a FROM cN) s N 18.6 master v5 v6 A v6 100 0.6 0.6 0.6 0.6 0.6 500 4.1 4.6 4.4 4.2 4.1 1000 10.9 12.0 11.9 11.1 10.8 2000 34.0 38.1 38.5 34.8 34.4 4000 127.8 152.7 151.1 128.8 126.3 And N subqueries in FROM, s1 .. sN: SELECT 1 FROM (SELECT 1 AS a OFFSET 0) s1 CROSS JOIN ... sN (N = 4000 measured one build at a time: with five servers up, one of them went away at that size) N 18.6 master v5 v6 A v6 100 0.5 0.5 0.5 0.5 0.5 500 3.9 3.8 3.7 4.0 3.6 1000 9.4 10.1 10.1 9.4 10.2 2000 29.4 32.8 33.8 29.2 29.8 4000 97.1 116.3 113.9 96.4 95.7 3. The threshold: choose_plan_name() alone, from a test-only C function that calls it N times on a fresh PlannerGlobal; total ns for the N calls, median of 1001, with the list only and with the hash table from the first name. The hash table is faster from N = 8 with generated names and from N = 40 with different names, where the list is cheapest; at 32 that case is 0.2 us slower in total. N generated (expr_N) different (cN) list hash list hash 4 250 240 50 140 8 1001 500 110 300 16 4348 1032 330 631 24 11312 1593 601 851 32 22804 2304 1001 1192 40 39306 2935 1512 1392 48 62111 3677 2184 1543 64 130494 5290 3997 2424 128 778317 10851 14608 6121 256 5213552 24187 54125 15580 4. Small statements, below 32 names: 8 statements (sublinks, CTEs, IN, EXISTS, UNION, min/max, a subquery in FROM, one with 20 sublinks), 20 times each per round; the sum of their planning times per round, with master, a second cluster of the same master build ("master again") and v6 taking turns; 4 runs of 40 rounds, pooled; median and 95% CI of the difference. Two clusters of one build differ by about 2%, as much as v6 and master do. "aligned": master and v6 both built with -falign-functions=64, which makes the difference go away, so it comes from where the code lands, not from what runs: below 32 names v6 runs master's loops plus one pointer test per call. The last line adds a statement with 40 subplans, which uses the hash table. v6 vs master: +3.6% [95% CI +2.0..+5.2], n=160+160 v6 vs master again: +1.6% [95% CI +0.1..+2.9], n=160+160 master again vs master: +2.0% [95% CI +0.7..+3.4], n=160+160 aligned: v6 vs master: +0.0% [95% CI -0.9..+1.2], n=160+160 aligned: v6 vs master again: -1.2% [95% CI -2.3..-0.1], n=160+160 aligned: master again vs master: +1.2% [95% CI +0.1..+2.6], n=160+160 with a 40-subplan statement: -2.3% [95% CI -3.2..-1.0], n=40+40 Each statement alone, median of 600, in us (EXPLAIN's resolution); "again" is the second cluster of the master build, in the same run; on the right, the builds with -falign-functions=64: default build aligned master again v6 master again v6 0 23 23 24 24 25 24 SELECT a, (SELECT max(b) FROM t2 1 10 10 10 10 11 10 SELECT * FROM t WHERE a IN (SELEC 2 41 41 42 42 42 42 SELECT * FROM t WHERE EXISTS (SEL 3 39 39 40 41 41 40 WITH x AS MATERIALIZED (SELECT a 4 19 19 20 20 21 20 SELECT a FROM t WHERE b = 1 UNION 5 12 12 13 13 13 13 SELECT min(a), max(a) FROM t 6 26 26 27 27 28 27 SELECT t.a, s.c FROM t, (SELECT a 7 106 105 107 109 110 107 SELECT (SELECT count(*) FROM t2 W 5. Names: v6 chooses the same names as master. make check and the checks of pg_overexplain, pg_plan_advice, pg_stash_advice and postgres_fdw pass, on master and REL_19_STABLE, and the names printed for 32 more statements (19 with every kind of subplan, 13 built so that generated names meet names given by the user; EXPLAIN with pg_overexplain's RANGE_TABLE and pg_plan_advice's PLAN_ADVICE) are byte-identical to master's. The same holds with the threshold set to 0, so that every name goes through the hash table.