Reduce memory overheads for storing a Memoize tuple
a0942f441 added ExecCopySlotMinimalTupleExtra(), which accepts a
parameter to specify the number of "extra" bytes that we want to
allocate along with the MinimalTuple. That's now used in
execGrouping.c to save some memory.
The same optimisation can be applied in nodeMemoize.c. MemoizeTuple
has a field for the tuple being stored and 1 other field to point to
the next tuple cached for this MemoizeEntry. Here we could use
ExecCopySlotMinimalTupleExtra() to specify that we want a pointer's
worth of extra bytes palloc'd for the MinimalTuple, and then store the
pointer to the next tuple in those bytes. This saves 16 bytes per
cached tuple. 24 bytes less because we don't palloc a MemoizeTuple
(including the MemoryChunk's 8 bytes), and 8 bytes more for the
ExecCopySlotMinimalTupleExtra bytes, a net saving of 16 bytes per
tuple.
Making Memoize use less memory is useful in cases where the cache
would otherwise have to reload entries that were cached previously but
were evicted due to reaching memory limits.
A quick example:
create table t1 (a int not null);
create table t2 (a int not null);
insert into t1 select x from generate_series(1,100000) x,
generate_series(1,100);
create index on t1 (a);
insert into t2 select x%1000+1 from generate_series(1,1000000)x;
analyze t1,t2;
explain analyze select count(*) from t1 inner join t2 on t1.a=t2.a;
Master: Memory Usage: 3583kB
Patched: Memory Usage: 2801kB
Really, the savings are double what's reported by EXPLAIN ANALYZE, as
CACHE_TUPLE_BYTES doesn't account for any of the MemoryChunks that are
consumed by palloc. We're now doing 1 fewer palloc per tuple due to
the removal of the palloc_object(MemoizeTuple) code, so more like 30%
less memory for this case.
Patch attached.
David
Attachments:
v1-0001-Reduce-memory-overheads-for-storing-a-Memoize-tup.patchapplication/octet-stream; name=v1-0001-Reduce-memory-overheads-for-storing-a-Memoize-tup.patchDownload+66-41
On Aug 1, 2026, at 16:35, David Rowley <dgrowleyml@gmail.com> wrote:
a0942f441 added ExecCopySlotMinimalTupleExtra(), which accepts a
parameter to specify the number of "extra" bytes that we want to
allocate along with the MinimalTuple. That's now used in
execGrouping.c to save some memory.The same optimisation can be applied in nodeMemoize.c. MemoizeTuple
has a field for the tuple being stored and 1 other field to point to
the next tuple cached for this MemoizeEntry. Here we could use
ExecCopySlotMinimalTupleExtra() to specify that we want a pointer's
worth of extra bytes palloc'd for the MinimalTuple, and then store the
pointer to the next tuple in those bytes. This saves 16 bytes per
cached tuple. 24 bytes less because we don't palloc a MemoizeTuple
(including the MemoryChunk's 8 bytes), and 8 bytes more for the
ExecCopySlotMinimalTupleExtra bytes, a net saving of 16 bytes per
tuple.Making Memoize use less memory is useful in cases where the cache
would otherwise have to reload entries that were cached previously but
were evicted due to reaching memory limits.A quick example:
create table t1 (a int not null);
create table t2 (a int not null);
insert into t1 select x from generate_series(1,100000) x,
generate_series(1,100);
create index on t1 (a);
insert into t2 select x%1000+1 from generate_series(1,1000000)x;
analyze t1,t2;explain analyze select count(*) from t1 inner join t2 on t1.a=t2.a;
Master: Memory Usage: 3583kB
Patched: Memory Usage: 2801kBReally, the savings are double what's reported by EXPLAIN ANALYZE, as
CACHE_TUPLE_BYTES doesn't account for any of the MemoryChunks that are
consumed by palloc. We're now doing 1 fewer palloc per tuple due to
the removal of the palloc_object(MemoizeTuple) code, so more like 30%
less memory for this case.Patch attached.
David
<v1-0001-Reduce-memory-overheads-for-storing-a-Memoize-tup.patch>
The optimized data structure looks good to me. The test result on my side exactly matches yours: 3583kB vs. 2801kB. I had to turn off hash join, otherwise the planner always chose a parallel hash join.
I have only one nitpick. MAXALIGN(sizeof(MinimalTuple)) appears 4 times. Would it make sense to define a macro for it, say MEMOIZE_TUPLE_LINK_SIZE?
Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/
On Mon, 3 Aug 2026 at 16:52, Chao Li <li.evan.chao@gmail.com> wrote:
The optimized data structure looks good to me. The test result on my side exactly matches yours: 3583kB vs. 2801kB. I had to turn off hash join, otherwise the planner always chose a parallel hash join.
Thanks for looking and checking.
I have only one nitpick. MAXALIGN(sizeof(MinimalTuple)) appears 4 times. Would it make sense to define a macro for it, say MEMOIZE_TUPLE_LINK_SIZE?
Good idea. Here's a new patch with that adjustment made.
David
Attachments:
v2-0001-Reduce-memory-overheads-for-storing-a-Memoize-tup.patchapplication/octet-stream; name=v2-0001-Reduce-memory-overheads-for-storing-a-Memoize-tup.patchDownload+75-41
On Aug 5, 2026, at 13:23, David Rowley <dgrowleyml@gmail.com> wrote:
On Mon, 3 Aug 2026 at 16:52, Chao Li <li.evan.chao@gmail.com> wrote:
The optimized data structure looks good to me. The test result on my side exactly matches yours: 3583kB vs. 2801kB. I had to turn off hash join, otherwise the planner always chose a parallel hash join.
Thanks for looking and checking.
I have only one nitpick. MAXALIGN(sizeof(MinimalTuple)) appears 4 times. Would it make sense to define a macro for it, say MEMOIZE_TUPLE_LINK_SIZE?
Good idea. Here's a new patch with that adjustment made.
David
<v2-0001-Reduce-memory-overheads-for-storing-a-Memoize-tup.patch>
V2 LGTM. I also reran the tests, everything is good.
Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/
Import Notes
Reply to msg id not found: CAApHDvpsZS=TyC3kRx1B2etSHxevgmoGjhXoR3U2O3rv=vHSVA@mail.gmail.com