pg.ddx.io pgsql-hackers@postgresql.org mailing list archive
help / color / mirror / Atom feed From: Tomas Vondra <tomas.vondra@enterprisedb.com>
To: David Rowley <dgrowleyml@gmail.com>
Cc: Andres Freund <andres@anarazel.de>
Cc: Tomas Vondra <tv@fuzzy.cz>
Cc: PostgreSQL Developers <pgsql-hackers@lists.postgresql.org>
Subject: Re: Use generation context to speed up tuplesorts
Date: Tue, 3 Aug 2021 16:10:23 +0200
Message-ID: <36829a8a-63d0-c428-89d5-07e49561973e@enterprisedb.com> (raw )
In-Reply-To: <CAApHDvp4cFZ6Qdw1Z2wrd1Uv5s6rPKP7FWC5jHFfzR=vU2Ox+w@mail.gmail.com >
References: <CAApHDvoH4ASzsAOyHcxkuY01Qf++8JJ0paw+03dk+W25tQEcNQ@mail.gmail.com >
<20210730203853.utjj43f6zzn5e2hy@alap3.anarazel.de >
<bc765deb-ca2b-838e-f980-16a77dd0922b@enterprisedb.com >
<d987fd54-01f8-0f73-af6c-519f799a0ab8@enterprisedb.com >
<CAApHDvp4cFZ6Qdw1Z2wrd1Uv5s6rPKP7FWC5jHFfzR=vU2Ox+w@mail.gmail.com >
On 8/2/21 1:17 PM, David Rowley wrote:
> On Sat, 31 Jul 2021 at 14:34, Tomas Vondra
> <tomas.vondra@enterprisedb.com> wrote:
>> I spent a bit of time hacking on the Generation context, adding the two
>> improvements discussed in this thread:
>>
>> 1) internal handling of block sizes, similar to what AllocSet does (it
>> pretty much just copies parts of it)
>>
>> 2) keeper block (we keep one empry block instead of freeing it)
>>
>> 3) I've also added allocChunkLimit, which makes it look a bit more like
>> AllocSet (instead of using just blockSize/8, which does not work too
>> well with dynamic blockSize)
>>
>> I haven't done any extensive tests on it, but it does pass check-world
>> with asserts etc. I haven't touched the comments, those need updating.
>> regards
>
> Thanks for starting work on that. I've only had a quick look, but I
> can have a more detailed look once you've got it more complete.
>
A review would be nice, although it can wait - It'd be interesting to
know if those patches help with the workload(s) you've been looking at.
> For now it does not really look like the keeper block stuff is wired
> up the same way as in aset.c. I'd expect you to be allocating that in
> the same malloc as you're using to allocate the context struct itself
> in GenerationContextCreate().
>
Yes, that difference is natural. The AllocSet works a bit differently,
as it does not release the blocks (except during reset), while the
Generation context frees the blocks. So it seems pointless to use the
same "keeper" block as AllocSet - instead my intention was to keep one
"allocated" block as a cache, which should help with tight pfree/palloc
cycles. Maybe we should not call that "keeper" block?
> Also, likely as a result of the above, minContextSize does not seem to
> be wired up to anything apart from an Assert().
>
Hmm, yeah. This is probably due to copying some of the block-growth and
keeper block code from AllocSet. There should be just init/max block
size, I think.
I did run the same set of benchmarks as for Slab, measuring some usual
allocation patterns. The results for i5-2500k machine are attached (for
the xeon it's almost exactly the same behavior). While running those
tests I realized the last patch is wrong and sets allocChunkLimit=1,
which is bogus and causes significant regression. So here's an updated
version of the patch series too.
regards
--
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Attachments:
[text/x-patch] 0001-generation-bench-v2.patch (18.9K, ../36829a8a-63d0-c428-89d5-07e49561973e@enterprisedb.com/2-0001-generation-bench-v2.patch)
download | inline diff:
From bf127426d5ad7add41a49cc80348233aa49e7d4e Mon Sep 17 00:00:00 2001
From: Tomas Vondra <tomas.vondra@postgresql.org>
Date: Sat, 31 Jul 2021 22:55:36 +0200
Subject: [PATCH 1/4] generation bench
---
contrib/generation_bench/.gitignore | 4 +
contrib/generation_bench/Makefile | 21 +
contrib/generation_bench/bench.sql | 40 ++
.../generation_bench--1.0.sql | 16 +
contrib/generation_bench/generation_bench.c | 438 ++++++++++++++++++
.../generation_bench/generation_bench.control | 4 +
6 files changed, 523 insertions(+)
create mode 100644 contrib/generation_bench/.gitignore
create mode 100644 contrib/generation_bench/Makefile
create mode 100644 contrib/generation_bench/bench.sql
create mode 100644 contrib/generation_bench/generation_bench--1.0.sql
create mode 100644 contrib/generation_bench/generation_bench.c
create mode 100644 contrib/generation_bench/generation_bench.control
diff --git a/contrib/generation_bench/.gitignore b/contrib/generation_bench/.gitignore
new file mode 100644
index 0000000000..5dcb3ff972
--- /dev/null
+++ b/contrib/generation_bench/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/contrib/generation_bench/Makefile b/contrib/generation_bench/Makefile
new file mode 100644
index 0000000000..0fee5b84db
--- /dev/null
+++ b/contrib/generation_bench/Makefile
@@ -0,0 +1,21 @@
+# contrib/generation_bench/Makefile
+
+MODULE_big = generation_bench
+OBJS = generation_bench.o
+
+EXTENSION = generation_bench
+DATA = generation_bench--1.0.sql
+PGFILEDESC = "generation_bench - slab context benchmarking functions"
+
+REGRESS = generation_bench
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/generation_bench
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/generation_bench/bench.sql b/contrib/generation_bench/bench.sql
new file mode 100644
index 0000000000..abf3511047
--- /dev/null
+++ b/contrib/generation_bench/bench.sql
@@ -0,0 +1,40 @@
+CREATE EXTENSION generation_bench;
+
+\o fifo-no-loops.data
+select run, block_size, chunk_size, 2*chunk_size, 1000000 * chunk_size, x.* from generate_series(1,5) r(run), generate_series(32,512,32) a(chunk_size), (values (1024), (2048), (4096), (8192), (16384), (32768)) AS b(block_size), lateral generation_bench_fifo(1000000, block_size, chunk_size, 2*chunk_size, 0, 0, 0) x;
+
+\o lifo-no-loops.data
+select run, block_size, chunk_size, 2*chunk_size, 1000000 * chunk_size, x.* from generate_series(1,5) r(run), generate_series(32,512,32) a(chunk_size), (values (1024), (2048), (4096), (8192), (16384), (32768)) AS b(block_size), lateral generation_bench_lifo(1000000, block_size, chunk_size, 2*chunk_size, 0, 0, 0) x;
+
+\o random-no-loops.data
+select run, block_size, chunk_size, 2*chunk_size, 1000000 * chunk_size, x.* from generate_series(1,5) r(run), generate_series(32,512,32) a(chunk_size), (values (1024), (2048), (4096), (8192), (16384), (32768)) AS b(block_size), lateral generation_bench_random(1000000, block_size, chunk_size, 2*chunk_size, 0, 0, 0) x;
+
+
+\o fifo-increase.data
+select run, block_size, chunk_size, 2*chunk_size, 1000000 * chunk_size, x.* from generate_series(1,5) r(run), generate_series(32,512,32) a(chunk_size), (values (1024), (2048), (4096), (8192), (16384), (32768)) AS b(block_size), lateral generation_bench_fifo(1000000, block_size, chunk_size, 2*chunk_size, 100, 10000, 15000) x;
+
+\o lifo-increase.data
+select run, block_size, chunk_size, 2*chunk_size, 1000000 * chunk_size, x.* from generate_series(1,5) r(run), generate_series(32,512,32) a(chunk_size), (values (1024), (2048), (4096), (8192), (16384), (32768)) AS b(block_size), lateral generation_bench_lifo(1000000, block_size, chunk_size, 2*chunk_size, 100, 10000, 15000) x;
+
+\o random-increase.data
+select run, block_size, chunk_size, 2*chunk_size, 1000000 * chunk_size, x.* from generate_series(1,5) r(run), generate_series(32,512,32) a(chunk_size), (values (1024), (2048), (4096), (8192), (16384), (32768)) AS b(block_size), lateral generation_bench_random(1000000, block_size, chunk_size, 2*chunk_size, 100, 10000, 15000) x;
+
+
+\o fifo-decrease.data
+select run, block_size, chunk_size, 2*chunk_size, 1000000 * chunk_size, x.* from generate_series(1,5) r(run), generate_series(32,512,32) a(chunk_size), (values (1024), (2048), (4096), (8192), (16384), (32768)) AS b(block_size), lateral generation_bench_fifo(1000000, block_size, chunk_size, 2*chunk_size, 100, 10000, 5000) x;
+
+\o lifo-decrease.data
+select run, block_size, chunk_size, 2*chunk_size, 1000000 * chunk_size, x.* from generate_series(1,5) r(run), generate_series(32,512,32) a(chunk_size), (values (1024), (2048), (4096), (8192), (16384), (32768)) AS b(block_size), lateral generation_bench_lifo(1000000, block_size, chunk_size, 2*chunk_size, 100, 10000, 5000) x;
+
+\o random-decrease.data
+select run, block_size, chunk_size, 2*chunk_size, 1000000 * chunk_size, x.* from generate_series(1,5) r(run), generate_series(32,512,32) a(chunk_size), (values (1024), (2048), (4096), (8192), (16384), (32768)) AS b(block_size), lateral generation_bench_random(1000000, block_size, chunk_size, 2*chunk_size, 100, 10000, 5000) x;
+
+
+\o fifo-cycle.data
+select run, block_size, chunk_size, 2*chunk_size, 1000000 * chunk_size, x.* from generate_series(1,5) r(run), generate_series(32,512,32) a(chunk_size), (values (1024), (2048), (4096), (8192), (16384), (32768)) AS b(block_size), lateral generation_bench_fifo(1000, block_size, chunk_size, 2*chunk_size, 10000, 1000, 1000) x;
+
+\o lifo-cycle.data
+select run, block_size, chunk_size, 2*chunk_size, 1000000 * chunk_size, x.* from generate_series(1,5) r(run), generate_series(32,512,32) a(chunk_size), (values (1024), (2048), (4096), (8192), (16384), (32768)) AS b(block_size), lateral generation_bench_lifo(1000, block_size, chunk_size, 2*chunk_size, 10000, 1000, 1000) x;
+
+\o random-cycle.data
+select run, block_size, chunk_size, 2*chunk_size, 1000000 * chunk_size, x.* from generate_series(1,5) r(run), generate_series(32,512,32) a(chunk_size), (values (1024), (2048), (4096), (8192), (16384), (32768)) AS b(block_size), lateral generation_bench_random(1000, block_size, chunk_size, 2*chunk_size, 10000, 1000, 1000) x;
diff --git a/contrib/generation_bench/generation_bench--1.0.sql b/contrib/generation_bench/generation_bench--1.0.sql
new file mode 100644
index 0000000000..5f7c46eef2
--- /dev/null
+++ b/contrib/generation_bench/generation_bench--1.0.sql
@@ -0,0 +1,16 @@
+/* generation_bench--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION generation_bench" to load this file. \quit
+
+CREATE FUNCTION generation_bench_random(nallocs bigint, block_size bigint, min_alloc_size bigint, max_alloc_size bigint, loops int, free_cnt int, alloc_cnt int, out mem_allocated bigint, out alloc_ms bigint, out free_ms bigint)
+AS 'MODULE_PATHNAME', 'generation_bench_random'
+LANGUAGE C VOLATILE STRICT;
+
+CREATE FUNCTION generation_bench_fifo(nallocs bigint, block_size bigint, min_alloc_size bigint, max_alloc_size bigint, loops int, free_cnt int, alloc_cnt int, out mem_allocated bigint, out alloc_ms bigint, out free_ms bigint)
+AS 'MODULE_PATHNAME', 'generation_bench_fifo'
+LANGUAGE C VOLATILE STRICT;
+
+CREATE FUNCTION generation_bench_lifo(nallocs bigint, block_size bigint, min_alloc_size bigint, max_alloc_size bigint, loops int, free_cnt int, alloc_cnt int, out mem_allocated bigint, out alloc_ms bigint, out free_ms bigint)
+AS 'MODULE_PATHNAME', 'generation_bench_lifo'
+LANGUAGE C VOLATILE STRICT;
diff --git a/contrib/generation_bench/generation_bench.c b/contrib/generation_bench/generation_bench.c
new file mode 100644
index 0000000000..1cff55b751
--- /dev/null
+++ b/contrib/generation_bench/generation_bench.c
@@ -0,0 +1,438 @@
+/*-------------------------------------------------------------------------
+ *
+ * generation_bench.c
+ *
+ * helper functions to benchmark generation context with different workloads
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <sys/time.h>
+
+#include "funcapi.h"
+#include "miscadmin.h"
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(generation_bench_random);
+PG_FUNCTION_INFO_V1(generation_bench_fifo);
+PG_FUNCTION_INFO_V1(generation_bench_lifo);
+
+typedef struct Chunk {
+ int random;
+ void *ptr;
+} Chunk;
+
+static int
+chunk_index_cmp(const void *a, const void *b)
+{
+ Chunk *ca = (Chunk *) a;
+ Chunk *cb = (Chunk *) b;
+
+ if (ca->random < cb->random)
+ return -1;
+ else if (ca->random > cb->random)
+ return 1;
+
+ return 0;
+}
+
+Datum
+generation_bench_random(PG_FUNCTION_ARGS)
+{
+ MemoryContext cxt,
+ oldcxt;
+ Chunk *chunks;
+ int64 i, j;
+ int64 nallocs = PG_GETARG_INT64(0);
+ int64 blockSize = PG_GETARG_INT64(1);
+ int64 minChunkSize = PG_GETARG_INT64(2);
+ int64 maxChunkSize = PG_GETARG_INT64(3);
+
+ int nloops = PG_GETARG_INT32(4);
+ int free_cnt = PG_GETARG_INT32(5);
+ int alloc_cnt = PG_GETARG_INT32(6);
+
+ struct timeval start_time,
+ end_time;
+ int64 alloc_time = 0,
+ free_time = 0;
+ int64 mem_allocated;
+
+ TupleDesc tupdesc;
+ Datum result;
+ HeapTuple tuple;
+ Datum values[9];
+ bool nulls[9];
+
+ int maxchunks;
+
+ maxchunks = nallocs + nloops * Max(0, alloc_cnt - free_cnt);
+
+ cxt = GenerationContextCreate(CurrentMemoryContext, "generation_bench", blockSize);
+
+ chunks = (Chunk *) palloc(maxchunks * sizeof(Chunk));
+
+ /* allocate the chunks in random order */
+ oldcxt = MemoryContextSwitchTo(cxt);
+
+ gettimeofday(&start_time, NULL);
+
+ for (i = 0; i < nallocs; i++)
+ {
+ int chunkSize = minChunkSize + random() % (maxChunkSize - minChunkSize);
+
+ chunks[i].ptr = palloc(chunkSize);
+ }
+
+ gettimeofday(&end_time, NULL);
+
+ alloc_time += (end_time.tv_sec - start_time.tv_sec) * 1000000L +
+ (end_time.tv_usec - start_time.tv_usec);
+
+ MemoryContextSwitchTo(oldcxt);
+
+ mem_allocated = MemoryContextMemAllocated(cxt, true);
+
+ /* do the requested number of free/alloc loops */
+ for (j = 0; j < nloops; j++)
+ {
+ CHECK_FOR_INTERRUPTS();
+
+ /* randomize the indexes */
+ for (i = 0; i < nallocs; i++)
+ chunks[i].random = random();
+
+ qsort(chunks, nallocs, sizeof(Chunk), chunk_index_cmp);
+
+ oldcxt = MemoryContextSwitchTo(cxt);
+
+ gettimeofday(&start_time, NULL);
+
+ /* free the first free_cnt chunks */
+ for (i = 0; i < Min(nallocs, free_cnt); i++)
+ pfree(chunks[i].ptr);
+
+ gettimeofday(&end_time, NULL);
+
+ nallocs -= Min(nallocs, free_cnt);
+
+ free_time += (end_time.tv_sec - start_time.tv_sec) * 1000000L +
+ (end_time.tv_usec - start_time.tv_usec);
+
+ memmove(chunks, &chunks[free_cnt], nallocs * sizeof(Chunk));
+
+
+ /* allocate alloc_cnt chunks at the end */
+ gettimeofday(&start_time, NULL);
+
+ /* free the first free_cnt chunks */
+ for (i = 0; i < alloc_cnt; i++)
+ {
+ int chunkSize = minChunkSize + random() % (maxChunkSize - minChunkSize);
+
+ chunks[nallocs + i].ptr = palloc(chunkSize);
+ }
+
+ gettimeofday(&end_time, NULL);
+
+ nallocs += alloc_cnt;
+
+ alloc_time += (end_time.tv_sec - start_time.tv_sec) * 1000000L +
+ (end_time.tv_usec - start_time.tv_usec);
+
+ MemoryContextSwitchTo(oldcxt);
+
+ mem_allocated = Max(mem_allocated, MemoryContextMemAllocated(cxt, true));
+ }
+
+ /* release the chunks in random order */
+ for (i = 0; i < nallocs; i++)
+ chunks[i].random = random();
+
+ qsort(chunks, nallocs, sizeof(Chunk), chunk_index_cmp);
+
+ gettimeofday(&start_time, NULL);
+
+ for (i = 0; i < nallocs; i++)
+ pfree(chunks[i].ptr);
+
+ gettimeofday(&end_time, NULL);
+
+ free_time += (end_time.tv_sec - start_time.tv_sec) * 1000000L +
+ (end_time.tv_usec - start_time.tv_usec);
+
+ /* Build a tuple descriptor for our result type */
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ values[0] = Int64GetDatum(mem_allocated);
+ values[1] = Int64GetDatum(alloc_time);
+ values[2] = Int64GetDatum(free_time);
+
+ memset(nulls, 0, sizeof(nulls));
+
+ tuple = heap_form_tuple(tupdesc, values, nulls);
+ result = HeapTupleGetDatum(tuple);
+
+ PG_RETURN_DATUM(result);
+}
+
+Datum
+generation_bench_fifo(PG_FUNCTION_ARGS)
+{
+ MemoryContext cxt,
+ oldcxt;
+ Chunk *chunks;
+ int64 i, j;
+ int64 nallocs = PG_GETARG_INT64(0);
+ int64 blockSize = PG_GETARG_INT64(1);
+ int64 minChunkSize = PG_GETARG_INT64(2);
+ int64 maxChunkSize = PG_GETARG_INT64(3);
+
+ int nloops = PG_GETARG_INT32(4);
+ int free_cnt = PG_GETARG_INT32(5);
+ int alloc_cnt = PG_GETARG_INT32(6);
+
+ struct timeval start_time,
+ end_time;
+ int64 alloc_time = 0,
+ free_time = 0;
+ int64 mem_allocated;
+
+ TupleDesc tupdesc;
+ Datum result;
+ HeapTuple tuple;
+ Datum values[9];
+ bool nulls[9];
+
+ int maxchunks;
+
+ maxchunks = nallocs + nloops * Max(0, alloc_cnt - free_cnt);
+
+ cxt = GenerationContextCreate(CurrentMemoryContext, "generation_bench", blockSize);
+
+ chunks = (Chunk *) palloc(maxchunks * sizeof(Chunk));
+
+ oldcxt = MemoryContextSwitchTo(cxt);
+
+ gettimeofday(&start_time, NULL);
+
+ for (i = 0; i < nallocs; i++)
+ {
+ int chunkSize = minChunkSize + random() % (maxChunkSize - minChunkSize);
+
+ chunks[i].ptr = palloc(chunkSize);
+ }
+
+ gettimeofday(&end_time, NULL);
+
+ alloc_time += (end_time.tv_sec - start_time.tv_sec) * 1000000L +
+ (end_time.tv_usec - start_time.tv_usec);
+
+ MemoryContextSwitchTo(oldcxt);
+
+ mem_allocated = MemoryContextMemAllocated(cxt, true);
+
+
+ /* do the requested number of free/alloc loops */
+ for (j = 0; j < nloops; j++)
+ {
+ CHECK_FOR_INTERRUPTS();
+
+ oldcxt = MemoryContextSwitchTo(cxt);
+
+ gettimeofday(&start_time, NULL);
+
+ /* free the first free_cnt chunks */
+ for (i = 0; i < Min(nallocs, free_cnt); i++)
+ pfree(chunks[i].ptr);
+
+ gettimeofday(&end_time, NULL);
+
+ nallocs -= Min(nallocs, free_cnt);
+
+ free_time += (end_time.tv_sec - start_time.tv_sec) * 1000000L +
+ (end_time.tv_usec - start_time.tv_usec);
+
+ memmove(chunks, &chunks[free_cnt], nallocs * sizeof(Chunk));
+
+ /* allocate alloc_cnt chunks at the end */
+ gettimeofday(&start_time, NULL);
+
+ /* free the first free_cnt chunks */
+ for (i = 0; i < alloc_cnt; i++)
+ {
+ int chunkSize = minChunkSize + random() % (maxChunkSize - minChunkSize);
+
+ chunks[nallocs + i].ptr = palloc(chunkSize);
+ }
+
+ gettimeofday(&end_time, NULL);
+
+ nallocs += alloc_cnt;
+
+ alloc_time += (end_time.tv_sec - start_time.tv_sec) * 1000000L +
+ (end_time.tv_usec - start_time.tv_usec);
+
+ MemoryContextSwitchTo(oldcxt);
+
+ mem_allocated = Max(mem_allocated, MemoryContextMemAllocated(cxt, true));
+ }
+
+
+ gettimeofday(&start_time, NULL);
+
+ for (i = 0; i < nallocs; i++)
+ pfree(chunks[i].ptr);
+
+ gettimeofday(&end_time, NULL);
+
+ free_time += (end_time.tv_sec - start_time.tv_sec) * 1000000L +
+ (end_time.tv_usec - start_time.tv_usec);
+
+ /* Build a tuple descriptor for our result type */
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ values[0] = Int64GetDatum(mem_allocated);
+ values[1] = Int64GetDatum(alloc_time);
+ values[2] = Int64GetDatum(free_time);
+
+ memset(nulls, 0, sizeof(nulls));
+
+ tuple = heap_form_tuple(tupdesc, values, nulls);
+ result = HeapTupleGetDatum(tuple);
+
+ PG_RETURN_DATUM(result);
+}
+
+Datum
+generation_bench_lifo(PG_FUNCTION_ARGS)
+{
+ MemoryContext cxt,
+ oldcxt;
+ Chunk *chunks;
+ int64 i, j;
+ int64 nallocs = PG_GETARG_INT64(0);
+ int64 blockSize = PG_GETARG_INT64(1);
+ int64 minChunkSize = PG_GETARG_INT64(2);
+ int64 maxChunkSize = PG_GETARG_INT64(3);
+
+ int nloops = PG_GETARG_INT32(4);
+ int free_cnt = PG_GETARG_INT32(5);
+ int alloc_cnt = PG_GETARG_INT32(6);
+
+ struct timeval start_time,
+ end_time;
+ int64 alloc_time = 0,
+ free_time = 0;
+ int64 mem_allocated;
+
+ TupleDesc tupdesc;
+ Datum result;
+ HeapTuple tuple;
+ Datum values[9];
+ bool nulls[9];
+
+ int maxchunks;
+
+ maxchunks = nallocs + nloops * Max(0, alloc_cnt - free_cnt);
+
+ cxt = GenerationContextCreate(CurrentMemoryContext, "generation_bench", blockSize);
+
+ chunks = (Chunk *) palloc(maxchunks * sizeof(Chunk));
+
+ oldcxt = MemoryContextSwitchTo(cxt);
+
+ /* palloc benchmark */
+ gettimeofday(&start_time, NULL);
+
+ for (i = 0; i < nallocs; i++)
+ {
+ int chunkSize = minChunkSize + random() % (maxChunkSize - minChunkSize);
+
+ chunks[i].ptr = palloc(chunkSize);
+ }
+
+ gettimeofday(&end_time, NULL);
+
+ alloc_time += (end_time.tv_sec - start_time.tv_sec) * 1000000L +
+ (end_time.tv_usec - start_time.tv_usec);
+
+ MemoryContextSwitchTo(oldcxt);
+
+ mem_allocated = MemoryContextMemAllocated(cxt, true);
+
+
+ /* do the requested number of free/alloc loops */
+ for (j = 0; j < nloops; j++)
+ {
+ CHECK_FOR_INTERRUPTS();
+
+ oldcxt = MemoryContextSwitchTo(cxt);
+
+ gettimeofday(&start_time, NULL);
+
+ /* free the first free_cnt chunks */
+ for (i = 1; i <= Min(nallocs, free_cnt); i++)
+ pfree(chunks[nallocs - i].ptr);
+
+ gettimeofday(&end_time, NULL);
+
+ nallocs -= Min(nallocs, free_cnt);
+
+ free_time += (end_time.tv_sec - start_time.tv_sec) * 1000000L +
+ (end_time.tv_usec - start_time.tv_usec);
+
+ /* allocate alloc_cnt chunks at the end */
+ gettimeofday(&start_time, NULL);
+
+ /* free the first free_cnt chunks */
+ for (i = 0; i < alloc_cnt; i++)
+ {
+ int chunkSize = minChunkSize + random() % (maxChunkSize - minChunkSize);
+
+ chunks[nallocs + i].ptr = palloc(chunkSize);
+ }
+
+ gettimeofday(&end_time, NULL);
+
+ nallocs += alloc_cnt;
+
+ alloc_time += (end_time.tv_sec - start_time.tv_sec) * 1000000L +
+ (end_time.tv_usec - start_time.tv_usec);
+
+ MemoryContextSwitchTo(oldcxt);
+
+ mem_allocated = Max(mem_allocated, MemoryContextMemAllocated(cxt, true));
+ }
+
+
+ gettimeofday(&start_time, NULL);
+
+ for (i = (nallocs - 1); i >= 0; i--)
+ pfree(chunks[i].ptr);
+
+ gettimeofday(&end_time, NULL);
+
+ free_time += (end_time.tv_sec - start_time.tv_sec) * 1000000L +
+ (end_time.tv_usec - start_time.tv_usec);
+
+ /* Build a tuple descriptor for our result type */
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ values[0] = Int64GetDatum(mem_allocated);
+ values[1] = Int64GetDatum(alloc_time);
+ values[2] = Int64GetDatum(free_time);
+
+ memset(nulls, 0, sizeof(nulls));
+
+ tuple = heap_form_tuple(tupdesc, values, nulls);
+ result = HeapTupleGetDatum(tuple);
+
+ MemoryContextDelete(cxt);
+
+ PG_RETURN_DATUM(result);
+}
diff --git a/contrib/generation_bench/generation_bench.control b/contrib/generation_bench/generation_bench.control
new file mode 100644
index 0000000000..8c81e22e6b
--- /dev/null
+++ b/contrib/generation_bench/generation_bench.control
@@ -0,0 +1,4 @@
+# generation_bench extension
+comment = 'functions for benchmarking generation context'
+default_version = '1.0'
+module_pathname = '$libdir/generation_bench'
--
2.31.1
[text/x-patch] 0002-Generation-grow-blocks-v2.patch (7.5K, ../36829a8a-63d0-c428-89d5-07e49561973e@enterprisedb.com/3-0002-Generation-grow-blocks-v2.patch)
download | inline diff:
From 3f858dfa27bd4faca7898d5edda1be5e60bb60cc Mon Sep 17 00:00:00 2001
From: Tomas Vondra <tomas.vondra@postgresql.org>
Date: Fri, 30 Jul 2021 23:53:52 +0200
Subject: [PATCH 2/4] Generation: grow blocks
---
contrib/generation_bench/generation_bench.c | 6 +--
src/backend/access/gist/gistvacuum.c | 2 +-
.../replication/logical/reorderbuffer.c | 2 +-
src/backend/utils/mmgr/generation.c | 53 ++++++++++++++-----
src/include/utils/memutils.h | 4 +-
5 files changed, 47 insertions(+), 20 deletions(-)
diff --git a/contrib/generation_bench/generation_bench.c b/contrib/generation_bench/generation_bench.c
index 1cff55b751..bc3d631205 100644
--- a/contrib/generation_bench/generation_bench.c
+++ b/contrib/generation_bench/generation_bench.c
@@ -69,7 +69,7 @@ generation_bench_random(PG_FUNCTION_ARGS)
maxchunks = nallocs + nloops * Max(0, alloc_cnt - free_cnt);
- cxt = GenerationContextCreate(CurrentMemoryContext, "generation_bench", blockSize);
+ cxt = GenerationContextCreate(CurrentMemoryContext, "generation_bench", blockSize, blockSize, 1024L * 1024L);
chunks = (Chunk *) palloc(maxchunks * sizeof(Chunk));
@@ -210,7 +210,7 @@ generation_bench_fifo(PG_FUNCTION_ARGS)
maxchunks = nallocs + nloops * Max(0, alloc_cnt - free_cnt);
- cxt = GenerationContextCreate(CurrentMemoryContext, "generation_bench", blockSize);
+ cxt = GenerationContextCreate(CurrentMemoryContext, "generation_bench", blockSize, blockSize, 1024L * 1024L);
chunks = (Chunk *) palloc(maxchunks * sizeof(Chunk));
@@ -339,7 +339,7 @@ generation_bench_lifo(PG_FUNCTION_ARGS)
maxchunks = nallocs + nloops * Max(0, alloc_cnt - free_cnt);
- cxt = GenerationContextCreate(CurrentMemoryContext, "generation_bench", blockSize);
+ cxt = GenerationContextCreate(CurrentMemoryContext, "generation_bench", blockSize, blockSize, 1024L * 1024L);
chunks = (Chunk *) palloc(maxchunks * sizeof(Chunk));
diff --git a/src/backend/access/gist/gistvacuum.c b/src/backend/access/gist/gistvacuum.c
index 0663193531..1818ed06fc 100644
--- a/src/backend/access/gist/gistvacuum.c
+++ b/src/backend/access/gist/gistvacuum.c
@@ -161,7 +161,7 @@ gistvacuumscan(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
*/
vstate.page_set_context = GenerationContextCreate(CurrentMemoryContext,
"GiST VACUUM page set context",
- 16 * 1024);
+ ALLOCSET_DEFAULT_SIZES);
oldctx = MemoryContextSwitchTo(vstate.page_set_context);
vstate.internal_page_set = intset_create();
vstate.empty_leaf_set = intset_create();
diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index 7378beb684..308d833292 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -329,7 +329,7 @@ ReorderBufferAllocate(void)
buffer->tup_context = GenerationContextCreate(new_ctx,
"Tuples",
- SLAB_LARGE_BLOCK_SIZE);
+ ALLOCSET_DEFAULT_SIZES);
hash_ctl.keysize = sizeof(TransactionId);
hash_ctl.entrysize = sizeof(ReorderBufferTXNByIdEnt);
diff --git a/src/backend/utils/mmgr/generation.c b/src/backend/utils/mmgr/generation.c
index 584cd614da..771a2525ca 100644
--- a/src/backend/utils/mmgr/generation.c
+++ b/src/backend/utils/mmgr/generation.c
@@ -60,7 +60,9 @@ typedef struct GenerationContext
MemoryContextData header; /* Standard memory-context fields */
/* Generational context parameters */
- Size blockSize; /* standard block size */
+ Size initBlockSize; /* initial block size */
+ Size maxBlockSize; /* maximum block size */
+ Size nextBlockSize; /* next block size to allocate */
GenerationBlock *block; /* current (most recently allocated) block */
dlist_head blocks; /* list of blocks */
@@ -196,7 +198,9 @@ static const MemoryContextMethods GenerationMethods = {
MemoryContext
GenerationContextCreate(MemoryContext parent,
const char *name,
- Size blockSize)
+ Size minContextSize,
+ Size initBlockSize,
+ Size maxBlockSize)
{
GenerationContext *set;
@@ -208,16 +212,20 @@ GenerationContextCreate(MemoryContext parent,
"padding calculation in GenerationChunk is wrong");
/*
- * First, validate allocation parameters. (If we're going to throw an
- * error, we should do so before the context is created, not after.) We
- * somewhat arbitrarily enforce a minimum 1K block size, mostly because
- * that's what AllocSet does.
+ * First, validate allocation parameters. Once these were regular runtime
+ * test and elog's, but in practice Asserts seem sufficient because nobody
+ * varies their parameters at runtime. We somewhat arbitrarily enforce a
+ * minimum 1K block size.
*/
- if (blockSize != MAXALIGN(blockSize) ||
- blockSize < 1024 ||
- !AllocHugeSizeIsValid(blockSize))
- elog(ERROR, "invalid blockSize for memory context: %zu",
- blockSize);
+ Assert(initBlockSize == MAXALIGN(initBlockSize) &&
+ initBlockSize >= 1024);
+ Assert(maxBlockSize == MAXALIGN(maxBlockSize) &&
+ maxBlockSize >= initBlockSize &&
+ AllocHugeSizeIsValid(maxBlockSize)); /* must be safe to double */
+ Assert(minContextSize == 0 ||
+ (minContextSize == MAXALIGN(minContextSize) &&
+ minContextSize >= 1024 &&
+ minContextSize <= maxBlockSize));
/*
* Allocate the context header. Unlike aset.c, we never try to combine
@@ -242,7 +250,9 @@ GenerationContextCreate(MemoryContext parent,
*/
/* Fill in GenerationContext-specific header fields */
- set->blockSize = blockSize;
+ set->initBlockSize = initBlockSize;
+ set->maxBlockSize = maxBlockSize;
+ set->nextBlockSize = initBlockSize;
set->block = NULL;
dlist_init(&set->blocks);
@@ -293,6 +303,9 @@ GenerationReset(MemoryContext context)
set->block = NULL;
+ /* Reset block size allocation sequence, too */
+ set->nextBlockSize = set->initBlockSize;
+
Assert(dlist_is_empty(&set->blocks));
}
@@ -329,9 +342,12 @@ GenerationAlloc(MemoryContext context, Size size)
GenerationBlock *block;
GenerationChunk *chunk;
Size chunk_size = MAXALIGN(size);
+ Size blockSize;
+
+ blockSize = (set->block) ? set->block->blksize : set->nextBlockSize;
/* is it an over-sized chunk? if yes, allocate special block */
- if (chunk_size > set->blockSize / 8)
+ if (chunk_size > (blockSize / 8))
{
Size blksize = chunk_size + Generation_BLOCKHDRSZ + Generation_CHUNKHDRSZ;
@@ -387,7 +403,16 @@ GenerationAlloc(MemoryContext context, Size size)
if ((block == NULL) ||
(block->endptr - block->freeptr) < Generation_CHUNKHDRSZ + chunk_size)
{
- Size blksize = set->blockSize;
+ Size blksize;
+
+ /*
+ * The first such block has size initBlockSize, and we double the
+ * space in each succeeding block, but not more than maxBlockSize.
+ */
+ blksize = set->nextBlockSize;
+ set->nextBlockSize <<= 1;
+ if (set->nextBlockSize > set->maxBlockSize)
+ set->nextBlockSize = set->maxBlockSize;
block = (GenerationBlock *) malloc(blksize);
diff --git a/src/include/utils/memutils.h b/src/include/utils/memutils.h
index ff872274d4..514c0bf75b 100644
--- a/src/include/utils/memutils.h
+++ b/src/include/utils/memutils.h
@@ -183,7 +183,9 @@ extern MemoryContext SlabContextCreate(MemoryContext parent,
/* generation.c */
extern MemoryContext GenerationContextCreate(MemoryContext parent,
const char *name,
- Size blockSize);
+ Size minContextSize,
+ Size initBlockSize,
+ Size maxBlockSize);
/*
* Recommended default alloc parameters, suitable for "ordinary" contexts
--
2.31.1
[text/x-patch] 0003-Generation-keeper-block-v2.patch (4.4K, ../36829a8a-63d0-c428-89d5-07e49561973e@enterprisedb.com/4-0003-Generation-keeper-block-v2.patch)
download | inline diff:
From e27b30bfb7b179b6a1b47c4907e562521e6fb5cd Mon Sep 17 00:00:00 2001
From: Tomas Vondra <tomas.vondra@postgresql.org>
Date: Sat, 31 Jul 2021 02:54:36 +0200
Subject: [PATCH 3/4] Generation: keeper block
---
src/backend/utils/mmgr/generation.c | 61 +++++++++++++++++++++++++----
1 file changed, 54 insertions(+), 7 deletions(-)
diff --git a/src/backend/utils/mmgr/generation.c b/src/backend/utils/mmgr/generation.c
index 771a2525ca..6c90416e27 100644
--- a/src/backend/utils/mmgr/generation.c
+++ b/src/backend/utils/mmgr/generation.c
@@ -65,6 +65,7 @@ typedef struct GenerationContext
Size nextBlockSize; /* next block size to allocate */
GenerationBlock *block; /* current (most recently allocated) block */
+ GenerationBlock *keeper; /* keeper block */
dlist_head blocks; /* list of blocks */
} GenerationContext;
@@ -254,6 +255,7 @@ GenerationContextCreate(MemoryContext parent,
set->maxBlockSize = maxBlockSize;
set->nextBlockSize = initBlockSize;
set->block = NULL;
+ set->keeper = NULL;
dlist_init(&set->blocks);
/* Finally, do the type-independent part of context creation */
@@ -302,6 +304,7 @@ GenerationReset(MemoryContext context)
}
set->block = NULL;
+ set->keeper = NULL;
/* Reset block size allocation sequence, too */
set->nextBlockSize = set->initBlockSize;
@@ -344,7 +347,7 @@ GenerationAlloc(MemoryContext context, Size size)
Size chunk_size = MAXALIGN(size);
Size blockSize;
- blockSize = (set->block) ? set->block->blksize : set->nextBlockSize;
+ blockSize = set->initBlockSize;
/* is it an over-sized chunk? if yes, allocate special block */
if (chunk_size > (blockSize / 8))
@@ -400,6 +403,26 @@ GenerationAlloc(MemoryContext context, Size size)
*/
block = set->block;
+ /*
+ * If we can't use the current block, and we have a keeper block with
+ * enough free space in it, use it as the block.
+ *
+ * XXX We don't want to do this when there's not enough free space
+ * (although the keeper block should be empty, so not sure if checking
+ * the space in the keeper block is necessary).
+ */
+ if (((block == NULL) ||
+ (block->endptr - block->freeptr) < Generation_CHUNKHDRSZ + chunk_size) &&
+ (set->keeper != NULL) &&
+ (set->keeper->endptr - set->keeper->freeptr) < Generation_CHUNKHDRSZ + chunk_size)
+ {
+ block = set->keeper;
+ set->keeper = NULL;
+
+ /* keeper block was not counted as allocated, so add it back */
+ context->mem_allocated += block->blksize;
+ }
+
if ((block == NULL) ||
(block->endptr - block->freeptr) < Generation_CHUNKHDRSZ + chunk_size)
{
@@ -524,16 +547,34 @@ GenerationFree(MemoryContext context, void *pointer)
if (block->nfree < block->nchunks)
return;
+ /* Also make sure the block is not marked as the current block. */
+ if (set->block == block)
+ set->block = NULL;
+
+ /* Keep the block for reuse, if we don't have one already. */
+ if (!set->keeper && block->blksize <= set->maxBlockSize)
+ {
+ /* reset the pointers before we use it as keeper block */
+ block->freeptr = ((char *) block) + Generation_BLOCKHDRSZ;
+ block->endptr = ((char *) block) + block->blksize;
+
+ block->nfree = 0;
+ block->nchunks = 0;
+
+ set->keeper = block;
+
+ /* keeper block is not counted as allocated */
+ context->mem_allocated -= block->blksize;
+
+ return;
+ }
+
/*
* The block is empty, so let's get rid of it. First remove it from the
* list of blocks, then return it to malloc().
*/
dlist_delete(&block->node);
- /* Also make sure the block is not marked as the current block. */
- if (set->block == block)
- set->block = NULL;
-
context->mem_allocated -= block->blksize;
free(block);
}
@@ -770,13 +811,19 @@ GenerationCheck(MemoryContext context)
nchunks;
char *ptr;
- total_allocated += block->blksize;
+ /*
+ * The keeper block in the list of blocks, but we don't consider it
+ * as allocated in memory accounting. So don't include it in the sum.
+ */
+ if (block != gen->keeper)
+ total_allocated += block->blksize;
/*
* nfree > nchunks is surely wrong, and we don't expect to see
* equality either, because such a block should have gotten freed.
*/
- if (block->nfree >= block->nchunks)
+ if ((block->nfree > block->nchunks) &&
+ ((block != gen->keeper) && (block->nfree == block->nchunks)))
elog(WARNING, "problem in Generation %s: number of free chunks %d in block %p exceeds %d allocated",
name, block->nfree, block, block->nchunks);
--
2.31.1
[text/x-patch] 0004-Generation-allocChunkLimit-v2.patch (3.0K, ../36829a8a-63d0-c428-89d5-07e49561973e@enterprisedb.com/5-0004-Generation-allocChunkLimit-v2.patch)
download | inline diff:
From 5de9bdbb42874a8b2706a80f4977c6009dfabbbf Mon Sep 17 00:00:00 2001
From: Tomas Vondra <tomas.vondra@postgresql.org>
Date: Sat, 31 Jul 2021 03:20:56 +0200
Subject: [PATCH 4/4] Generation: allocChunkLimit
---
src/backend/utils/mmgr/generation.c | 28 ++++++++++++++++++++++++----
1 file changed, 24 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/mmgr/generation.c b/src/backend/utils/mmgr/generation.c
index 6c90416e27..2c98877953 100644
--- a/src/backend/utils/mmgr/generation.c
+++ b/src/backend/utils/mmgr/generation.c
@@ -46,6 +46,8 @@
#define Generation_BLOCKHDRSZ MAXALIGN(sizeof(GenerationBlock))
#define Generation_CHUNKHDRSZ sizeof(GenerationChunk)
+#define Generation_CHUNK_FRACTION 8
+
typedef struct GenerationBlock GenerationBlock; /* forward reference */
typedef struct GenerationChunk GenerationChunk;
@@ -63,6 +65,7 @@ typedef struct GenerationContext
Size initBlockSize; /* initial block size */
Size maxBlockSize; /* maximum block size */
Size nextBlockSize; /* next block size to allocate */
+ Size allocChunkLimit; /* effective chunk size limit */
GenerationBlock *block; /* current (most recently allocated) block */
GenerationBlock *keeper; /* keeper block */
@@ -254,6 +257,17 @@ GenerationContextCreate(MemoryContext parent,
set->initBlockSize = initBlockSize;
set->maxBlockSize = maxBlockSize;
set->nextBlockSize = initBlockSize;
+
+ /*
+ * Compute the allocation chunk size limit for this context.
+ *
+ * Follows similar ideas as AllocSet, see aset.c for details ...
+ */
+ set->allocChunkLimit = maxBlockSize;
+ while ((Size) (set->allocChunkLimit + Generation_CHUNKHDRSZ) >
+ (Size) ((Size) (maxBlockSize - Generation_BLOCKHDRSZ) / Generation_CHUNK_FRACTION))
+ set->allocChunkLimit >>= 1;
+
set->block = NULL;
set->keeper = NULL;
dlist_init(&set->blocks);
@@ -345,12 +359,9 @@ GenerationAlloc(MemoryContext context, Size size)
GenerationBlock *block;
GenerationChunk *chunk;
Size chunk_size = MAXALIGN(size);
- Size blockSize;
-
- blockSize = set->initBlockSize;
/* is it an over-sized chunk? if yes, allocate special block */
- if (chunk_size > (blockSize / 8))
+ if (chunk_size > set->allocChunkLimit)
{
Size blksize = chunk_size + Generation_BLOCKHDRSZ + Generation_CHUNKHDRSZ;
@@ -427,6 +438,7 @@ GenerationAlloc(MemoryContext context, Size size)
(block->endptr - block->freeptr) < Generation_CHUNKHDRSZ + chunk_size)
{
Size blksize;
+ Size required_size;
/*
* The first such block has size initBlockSize, and we double the
@@ -437,6 +449,14 @@ GenerationAlloc(MemoryContext context, Size size)
if (set->nextBlockSize > set->maxBlockSize)
set->nextBlockSize = set->maxBlockSize;
+ /*
+ * If initBlockSize is less than ALLOC_CHUNK_LIMIT, we could need more
+ * space... but try to keep it a power of 2.
+ */
+ required_size = chunk_size + Generation_BLOCKHDRSZ + Generation_CHUNKHDRSZ;
+ while (blksize < required_size)
+ blksize <<= 1;
+
block = (GenerationBlock *) malloc(blksize);
if (block == NULL)
--
2.31.1
[application/vnd.oasis.opendocument.spreadsheet] generation i5.ods (889.8K, ../36829a8a-63d0-c428-89d5-07e49561973e@enterprisedb.com/6-generation%20i5.ods)
download
view thread (50+ messages) latest in thread
Message-ID: <36829a8a-63d0-c428-89d5-07e49561973e@enterprisedb.com>
Permalink: ../36829a8a-63d0-c428-89d5-07e49561973e@enterprisedb.com/
Also on: postgresql.org/message-id/36829a8a-63d0-c428-89d5-07e49561973e@enterprisedb.com
copy link · copy postgr.es · copy git am
reply Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: pgsql-hackers@postgresql.org
Cc: tomas.vondra@enterprisedb.com, dgrowleyml@gmail.com, andres@anarazel.de, tv@fuzzy.cz, pgsql-hackers@lists.postgresql.org
Subject: Re: Use generation context to speed up tuplesorts
In-Reply-To: <36829a8a-63d0-c428-89d5-07e49561973e@enterprisedb.com>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox