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.

Attachment: valgrind_demo.tar.gz
Description: application/gzip

From 85ef5cffe892aed72980898702e31095a13fe6e0 Mon Sep 17 00:00:00 2001
From: "Ivan N. Taranov" <i.tara...@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

Reply via email to