[PATCH] optimization of VALGRIND_MEMPOOL_* usage
Propose
Optimization of using the family of macros VALGRIND_MEMPOOL_*.
How?
- add the GetMemoryChunkCapacity method, which returns the size of
usable space in the chunk
- use GetMemoryChunkCapacity on VALGRIND_MEMPOOL_* calls
- call VALGRIND_MEMPOOL_CHANGED only for really changed chunks
*) Full patch code see in attachment
001-mem-chunk-capacity-and-repalloc-valgrind.patch
Why?
Under the valgrind control, the VALGRIND_MEMPOOL_CHANGE call works
very slooowly. With a large number of allocated memory chunks (a few
thousand and more) it is almost impossible to wait for the
program/test to done. This creates problems. During debugging and
auto-tests.
For example, below code is executed 90000ms on Core i7
for (int64 i = 0; i < 16000; ++i)
chunks[i] = palloc(64);
for (int64 i = 0; i < 16000; ++i)
chunks[i] = repalloc(chunks[i], 62);
With patch above - this code is executed 150ms.
*) Full extension code to demonstrate the problem see in attachment
valgrind_demo.tar.gz
An additional example
is the rum extension (https://github.com/postgrespro/rum).
To be able to perform the tests - need reduce the generate_series size
from 100K to 16K (https://github.com/postgrespro/rum/issues/76). But
under valgrind test execution remaining unacceptably sloow. Patch
above - completely solves the problem.
Attachments:
001-mem-chunk-capacity-and-repalloc-valgrind.patchtext/x-patch; charset=US-ASCII; name=001-mem-chunk-capacity-and-repalloc-valgrind.patchDownload
From 85ef5cffe892aed72980898702e31095a13fe6e0 Mon Sep 17 00:00:00 2001
From: "Ivan N. Taranov" <i.taranov@postgrespro.ru>
Date: Thu, 2 Apr 2020 12:22:38 +0300
Subject: [PATCH] +GetMemoryChunkCapacity +repalloc passes to VALGRIND only really changed chunks
diff --git a/src/backend/utils/mmgr/aset.c b/src/backend/utils/mmgr/aset.c
index c0623f106d2..7327f2e982e 100644
--- a/src/backend/utils/mmgr/aset.c
+++ b/src/backend/utils/mmgr/aset.c
@@ -272,6 +272,7 @@ static void *AllocSetRealloc(MemoryContext context, void *pointer, Size size);
static void AllocSetReset(MemoryContext context);
static void AllocSetDelete(MemoryContext context);
static Size AllocSetGetChunkSpace(MemoryContext context, void *pointer);
+static Size AllocSetGetChunkCapacity(MemoryContext context, void *pointer);
static bool AllocSetIsEmpty(MemoryContext context);
static void AllocSetStats(MemoryContext context,
MemoryStatsPrintFunc printfunc, void *passthru,
@@ -291,6 +292,7 @@ static const MemoryContextMethods AllocSetMethods = {
AllocSetReset,
AllocSetDelete,
AllocSetGetChunkSpace,
+ AllocSetGetChunkCapacity,
AllocSetIsEmpty,
AllocSetStats
#ifdef MEMORY_CONTEXT_CHECKING
@@ -1337,6 +1339,23 @@ AllocSetGetChunkSpace(MemoryContext context, void *pointer)
return result;
}
+/*
+ * AllocSetGetChunkCapacity
+ * Given a currently-allocated chunk, return the size of the
+ * usable space in the chunk.
+ */
+static Size
+AllocSetGetChunkCapacity(MemoryContext context, void *pointer)
+{
+ AllocChunk chunk = AllocPointerGetChunk(pointer);
+ Size result;
+
+ VALGRIND_MAKE_MEM_DEFINED(chunk, ALLOCCHUNK_PRIVATE_LEN);
+ result = chunk->size;
+ VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOCCHUNK_PRIVATE_LEN);
+ return result;
+}
+
/*
* AllocSetIsEmpty
* Is an allocset empty of any allocated space?
diff --git a/src/backend/utils/mmgr/generation.c b/src/backend/utils/mmgr/generation.c
index 56651d06931..09ccd811d48 100644
--- a/src/backend/utils/mmgr/generation.c
+++ b/src/backend/utils/mmgr/generation.c
@@ -152,6 +152,7 @@ static void *GenerationRealloc(MemoryContext context, void *pointer, Size size);
static void GenerationReset(MemoryContext context);
static void GenerationDelete(MemoryContext context);
static Size GenerationGetChunkSpace(MemoryContext context, void *pointer);
+static Size GenerationGetChunkCapacity(MemoryContext context, void *pointer);
static bool GenerationIsEmpty(MemoryContext context);
static void GenerationStats(MemoryContext context,
MemoryStatsPrintFunc printfunc, void *passthru,
@@ -171,6 +172,7 @@ static const MemoryContextMethods GenerationMethods = {
GenerationReset,
GenerationDelete,
GenerationGetChunkSpace,
+ GenerationGetChunkCapacity,
GenerationIsEmpty,
GenerationStats
#ifdef MEMORY_CONTEXT_CHECKING
@@ -666,6 +668,23 @@ GenerationGetChunkSpace(MemoryContext context, void *pointer)
return result;
}
+/*
+ * GenerationGetChunkCapacity
+ * Given a currently-allocated chunk, return the size of the
+ * usable space in the chunk.
+ */
+static Size
+GenerationGetChunkCapacity(MemoryContext context, void *pointer)
+{
+ GenerationChunk *chunk = GenerationPointerGetChunk(pointer);
+ Size result;
+
+ VALGRIND_MAKE_MEM_DEFINED(chunk, GENERATIONCHUNK_PRIVATE_LEN);
+ result = chunk->size;
+ VALGRIND_MAKE_MEM_NOACCESS(chunk, GENERATIONCHUNK_PRIVATE_LEN);
+ return result;
+}
+
/*
* GenerationIsEmpty
* Is a GenerationContext empty of any allocated space?
diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c
index 9e24fec72d6..4e5adf5c3ac 100644
--- a/src/backend/utils/mmgr/mcxt.c
+++ b/src/backend/utils/mmgr/mcxt.c
@@ -431,6 +431,21 @@ GetMemoryChunkSpace(void *pointer)
return context->methods->get_chunk_space(context, pointer);
}
+/*
+ * GetMemoryChunkCapacity
+ * Given a currently-allocated chunk, return the size of the
+ * usable space in the chunk.
+ *
+ * This is useful for measuring the space available for data on the chunk.
+ */
+Size
+GetMemoryChunkCapacity(void *pointer)
+{
+ MemoryContext context = GetMemoryChunkContext(pointer);
+
+ return context->methods->get_chunk_capacity(context, pointer);
+}
+
/*
* MemoryContextGetParent
* Get the parent context (if any) of the specified context
@@ -823,7 +838,7 @@ MemoryContextAlloc(MemoryContext context, Size size)
size, context->name)));
}
- VALGRIND_MEMPOOL_ALLOC(context, ret, size);
+ VALGRIND_MEMPOOL_ALLOC(context, ret, GetMemoryChunkCapacity(ret));
return ret;
}
@@ -859,7 +874,7 @@ MemoryContextAllocZero(MemoryContext context, Size size)
size, context->name)));
}
- VALGRIND_MEMPOOL_ALLOC(context, ret, size);
+ VALGRIND_MEMPOOL_ALLOC(context, ret, GetMemoryChunkCapacity(ret));
MemSetAligned(ret, 0, size);
@@ -897,7 +912,7 @@ MemoryContextAllocZeroAligned(MemoryContext context, Size size)
size, context->name)));
}
- VALGRIND_MEMPOOL_ALLOC(context, ret, size);
+ VALGRIND_MEMPOOL_ALLOC(context, ret, GetMemoryChunkCapacity(ret));
MemSetLoop(ret, 0, size);
@@ -937,7 +952,7 @@ MemoryContextAllocExtended(MemoryContext context, Size size, int flags)
return NULL;
}
- VALGRIND_MEMPOOL_ALLOC(context, ret, size);
+ VALGRIND_MEMPOOL_ALLOC(context, ret, GetMemoryChunkCapacity(ret));
if ((flags & MCXT_ALLOC_ZERO) != 0)
MemSetAligned(ret, 0, size);
@@ -971,7 +986,7 @@ palloc(Size size)
size, context->name)));
}
- VALGRIND_MEMPOOL_ALLOC(context, ret, size);
+ VALGRIND_MEMPOOL_ALLOC(context, ret, GetMemoryChunkCapacity(ret));
return ret;
}
@@ -1002,7 +1017,7 @@ palloc0(Size size)
size, context->name)));
}
- VALGRIND_MEMPOOL_ALLOC(context, ret, size);
+ VALGRIND_MEMPOOL_ALLOC(context, ret, GetMemoryChunkCapacity(ret));
MemSetAligned(ret, 0, size);
@@ -1040,7 +1055,7 @@ palloc_extended(Size size, int flags)
return NULL;
}
- VALGRIND_MEMPOOL_ALLOC(context, ret, size);
+ VALGRIND_MEMPOOL_ALLOC(context, ret, GetMemoryChunkCapacity(ret));
if ((flags & MCXT_ALLOC_ZERO) != 0)
MemSetAligned(ret, 0, size);
@@ -1069,7 +1084,10 @@ void *
repalloc(void *pointer, Size size)
{
MemoryContext context = GetMemoryChunkContext(pointer);
- void *ret;
+ void *ret;
+#ifdef USE_VALGRIND
+ Size prev_capacity;
+#endif
if (!AllocSizeIsValid(size))
elog(ERROR, "invalid memory alloc request size %zu", size);
@@ -1079,6 +1097,10 @@ repalloc(void *pointer, Size size)
/* isReset must be false already */
Assert(!context->isReset);
+#ifdef USE_VALGRIND
+ prev_capacity = GetMemoryChunkCapacity(pointer);
+#endif
+
ret = context->methods->realloc(context, pointer, size);
if (unlikely(ret == NULL))
{
@@ -1090,7 +1112,12 @@ repalloc(void *pointer, Size size)
size, context->name)));
}
- VALGRIND_MEMPOOL_CHANGE(context, pointer, ret, size);
+#ifdef USE_VALGRIND
+ if ((ret != pointer) || (GetMemoryChunkCapacity(ret) != prev_capacity))
+ {
+ VALGRIND_MEMPOOL_CHANGE(context, pointer, ret, GetMemoryChunkCapacity(ret));
+ }
+#endif
return ret;
}
@@ -1125,7 +1152,7 @@ MemoryContextAllocHuge(MemoryContext context, Size size)
size, context->name)));
}
- VALGRIND_MEMPOOL_ALLOC(context, ret, size);
+ VALGRIND_MEMPOOL_ALLOC(context, ret, GetMemoryChunkCapacity(ret));
return ret;
}
@@ -1139,7 +1166,10 @@ void *
repalloc_huge(void *pointer, Size size)
{
MemoryContext context = GetMemoryChunkContext(pointer);
- void *ret;
+ void *ret;
+#ifdef USE_VALGRIND
+ Size prev_capacity;
+#endif
if (!AllocHugeSizeIsValid(size))
elog(ERROR, "invalid memory alloc request size %zu", size);
@@ -1149,6 +1179,10 @@ repalloc_huge(void *pointer, Size size)
/* isReset must be false already */
Assert(!context->isReset);
+#ifdef USE_VALGRIND
+ prev_capacity = GetMemoryChunkCapacity(pointer);
+#endif
+
ret = context->methods->realloc(context, pointer, size);
if (unlikely(ret == NULL))
{
@@ -1160,7 +1194,12 @@ repalloc_huge(void *pointer, Size size)
size, context->name)));
}
- VALGRIND_MEMPOOL_CHANGE(context, pointer, ret, size);
+#ifdef USE_VALGRIND
+ if ((ret != pointer) || (GetMemoryChunkCapacity(ret) != prev_capacity))
+ {
+ VALGRIND_MEMPOOL_CHANGE(context, pointer, ret, GetMemoryChunkCapacity(ret));
+ }
+#endif
return ret;
}
diff --git a/src/backend/utils/mmgr/slab.c b/src/backend/utils/mmgr/slab.c
index c928476c479..e2c2c8ed8fd 100644
--- a/src/backend/utils/mmgr/slab.c
+++ b/src/backend/utils/mmgr/slab.c
@@ -132,6 +132,7 @@ static void *SlabRealloc(MemoryContext context, void *pointer, Size size);
static void SlabReset(MemoryContext context);
static void SlabDelete(MemoryContext context);
static Size SlabGetChunkSpace(MemoryContext context, void *pointer);
+static Size SlabGetChunkCapacity(MemoryContext context, void *pointer);
static bool SlabIsEmpty(MemoryContext context);
static void SlabStats(MemoryContext context,
MemoryStatsPrintFunc printfunc, void *passthru,
@@ -150,6 +151,7 @@ static const MemoryContextMethods SlabMethods = {
SlabReset,
SlabDelete,
SlabGetChunkSpace,
+ SlabGetChunkCapacity,
SlabIsEmpty,
SlabStats
#ifdef MEMORY_CONTEXT_CHECKING
@@ -630,6 +632,21 @@ SlabGetChunkSpace(MemoryContext context, void *pointer)
return slab->fullChunkSize;
}
+/*
+ * SlabGetChunkCapacity
+ * Given a currently-allocated chunk, return the size of the
+ * usable space in the chunk.
+ */
+static Size
+SlabGetChunkCapacity(MemoryContext context, void *pointer)
+{
+ SlabContext *slab = castNode(SlabContext, context);
+
+ Assert(slab);
+
+ return slab->chunkSize;
+}
+
/*
* SlabIsEmpty
* Is an Slab empty of any allocated space?
diff --git a/src/include/nodes/memnodes.h b/src/include/nodes/memnodes.h
index c9f2bbcb367..1d6d096ffe8 100644
--- a/src/include/nodes/memnodes.h
+++ b/src/include/nodes/memnodes.h
@@ -63,6 +63,7 @@ typedef struct MemoryContextMethods
void (*reset) (MemoryContext context);
void (*delete_context) (MemoryContext context);
Size (*get_chunk_space) (MemoryContext context, void *pointer);
+ Size (*get_chunk_capacity) (MemoryContext context, void *pointer);
bool (*is_empty) (MemoryContext context);
void (*stats) (MemoryContext context,
MemoryStatsPrintFunc printfunc, void *passthru,
diff --git a/src/include/utils/memutils.h b/src/include/utils/memutils.h
index 909bc2e9888..7bfbfee5ba1 100644
--- a/src/include/utils/memutils.h
+++ b/src/include/utils/memutils.h
@@ -80,6 +80,7 @@ extern void MemoryContextSetIdentifier(MemoryContext context, const char *id);
extern void MemoryContextSetParent(MemoryContext context,
MemoryContext new_parent);
extern Size GetMemoryChunkSpace(void *pointer);
+extern Size GetMemoryChunkCapacity(void *pointer);
extern MemoryContext MemoryContextGetParent(MemoryContext context);
extern bool MemoryContextIsEmpty(MemoryContext context);
extern Size MemoryContextMemAllocated(MemoryContext context, bool recurse);
--
2.26.0