diff --git a/contrib/alloc_bench/.gitignore b/contrib/alloc_bench/.gitignore new file mode 100644 index 0000000000..5dcb3ff972 --- /dev/null +++ b/contrib/alloc_bench/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/alloc_bench/Makefile b/contrib/alloc_bench/Makefile new file mode 100644 index 0000000000..c672b29bdb --- /dev/null +++ b/contrib/alloc_bench/Makefile @@ -0,0 +1,21 @@ +# contrib/alloc_bench/Makefile + +MODULE_big = alloc_bench +OBJS = alloc_bench.o + +EXTENSION = alloc_bench +DATA = alloc_bench--1.0.sql +PGFILEDESC = "alloc_bench - memory context benchmarking functions" + +REGRESS = alloc_bench + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/alloc_bench +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/alloc_bench/alloc_bench--1.0.sql b/contrib/alloc_bench/alloc_bench--1.0.sql new file mode 100644 index 0000000000..2ca604a682 --- /dev/null +++ b/contrib/alloc_bench/alloc_bench--1.0.sql @@ -0,0 +1,9 @@ +/* alloc_bench--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION alloc_bench" to load this file. \quit + +CREATE FUNCTION alloc_bench(context_type text, pattern text, nchunks bigint, block_size bigint, chunk_size bigint, nloops int, random_segments int, out mem_allocated bigint, out alloc_us bigint, out free_us bigint) +AS 'MODULE_PATHNAME', 'alloc_bench' +LANGUAGE C VOLATILE STRICT; + diff --git a/contrib/alloc_bench/alloc_bench.c b/contrib/alloc_bench/alloc_bench.c new file mode 100644 index 0000000000..f76501c053 --- /dev/null +++ b/contrib/alloc_bench/alloc_bench.c @@ -0,0 +1,199 @@ +/*------------------------------------------------------------------------- + * + * alloc_bench.c + * + * helper functions to benchmark memory contexts with different workloads + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include + +#include "funcapi.h" +#include "miscadmin.h" +#include "utils/builtins.h" + +PG_MODULE_MAGIC; + +PG_FUNCTION_INFO_V1(alloc_bench); + +typedef enum AllocationPattern { + FIFO, + LIFO +} AllocationPattern; + +static int random_segment_size; +static int random_fifo = 0; + +static int +int64_random_compare(const void *a, const void *b) +{ + const int64 ia = *(const int64 *) a; + const int64 ib = *(const int64 *) b; + int rss = random_segment_size; + + if ((ia / rss * rss) + (random() % rss) > ib) + return random_fifo == 0 ? -1 : 1; + else + return random_fifo == 0 ? 1 : -1; +} + +Datum +alloc_bench(PG_FUNCTION_ARGS) +{ + MemoryContext cxt, + oldcxt; + void **chunks; + int64 *indexes; + int64 j; + char *context_type; + text *context_type_text = PG_GETARG_TEXT_PP(0); + char *pattern_str; + text *pattern_text = PG_GETARG_TEXT_PP(1); + int64 nchunks = PG_GETARG_INT64(2); + int64 blockSize = PG_GETARG_INT64(3); + int64 chunkSize = PG_GETARG_INT64(4); + + int nloops = PG_GETARG_INT32(5); + int random_segments = 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]; + AllocationPattern pattern; + + context_type = text_to_cstring(context_type_text); + + if (strcmp(context_type, "generation") == 0) + cxt = GenerationContextCreate(CurrentMemoryContext, + "alloc_bench", + ALLOCSET_DEFAULT_SIZES); + else if (strcmp(context_type, "aset") == 0) + cxt = AllocSetContextCreate(CurrentMemoryContext, + "alloc_bench", + ALLOCSET_DEFAULT_SIZES); + else if (strcmp(context_type, "slab") == 0) + cxt = SlabContextCreate(CurrentMemoryContext, + "alloc_bench", + blockSize, + chunkSize); + else + elog(ERROR, "%s is not a valid context type. context_type must be \"generation\", \"aset\" or \"slab\"", + context_type); + + pattern_str = text_to_cstring(pattern_text); + + if (strcmp(pattern_str, "fifo") == 0) + pattern = FIFO; + else if (strcmp(pattern_str, "lifo") == 0) + pattern = LIFO; + else + elog(ERROR, "%s is not a valid allocation pattern. Must be \"fifo\" or \"lifo\"", + pattern_str); + + chunks = (void **) palloc(nchunks * sizeof(void *)); + indexes = (int64 *) palloc(nchunks * sizeof(int64)); + + /* set the indexes so according to the allocation pattern */ + switch (pattern) + { + case FIFO: + for (int64 i = 0; i < nchunks; i++) + indexes[i] = i; + break; + case LIFO: + for (int64 i = 0; i < nchunks; i++) + indexes[i] = nchunks - i - 1; + break; + } + + /* + * When a non-zero random_segments is specified, we randomize the + * order of the pfree's so they're random within their own segment. + * This means we don't entirely randomize the order, but if there are + * say, 10 segments and 60 chunks, we only randomize the first 6 + * chunks then the next 6. Specifiying a lower number of + * random_segments means the FIFO or LIFO pattern becomes more random. + */ + if (random_segments > 0) + { + random_segment_size = nchunks / random_segments; + random_fifo = (pattern == FIFO); + qsort(indexes, nchunks, sizeof(int64), int64_random_compare); + } + + mem_allocated = 0; + + oldcxt = MemoryContextSwitchTo(cxt); + + /* do the requested number of pfree/palloc loops */ + for (j = 0; j < nloops; j++) + { + CHECK_FOR_INTERRUPTS(); + + gettimeofday(&start_time, NULL); + + /* + * We do palloc0 instead of palloc to simulate touching the + * memory. This will load cachelines, which is likely more + * realistic than just allocating memory and not doing + * anything with it. + */ + for (int64 i = 0; i < nchunks; i++) + chunks[i] = palloc0(chunkSize); + + gettimeofday(&end_time, NULL); + + alloc_time += (end_time.tv_sec - start_time.tv_sec) * 1000000L + + (end_time.tv_usec - start_time.tv_usec); + + gettimeofday(&start_time, NULL); + + for (int64 i = 0; i < nchunks; i++) + { + char *ptr = (char *) chunks[indexes[i]]; + + /* + * Free the chunk, but first touch the first cacheline + * of the chunk to simulate that we've just done + * something real with this memory. + */ + if (ptr[0] == '\0') + pfree(ptr); + } + + gettimeofday(&end_time, NULL); + + free_time += (end_time.tv_sec - start_time.tv_sec) * 1000000L + + (end_time.tv_usec - start_time.tv_usec); + + mem_allocated = Max(mem_allocated, MemoryContextMemAllocated(cxt, true)); + } + + MemoryContextSwitchTo(oldcxt); + MemoryContextDelete(cxt); + + /* 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); +} + diff --git a/contrib/alloc_bench/alloc_bench.control b/contrib/alloc_bench/alloc_bench.control new file mode 100644 index 0000000000..597480ed5e --- /dev/null +++ b/contrib/alloc_bench/alloc_bench.control @@ -0,0 +1,4 @@ +# alloc_bench extension +comment = 'functions for benchmarking memory context' +default_version = '1.0' +module_pathname = '$libdir/alloc_bench' diff --git a/contrib/alloc_bench/bench.sql b/contrib/alloc_bench/bench.sql new file mode 100644 index 0000000000..d476a386c2 --- /dev/null +++ b/contrib/alloc_bench/bench.sql @@ -0,0 +1,40 @@ +CREATE EXTENSION slab_bench; + +\o fifo-no-loops.data +select run, block_size, 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 slab_bench_fifo(1000000, block_size, chunk_size, 0, 0, 0) x; + +\o lifo-no-loops.data +select run, block_size, 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 slab_bench_lifo(1000000, block_size, chunk_size, 0, 0, 0) x; + +\o random-no-loops.data +select run, block_size, 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 slab_bench_random(1000000, block_size, chunk_size, 0, 0, 0) x; + + +\o fifo-increase.data +select run, block_size, 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 slab_bench_fifo(1000000, block_size, chunk_size, 100, 10000, 15000) x; + +\o lifo-increase.data +select run, block_size, 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 slab_bench_lifo(1000000, block_size, chunk_size, 100, 10000, 15000) x; + +\o random-increase.data +select run, block_size, 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 slab_bench_random(1000000, block_size, chunk_size, 100, 10000, 15000) x; + + +\o fifo-decrease.data +select run, block_size, 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 slab_bench_fifo(1000000, block_size, chunk_size, 100, 10000, 5000) x; + +\o lifo-decrease.data +select run, block_size, 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 slab_bench_lifo(1000000, block_size, chunk_size, 100, 10000, 5000) x; + +\o random-decrease.data +select run, block_size, 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 slab_bench_random(1000000, block_size, chunk_size, 100, 10000, 5000) x; + + +\o fifo-cycle.data +select run, block_size, 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 slab_bench_fifo(1000, block_size, chunk_size, 10000, 1000, 1000) x; + +\o lifo-cycle.data +select run, block_size, 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 slab_bench_lifo(1000, block_size, chunk_size, 10000, 1000, 1000) x; + +\o random-cycle.data +select run, block_size, 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 slab_bench_random(1000, block_size, chunk_size, 10000, 1000, 1000) x;