mwrap (Perl version) user+dev discussion/patches/pulls/bugs/help
 help / color / mirror / code / Atom feed
* [PATCH] implement "age" concept
@ 2019-11-21 23:04 ew
  0 siblings, 0 replies; only message in thread
From: ew @ 2019-11-21 23:04 UTC (permalink / raw)
  To: mwrap-perl

Since Perl5 doesn't have a GC like Ruby and the concept of GC
generations, we'll instead rely on the total bytes allocated in
the process at the time of allocation so allocations can have
a relative (and high-granularity) age to compare each other
against.
---
 Mwrap.xs  | 76 +++++++++++++++++++++++++++++++++++++++++++++----------
 t/mwrap.t | 14 ++++++++++
 typemap   |  1 +
 3 files changed, 77 insertions(+), 14 deletions(-)

diff --git a/Mwrap.xs b/Mwrap.xs
index 6d3c6d2..ed51be4 100644
--- a/Mwrap.xs
+++ b/Mwrap.xs
@@ -26,6 +26,12 @@
 #include <urcu/rculist.h>
 #include "jhash.h"
 
+/*
+ * Perl doesn't have a GC the same way (C) Ruby does, so no GC count.
+ * Instead, the relative age of an object is the number of total bytes
+ * allocated (and we don't care about overflow on 32-bit since
+ * hardly anybody still uses it).
+ */
 static size_t total_bytes_inc, total_bytes_dec;
 
 extern pthread_key_t __attribute__((weak)) PL_thr_key;
@@ -194,6 +200,8 @@ struct src_loc {
 	size_t total;
 	size_t allocations;
 	size_t frees;
+	size_t age_total; /* (age_total / frees) => mean age at free */
+	size_t max_lifespan;
 	struct cds_lfht_node hnode;
 	struct cds_list_head allocs; /* <=> alloc_hdr.node */
 	uint32_t hval;
@@ -215,6 +223,7 @@ struct alloc_hdr {
 	struct cds_list_head anode; /* <=> src_loc.allocs */
 	union {
 		struct {
+			size_t gen; /* global age */
 			struct src_loc *loc;
 		} live;
 		struct rcu_head dead;
@@ -281,6 +290,8 @@ again:
 		if (!l) goto out_unlock;
 		memcpy(l, k, sizeof(*l) + n);
 		l->mtx = mutex_assign();
+		l->age_total = 0;
+		l->max_lifespan = 0;
 		l->frees = 0;
 		l->allocations = 1;
 		CDS_INIT_LIST_HEAD(&l->allocs);
@@ -301,17 +312,18 @@ static void update_stats_rcu_unlock(const struct src_loc *l)
 	if (caa_likely(l)) rcu_read_unlock();
 }
 
-static struct src_loc *update_stats_rcu_lock(size_t size, uintptr_t caller)
+static struct src_loc *
+update_stats_rcu_lock(size_t *generation, size_t size, uintptr_t caller)
 {
-	static const size_t xlen = sizeof(caller);
 	struct src_loc *k, *ret = 0;
+	static const size_t xlen = sizeof(caller);
 	char *dst;
 	const COP *cop;
 
 	if (caa_unlikely(!totals)) return 0;
 	if (locating++) goto out; /* do not recurse into another *alloc */
 
-	uatomic_add(&total_bytes_inc, size);
+	*generation = uatomic_add_return(&total_bytes_inc, size);
 	cop = PL_curcop;
 
 	rcu_read_lock();
@@ -379,12 +391,17 @@ void free(void *p)
 
 		if (!real_free) return; /* oh well, leak a little */
 		if (l) {
+			size_t current_bytes = uatomic_read(&total_bytes_inc);
+			size_t age = current_bytes - h->as.live.gen;
 			uatomic_add(&total_bytes_dec, h->size);
 			uatomic_set(&h->size, 0);
 			uatomic_add(&l->frees, 1);
+			uatomic_add(&l->age_total, age);
 
 			mutex_lock(l->mtx);
 			cds_list_del_rcu(&h->anode);
+			if (age > l->max_lifespan)
+				l->max_lifespan = age;
 			mutex_unlock(l->mtx);
 
 			call_rcu(&h->as.dead, free_hdr_rcu);
@@ -395,13 +412,15 @@ void free(void *p)
 }
 
 static void
-alloc_insert_rcu(struct src_loc *l, struct alloc_hdr *h, size_t size, void *real)
+alloc_insert_rcu(struct src_loc *l, struct alloc_hdr *h, size_t size,
+		void *real, size_t generation)
 {
 	/* we need src_loc to remain alive for the duration of this call */
 	if (!h) return;
 	h->size = size;
 	h->real = real;
 	h->as.live.loc = l;
+	h->as.live.gen = generation;
 	if (l) {
 		mutex_lock(l->mtx);
 		cds_list_add_rcu(&h->anode, &l->allocs);
@@ -433,6 +452,7 @@ internal_memalign(void **pp, size_t alignment, size_t size, uintptr_t caller)
 	struct alloc_hdr *h;
 	void *real;
 	size_t asize;
+	size_t generation = 0;
 	size_t d = alignment / sizeof(void*);
 	size_t r = alignment % sizeof(void*);
 
@@ -453,7 +473,7 @@ internal_memalign(void **pp, size_t alignment, size_t size, uintptr_t caller)
 	    __builtin_add_overflow(asize, sizeof(struct alloc_hdr), &asize))
 		return ENOMEM;
 
-	l = update_stats_rcu_lock(size, caller);
+	l = update_stats_rcu_lock(&generation, size, caller);
 
 	real = real_malloc(asize);
 	if (real) {
@@ -461,7 +481,7 @@ internal_memalign(void **pp, size_t alignment, size_t size, uintptr_t caller)
 		if (!ptr_is_aligned(p, alignment))
 			p = ptr_align(p, alignment);
 		h = ptr2hdr(p);
-		alloc_insert_rcu(l, h, size, real);
+		alloc_insert_rcu(l, h, size, real, generation);
 		*pp = p;
 	}
 	update_stats_rcu_unlock(l);
@@ -530,6 +550,7 @@ void *malloc(size_t size)
 	struct alloc_hdr *h;
 	size_t asize;
 	void *p;
+	size_t generation = 0;
 
 	if (__builtin_add_overflow(size, sizeof(struct alloc_hdr), &asize))
 		goto enomem;
@@ -545,10 +566,10 @@ void *malloc(size_t size)
 		real_malloc = dlsym(RTLD_NEXT, "malloc");
 	}
 #endif
-	l = update_stats_rcu_lock(size, RETURN_ADDRESS(0));
+	l = update_stats_rcu_lock(&generation, size, RETURN_ADDRESS(0));
 	p = h = real_malloc(asize);
 	if (h) {
-		alloc_insert_rcu(l, h, size, h);
+		alloc_insert_rcu(l, h, size, h, generation);
 		p = hdr2ptr(h);
 	}
 	update_stats_rcu_unlock(l);
@@ -565,6 +586,7 @@ void *calloc(size_t nmemb, size_t size)
 	struct src_loc *l;
 	struct alloc_hdr *h;
 	size_t asize;
+	size_t generation = 0;
 
 	if (__builtin_mul_overflow(size, nmemb, &size)) {
 		errno = ENOMEM;
@@ -575,10 +597,10 @@ void *calloc(size_t nmemb, size_t size)
 		return 0;
 	}
 	RETURN_IF_NOT_READY();
-	l = update_stats_rcu_lock(size, RETURN_ADDRESS(0));
+	l = update_stats_rcu_lock(&generation, size, RETURN_ADDRESS(0));
 	p = h = real_malloc(asize);
 	if (p) {
-		alloc_insert_rcu(l, h, size, h);
+		alloc_insert_rcu(l, h, size, h, generation);
 		p = hdr2ptr(h);
 		memset(p, 0, size);
 	}
@@ -593,6 +615,7 @@ void *realloc(void *ptr, size_t size)
 	struct src_loc *l;
 	struct alloc_hdr *h;
 	size_t asize;
+	size_t generation = 0;
 
 	if (!size) {
 		free(ptr);
@@ -604,10 +627,10 @@ void *realloc(void *ptr, size_t size)
 	}
 	RETURN_IF_NOT_READY();
 
-	l = update_stats_rcu_lock(size, RETURN_ADDRESS(0));
+	l = update_stats_rcu_lock(&generation, size, RETURN_ADDRESS(0));
 	p = h = real_malloc(asize);
 	if (p) {
-		alloc_insert_rcu(l, h, size, h);
+		alloc_insert_rcu(l, h, size, h, generation);
 		p = hdr2ptr(h);
 	}
 	update_stats_rcu_unlock(l);
@@ -758,17 +781,24 @@ BOOT:
 
 PROTOTYPES: ENABLE
 
+size_t
+mwrap_current_age()
+CODE:
+	RETVAL = uatomic_read(&total_bytes_inc);
+OUTPUT:
+	RETVAL
+
 size_t
 mwrap_total_bytes_allocated()
 CODE:
-	RETVAL = total_bytes_inc;
+	RETVAL = uatomic_read(&total_bytes_inc);
 OUTPUT:
 	RETVAL
 
 size_t
 mwrap_total_bytes_freed()
 CODE:
-	RETVAL = total_bytes_dec;
+	RETVAL = uatomic_read(&total_bytes_dec);
 OUTPUT:
 	RETVAL
 
@@ -839,6 +869,8 @@ CODE:
 		uatomic_set(&l->total, 0);
 		uatomic_set(&l->allocations, 0);
 		uatomic_set(&l->frees, 0);
+		uatomic_set(&l->age_total, 0);
+		uatomic_set(&l->max_lifespan, 0);
 	}
 	rcu_read_unlock();
 
@@ -932,6 +964,18 @@ OUTPUT:
 CLEANUP:
 	--locating;
 
+double
+src_loc_mean_lifespan(self)
+	Devel::Mwrap::SrcLoc self
+PREINIT:
+	size_t tot, frees;
+CODE:
+	frees = uatomic_read(&self->frees);
+	tot = uatomic_read(&self->age_total);
+	RETVAL = frees ? ((double)tot/(double)frees) : HUGE_VAL;
+OUTPUT:
+	RETVAL
+
 SV *
 src_loc_name(self)
 	Devel::Mwrap::SrcLoc self
@@ -956,6 +1000,7 @@ CODE:
 	++locating;
 	rcu_read_lock();
 	cds_list_for_each_entry_rcu(h, &self->allocs, anode) {
+		size_t gen = uatomic_read(&h->as.live.gen);
 		size_t size = uatomic_read(&h->size);
 
 		if (size > min) {
@@ -970,6 +1015,7 @@ CODE:
 			 * since that opens us up to use-after-free
 			 */
 			XPUSHs(sv_2mortal(newSVuv(size)));
+			XPUSHs(sv_2mortal(newSVuv(gen)));
 			PUTBACK;
 
 			call_sv(cb, G_DISCARD|G_EVAL);
@@ -990,3 +1036,5 @@ CODE:
 CLEANUP:
 	rcu_read_unlock();
 	--locating;
+
+
diff --git a/t/mwrap.t b/t/mwrap.t
index 661a90a..aba9709 100644
--- a/t/mwrap.t
+++ b/t/mwrap.t
@@ -38,6 +38,7 @@ SKIP: { # C++ program which uses malloc via "new"
 	mwrap_run('cmake (C++ new)', {}, '-e', 'system(qw(cmake -h)); exit $?');
 	my $res = slurp($out);
 	is($res, $exp, "`cmake -h' works");
+	diag slurp($err);
 };
 
 {
@@ -102,11 +103,24 @@ sub do_read () {
 for (1..$nr) { do_read() }
 my $loc = Devel::Mwrap::get('-e:6');
 $loc && $loc->total >= ($nbytes * $nr) or die "wrong line or bad stats";
+my $ml = $loc->mean_lifespan;
+$ml >= 0.0 or die "mean_lifespan broken";
 my @sl_each;
 $loc->each($nbytes, sub { push @sl_each, \@_ });
 my $n = scalar(@sl_each);
 $n == 1 or die "SrcLoc::each returned unexpected: $n";
 $sl_each[0]->[0] >= $nbytes or die "$sl_each[0]->[0] < $nbytes";
+
+my $age_before = $sl_each[0]->[1];
+$nbytes = 1024 * 1024 * 8;
+do_read() until Devel::Mwrap::current_age() > ($age_before + $nbytes);
+@sl_each = ();
+$loc->each($nbytes, sub { push @sl_each, \@_ });
+$n = scalar(@sl_each);
+$n == 1 or die "SrcLoc::each returned unexpected: $n";
+$sl_each[0]->[0] >= $nbytes or die "$sl_each[0]->[0] < $nbytes";
+my $age_after = $sl_each[0]->[1];
+$age_after >= $age_before or die "age did not increment";
 EOF
 diag slurp($out);
 
diff --git a/typemap b/typemap
index 9531289..0b0e4a3 100644
--- a/typemap
+++ b/typemap
@@ -2,3 +2,4 @@ TYPEMAP
 size_t	T_UV
 const char *	T_PV
 Devel::Mwrap::SrcLoc	T_PTROBJ
+double	T_DOUBLE

^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2019-11-21 23:04 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-11-21 23:04 [PATCH] implement "age" concept ew

Code repositories for project(s) associated with this public inbox

	https://80x24.org/mwrap-perl.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).