This is the mail archive of the libc-alpha@sourceware.org mailing list for the glibc project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[PATCH v2] Allow shrinking of arena heaps using mmap based onovercommit settings when available


On Mon, 13 Aug 2012 02:32:36 -0400, KOSAKI wrote:
> (Again, MADV_DONTNEED has good multi thread performance than
> PROT_NONE.)

Right, so I did another program[1] that uses malloc/free rather than the
system calls directly to get an idea of the kind of performance
improvement we would see in a multithreaded scenario and as it turns
out, there is a significant difference as you said.

The program I wrote, spawns 8 threads (conservative number for my 4
core box), each of them allocating 64MB in splits of 16MB
(MMAP_THRESHOLD adjusted to put these on arenas) and freeing them in
reverse order, resulting in a heap trim at each free call. Each of
those threads did 1024 such iterations, giving me 4096 samples. The
results were as follows:

With madvise:

RUNS=4096, TOTAL=9264596.000000, AVG=2261.864258
RUNS=4096, TOTAL=9541428.000000, AVG=2329.450195
RUNS=4096, TOTAL=9381367.000000, AVG=2290.372803
RUNS=4096, TOTAL=9495634.000000, AVG=2318.270020
RUNS=4096, TOTAL=9654729.000000, AVG=2357.111572
RUNS=4096, TOTAL=9202857.000000, AVG=2246.791260
RUNS=4096, TOTAL=9444178.000000, AVG=2305.707520
RUNS=4096, TOTAL=9105736.000000, AVG=2223.080078

With mmap:

RUNS=4096, TOTAL=10211110.000000, AVG=2492.946777
RUNS=4096, TOTAL=10429254.000000, AVG=2546.204590
RUNS=4096, TOTAL=10463869.000000, AVG=2554.655518
RUNS=4096, TOTAL=10599614.000000, AVG=2587.796387
RUNS=4096, TOTAL=10318008.000000, AVG=2519.044922
RUNS=4096, TOTAL=10509473.000000, AVG=2565.789307
RUNS=4096, TOTAL=10325819.000000, AVG=2520.951904
RUNS=4096, TOTAL=10497513.000000, AVG=2562.869385

which gives a roughly 300us advantage per call on average, which is
quite significant. However, since the madvise will still break when
vm.overcommit_memory == 2, I tried a different approach for the patch.
Instead of removing that check, I introduced some Linux-specific code
that checks the contents of /proc/sys/vm.overcommit_memory and if it is
2, uses MAP_FIXED to drop the PTEs. I have attached the patch for
review.

Regards,
Siddhesh

ChangeLog:

	* malloc/arena.c: Include malloc-sysdep.h.
	(shrink_heap): New static variable may_shrink_heap to decide if
	madvise is sufficient to shrink the heap or an unmap is needed.
	* sysdeps/generic/Makefile (sysdep_headers): Add
	malloc-sysdep.h as a dependency when building malloc.
	* sysdeps/generic/malloc-sysdep.h: New file.  Define
	new function check_may_shrink_heap.
	* sysdeps/unix/sysv/linux/Makefile (sysdep_headers): Add
	malloc-sysdep.h as dependency when building malloc.
	* sysdeps/unix/sysv/linux/malloc-sysdep.h: New file.  Define
	new function check_may_shrink_heap.


[1] The benchmark code:

#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <malloc.h>
#include <assert.h>

#define NTHR 8
#define BLOCKSIZE 16*1024*1024
#define COUNT 1024

pthread_mutex_t printf_lock = PTHREAD_MUTEX_INITIALIZER;

void *
thr (void *num)
{
  double total = 0.0;
  int i;

  for (i = 0; i < COUNT; i++)
    {
      /* 4 blocks of 16M each to fill my 64M heap.  */
      int j = 0;
      void *m[4];

      for (j = 0; j < 4; j++)
	{
	  m[j] = malloc (BLOCKSIZE);
	  assert (m[j] != NULL);
	  memset (m[j], 0, BLOCKSIZE);
	}

      for (j = 3; j >= 0; j--)
	{
	  struct timespec before, after;
	  double elapsed;

	  clock_gettime (CLOCK_MONOTONIC_RAW, &before);
	  free (m[j]);
	  clock_gettime (CLOCK_MONOTONIC_RAW, &after);
	  elapsed = (after.tv_sec - before.tv_sec) * 1000000;
	  elapsed += (after.tv_nsec - before.tv_nsec) / 1000;
	  total += elapsed;
	  pthread_mutex_lock (&printf_lock);
	  printf ("%d:%d:%d: %lf\n", (long)num, i, j, elapsed);
	  pthread_mutex_unlock (&printf_lock);
	}
    }
  pthread_mutex_lock (&printf_lock);
  for (i = 0; i < 80; i++)
    putchar ('=');
  putchar ('\n');
  printf ("RUNS=%d, TOTAL=%lf, AVG=%lf\n", COUNT * 4,
	  total, total / (COUNT * 4));
  pthread_mutex_unlock (&printf_lock);
}

int
main ()
{
  long i;
  pthread_t t[NTHR];

  mallopt (M_MMAP_THRESHOLD, 31 * 1024 * 1024);

  for (i = 0; i < NTHR; i++)
    {
      pthread_create (&t[i], NULL, thr, (void *)i);
    }

  for (i = 0; i < NTHR; i++)
    {
      pthread_join (t[i], NULL);
    }
  return 0;
}
diff --git a/malloc/arena.c b/malloc/arena.c
index 9e5e332..b7aacc4 100644
--- a/malloc/arena.c
+++ b/malloc/arena.c
@@ -20,6 +20,9 @@
 
 #include <stdbool.h>
 
+/* Get the implementation for check_may_shrink_heap.  */
+#include <malloc-sysdep.h>
+
 /* Compile-time constants.  */
 
 #define HEAP_MIN_SIZE (32*1024)
@@ -618,13 +621,17 @@ static int
 shrink_heap(heap_info *h, long diff)
 {
   long new_size;
+  static int may_shrink_heap = -1;
+
+  if (__builtin_expect (may_shrink_heap < 0, 0))
+    may_shrink_heap = check_may_shrink_heap ();
 
   new_size = (long)h->size - diff;
   if(new_size < (long)sizeof(*h))
     return -1;
-  /* Try to re-map the extra heap space freshly to save memory, and
-     make it inaccessible. */
-  if (__builtin_expect (__libc_enable_secure, 0))
+  /* Try to re-map the extra heap space freshly to save memory, and make it
+     inaccessible.  See malloc-sysdep.h to know when this is true.  */
+  if (__builtin_expect (may_shrink_heap, 0))
     {
       if((char *)MMAP((char *)h + new_size, diff, PROT_NONE,
 		      MAP_FIXED) == (char *) MAP_FAILED)
diff --git a/sysdeps/generic/Makefile b/sysdeps/generic/Makefile
index f74109d..247cdd3 100644
--- a/sysdeps/generic/Makefile
+++ b/sysdeps/generic/Makefile
@@ -20,6 +20,10 @@ ifeq ($(subdir),string)
 CFLAGS-wordcopy.c += -Wno-uninitialized
 endif
 
+ifeq ($(subdir),malloc)
+sysdep_headers += malloc-sysdep.h
+endif
+
 ifeq ($(subdir),elf)
 ifeq (yes:yes,$(build-shared):$(unwind-find-fde))
 # This is needed to support g++ v2 and v3.
diff --git a/sysdeps/generic/malloc-sysdep.h b/sysdeps/generic/malloc-sysdep.h
new file mode 100644
index 0000000..cbb34d6
--- /dev/null
+++ b/sysdeps/generic/malloc-sysdep.h
@@ -0,0 +1,27 @@
+/* Copyright (C) 2012 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+/* Force an unmap on heap shrink for setuid programs.  This is so that the
+   kernel immediately drops the PTE for the mapping.  */
+
+#include <unistd.h>
+
+static inline int
+check_may_shrink_heap (void)
+{
+  return __libc_enable_secure;
+}
diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile
index ddae686..f129f45 100644
--- a/sysdeps/unix/sysv/linux/Makefile
+++ b/sysdeps/unix/sysv/linux/Makefile
@@ -9,6 +9,7 @@ endif
 
 ifeq ($(subdir),malloc)
 CFLAGS-malloc.c += -DMORECORE_CLEARS=2
+sysdep_headers += malloc-sysdep.h
 endif
 
 ifeq ($(subdir),socket)
diff --git a/sysdeps/unix/sysv/linux/malloc-sysdep.h b/sysdeps/unix/sysv/linux/malloc-sysdep.h
new file mode 100644
index 0000000..4b66b70
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/malloc-sysdep.h
@@ -0,0 +1,57 @@
+/* Copyright (C) 2012 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+/* The Linux kernel overcommits address space by default and if there is not
+   enough memory available, it uses various parameters to decide the process to
+   kill.  It is however possible to disable or curb this overcommit behaviour
+   by setting the proc sysctl vm.overcommit_memory to the value '2' and with
+   that, a process is only allowed to use the maximum of a pre-determined
+   fraction of the total address space.  In such a case, we want to make sure
+   that we are judicious with our heap usage as well, and explicitly give away
+   the freed top of the heap to reduce our commit charge.  See the proc(5) man
+   page to know more about overcommit behaviour.
+   
+   Other than that, we also force an unmap for setuid programs.  */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+static inline int
+check_may_shrink_heap (void)
+{
+  int fd;
+
+  if (__libc_enable_secure)
+    return 1;
+
+  fd = open ("/proc/sys/vm/overcommit_memory", O_RDONLY);
+
+  if (fd != -1)
+    {
+      char val;
+      int n = read (fd, &val, 1);
+      if (n > 0)
+	val -= '0';
+
+      if (val == 2)
+	return 1;
+    }
+
+  return 0;
+}

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]