aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--TODO2
-rw-r--r--configure.ac38
-rw-r--r--src/Makefile.am3
-rw-r--r--src/interface-curses/Makefile.am3
-rw-r--r--src/interface-gtk/Makefile.am3
-rw-r--r--src/memory.cpp183
6 files changed, 146 insertions, 86 deletions
diff --git a/TODO b/TODO
index 4f93a5e..d40db1e 100644
--- a/TODO
+++ b/TODO
@@ -84,8 +84,6 @@ Known Bugs:
Also, auto-completions within string-building constructs
(except Q-Reg specs) should generally be disabled since
the result will be unpredictable.
- * Shutdown seems to hang after generating lots of undo tokens:
- 500000<%A>EX$$
Features:
* Auto-indention could be implemented via context-sensitive
diff --git a/configure.ac b/configure.ac
index e3fd728..1bd446d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -172,15 +172,35 @@ case $host in
;;
esac
-# Check for optional glibc features.
-# Will probably only be found on Linux/glibc.
-AC_CHECK_HEADERS([malloc.h])
-AC_CHECK_FUNCS([malloc_trim mallinfo])
-
-# jemalloc-specific functions.
-# Will probably only be foudn on FreeBSD.
-AC_CHECK_HEADERS([malloc_np.h])
-AC_CHECK_FUNCS([mallctlnametomib mallctlbymib])
+# Check for optional libc features.
+# Some of this will only be found on glibc,
+# others on FreeBSD/jemalloc.
+AC_CHECK_HEADERS([malloc.h malloc_np.h])
+AC_CHECK_FUNCS([malloc_trim malloc_usable_size])
+
+# Check whether compiler supports global sized deallocations.
+# If yes, this will improve the memory limiting fallback
+# implementation.
+# Once we can depend on C++14, this check is no longer necessary.
+AC_CACHE_CHECK([if C++ compiler supports -fsized-deallocation],
+ [sciteco_cv_sized_deallocation_result], [
+ AC_LANG_PUSH(C++)
+ OLD_CXXFLAGS="$CXXFLAGS"
+ CXXFLAGS="$CXXFLAGS -fsized-deallocation"
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
+ [[#include <new>]],
+ [[(::operator delete)(0, 256)]]
+ )], sciteco_cv_sized_deallocation_result=yes,
+ sciteco_cv_sized_deallocation_result=no)
+ CXXFLAGS="$OLD_CXXFLAGS"
+ AC_LANG_POP(C++)
+])
+if [[ x$sciteco_cv_sized_deallocation_result = xyes ]]; then
+ AC_DEFINE(HAVE_SIZED_DEALLOCATION, 1,
+ [C++ compiler supports -fsized-deallocation])
+fi
+AM_CONDITIONAL(HAVE_SIZED_DEALLOCATION,
+ test "$sciteco_cv_sized_deallocation_result" = yes)
#
# Config options
diff --git a/src/Makefile.am b/src/Makefile.am
index ba21109..5ca4159 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -15,6 +15,9 @@ AM_CXXFLAGS = -Wall -Wno-char-subscripts
if CLANG
AM_CXXFLAGS += -Wno-mismatched-tags
endif
+if HAVE_SIZED_DEALLOCATION
+AM_CXXFLAGS += -fsized-deallocation
+endif
if STATIC_EXECUTABLES
# AM_LDFLAGS are libtool flags, NOT compiler/linker flags
diff --git a/src/interface-curses/Makefile.am b/src/interface-curses/Makefile.am
index e9a2d14..bd61e14 100644
--- a/src/interface-curses/Makefile.am
+++ b/src/interface-curses/Makefile.am
@@ -4,6 +4,9 @@ AM_CXXFLAGS = -std=c++11 -Wall -Wno-char-subscripts
if CLANG
AM_CXXFLAGS += -Wno-mismatched-tags
endif
+if HAVE_SIZED_DEALLOCATION
+AM_CXXFLAGS += -fsized-deallocation
+endif
noinst_LTLIBRARIES = libsciteco-interface.la
libsciteco_interface_la_SOURCES = interface-curses.cpp interface-curses.h \
diff --git a/src/interface-gtk/Makefile.am b/src/interface-gtk/Makefile.am
index dc4da3a..aa42871 100644
--- a/src/interface-gtk/Makefile.am
+++ b/src/interface-gtk/Makefile.am
@@ -5,6 +5,9 @@ AM_CXXFLAGS = -std=c++11 -Wall -Wno-char-subscripts
if CLANG
AM_CXXFLAGS += -Wno-mismatched-tags
endif
+if HAVE_SIZED_DEALLOCATION
+AM_CXXFLAGS += -fsized-deallocation
+endif
EXTRA_DIST = gtk-info-popup.gob \
gtk-canonicalized-label.gob
diff --git a/src/memory.cpp b/src/memory.cpp
index 15f8820..77980b5 100644
--- a/src/memory.cpp
+++ b/src/memory.cpp
@@ -19,8 +19,7 @@
#include "config.h"
#endif
-#include <stdint.h>
-
+/* for malloc_usable_size() */
#ifdef HAVE_MALLOC_H
#include <malloc.h>
#endif
@@ -28,6 +27,8 @@
#include <malloc_np.h>
#endif
+#include <new>
+
#include <glib.h>
#include "sciteco.h"
@@ -101,79 +102,30 @@ MemoryLimit memlimit;
* using malloc_trim(0).
* - The sbrk(0) method thus depends on implementation details
* of the libc.
- * - For these reasons, we rather stick to non-portable,
- * libc-specific, perhaps slow, but stable techniques to measure
- * memory usage.
- * Implementations for yet unsupported UNIXoid systems might
- * still want to pick up any of the ideas above, if they can be
- * proven to work well on those platforms.
- */
-
-#ifdef HAVE_MALLINFO
-/*
- * Linux/glibc-specific implementation.
- * Unfortunately, this slows things down when called frequently.
+ * - glibc and some other platforms have mallinfo().
+ * But at least on glibc it can get unbearably slow on programs
+ * with a lot of (virtual/resident) memory.
+ * Besides, mallinfo's API is broken on 64-bit systems, effectively
+ * limiting the enforcable memory limit to 4GB.
+ * Other glibc-specific introspection functions like malloc_info()
+ * can be even slower because of the syscalls required.
+ * - Linux has /proc/self/stat and /proc/self/statm but polling them
+ * is very inefficient.
+ * - FreeBSD/jemalloc has mallctl("stats.allocated") which even when
+ * optimized is significantly slower than the fallback but generally
+ * acceptable.
+ * - On all other platforms we (have to) rely on the fallback
+ * implementation based on C++ allocators/deallocators.
+ * They have been improved significantly to count as much memory
+ * as possible, even using libc-specific APIs like malloc_usable_size().
+ * Since this has been proven to work sufficiently well even on FreeBSD,
+ * there is no longer any UNIX-specific implementation.
+ * Even the malloc_usable_size() workaround for old or non-GNU
+ * compilers is still faster than mallctl() on FreeBSD.
+ * This might need to change in the future.
*/
-gsize
-MemoryLimit::get_usage(void)
-{
- struct mallinfo info = mallinfo();
-
- /*
- * NOTE: `uordblks` is an int and thus prone
- * to wrap-around issues.
- *
- * Unfortunately, the only other machine readable
- * alternative is malloc_info() which prints
- * into a FILE * stream [sic!] and is unspeakably
- * slow even if writing to an unbuffered fmemopen()ed
- * stream.
- */
- return info.uordblks;
-}
-
-#elif defined(HAVE_MALLCTLNAMETOMIB) && defined(HAVE_MALLCTLBYMIB)
-/*
- * FreeBSD/jemalloc-specific implementation.
- * Unfortunately, this slows things down when called frequently.
- */
-
-gsize
-MemoryLimit::get_usage(void)
-{
- static size_t epoch_mib[1] = {0};
- static size_t stats_allocated_mib[2] = {0};
-
- uint64_t epoch = 1;
- size_t stats_allocated;
- size_t stats_allocated_len = sizeof(stats_allocated);
-
- if (G_UNLIKELY(!epoch_mib[0])) {
- size_t len;
- int rc;
-
- len = G_N_ELEMENTS(epoch_mib);
- rc = mallctlnametomib("epoch", epoch_mib, &len);
- g_assert(rc == 0 && len == G_N_ELEMENTS(epoch_mib));
-
- len = G_N_ELEMENTS(stats_allocated_mib);
- rc = mallctlnametomib("stats.allocated",
- stats_allocated_mib, &len);
- g_assert(rc == 0 && len == G_N_ELEMENTS(stats_allocated_mib));
- }
-
- /* refresh statistics */
- mallctlbymib(epoch_mib, G_N_ELEMENTS(epoch_mib),
- NULL, NULL, &epoch, sizeof(epoch));
- /* query the total number of allocated bytes */
- mallctlbymib(stats_allocated_mib, G_N_ELEMENTS(stats_allocated_mib),
- &stats_allocated, &stats_allocated_len, NULL, 0);
-
- return stats_allocated;
-}
-
-#elif defined(G_OS_WIN32)
+#ifdef G_OS_WIN32
/*
* Uses the Windows-specific GetProcessMemoryInfo(),
* so the entire process heap is measured.
@@ -184,6 +136,8 @@ MemoryLimit::get_usage(void)
* counting the chunks with the MSVCRT-specific _minfo().
* Since we will always run against MSVCRT, the disadvantages
* discussed above for the UNIX-case may not be important.
+ * We might also just use the fallback implementation with some
+ * additional support for _msize().
*/
gsize
@@ -213,8 +167,11 @@ MemoryLimit::get_usage(void)
/*
* Portable fallback-implementation relying on C++11 sized allocators.
*
- * Unfortunately, this will only measure the heap used by C++ objects
- * in SciTECO's sources; not even Scintilla, nor all g_malloc() calls.
+ * Unfortunately, in the worst case, this will only measure the heap used
+ * by C++ objects in SciTECO's sources; not even Scintilla, nor all
+ * g_malloc() calls.
+ * Usually, we will be able to use global sized deallocators or
+ * libc-specific support to get more accurate results, though.
*/
#define MEMORY_USAGE_FALLBACK
@@ -264,6 +221,13 @@ MemoryLimit::check(void)
}
}
+/*
+ * The object-specific sized deallocators allow memory
+ * counting portably, even in strict C++11 mode.
+ * Once we depend on C++14, they and the entire `Object`
+ * class hack can be avoided.
+ */
+
void *
Object::operator new(size_t size) noexcept
{
@@ -303,3 +267,72 @@ Object::operator delete(void *ptr, size_t size) noexcept
}
} /* namespace SciTECO */
+
+#ifdef HAVE_SIZED_DEALLOCATION
+/*
+ * Global sized deallocators must be defined in the
+ * root namespace.
+ * Since we only depend on C++11, we cannot just always
+ * define them (they are a C++14 feature).
+ * They will effectively measure all C++ allocations,
+ * including the ones in Scintilla which can make a huge
+ * difference.
+ * Due to the poor platform support for memory measurement,
+ * it's still worth to support them optionally.
+ */
+
+void *
+operator new(size_t size)
+{
+#ifdef MEMORY_USAGE_FALLBACK
+ SciTECO::memory_usage += size;
+#endif
+
+ return g_malloc(size);
+}
+
+void
+operator delete(void *ptr, size_t size) noexcept
+{
+ g_free(ptr);
+
+#ifdef MEMORY_USAGE_FALLBACK
+ SciTECO::memory_usage -= size;
+#endif
+}
+
+#else
+/*
+ * In strict C++11, we can still use global non-sized
+ * deallocators.
+ * On their own, they bring little benefit, but with
+ * some libc-specific functionality, they can be used
+ * to improve the fallback memory measurements to include
+ * all allocations (including Scintilla).
+ * This comes with a moderate runtime penalty.
+ */
+
+void *
+operator new(size_t size)
+{
+ void *ptr = g_malloc(size);
+
+#if defined(MEMORY_USAGE_FALLBACK) && defined(HAVE_MALLOC_USABLE_SIZE)
+ /* NOTE: g_malloc() should always use the system malloc(). */
+ SciTECO::memory_usage += malloc_usable_size(ptr);
+#endif
+
+ return ptr;
+}
+
+void
+operator delete(void *ptr) noexcept
+{
+#if defined(MEMORY_USAGE_FALLBACK) && defined(HAVE_MALLOC_USABLE_SIZE)
+ if (ptr)
+ SciTECO::memory_usage -= malloc_usable_size(ptr);
+#endif
+ g_free(ptr);
+}
+
+#endif /* !HAVE_SIZED_DEALLOCATION */