shithub: libmujs

Download patch

ref: b5eccea611ebc00d0b8b5c269f2e1f4af82e3c13
parent: c4c1524e97a99e06acb8382a7d325ce99bf553f9
author: Avi Halachmi (:avih) <avihpit@yahoo.com>
date: Sat Mar 28 12:06:17 EDT 2020

gc: use proportional instead of fixed threshold

The problem with a fixed count value is that it result in varying
degrees of performance and memory impact proportions depending on
the usage pattern of each script.

E.g. if a script keeps using the same 1M objects, then a threshold of
10K objects will result in GC cycles which free only 1% of objects,
which is hugely wasteful in terms of performance. On the other hand,
if a script only uses 100 objects then a threshold of 10K means it
uses 100 times more memory than it actually needs before GC triggers.

Now the threshold is a target memory usage factor (of the minimum
needed memory) which GC tries to aim at. This makes the GC impact
have constant proportions.

The default aims at a memory usage factor of 5, i.e. 80% garbage
and 20% remaining on each GC cycle.

The factor is only a target/goal because the actual overhead is not
known until GC completes. However, most scripts exhibit consistent
enough behavior such that the real overhead is within 10% or less of
the goal even when the usage pattern changes over time.

Within the v8 bench test suite, the actual GC threshold count varies
between ~50K to ~500K, where only one test (raytrace.js) stabilizes on
less than 10K (the previous fixed default) - at about 9K and its score
decreases by ~5%.

The splay.js score increases about x12 fold (or x50 fold from the
previous commit which counts properties), other tests quite a bit
less or none at all, and the overall score increases by nearly 40%.

Also, change the count type from int to unsigned int to get twice the
range. Preferably we should make it even bigger, maybe uint64_t.

For instance the splay.js v8 bench test surpasses million non-garbage
allocations within few seconds (that's why its score increased so much
with a proportional overhead), and the default GC threshold is 5 times
that number.

--- a/jsgc.c
+++ b/jsgc.c
@@ -130,8 +130,8 @@
 	js_Object *obj, *nextobj, **prevnextobj;
 	js_String *str, *nextstr, **prevnextstr;
 	js_Environment *env, *nextenv, **prevnextenv;
-	int nenv = 0, nfun = 0, nobj = 0, nstr = 0, nprop = 0;
-	int genv = 0, gfun = 0, gobj = 0, gstr = 0, gprop = 0;
+	unsigned int nenv = 0, nfun = 0, nobj = 0, nstr = 0, nprop = 0;
+	unsigned int genv = 0, gfun = 0, gobj = 0, gstr = 0, gprop = 0;
 	int mark;
 	int i;
 
@@ -141,8 +141,6 @@
 		return;
 	}
 
-	J->gccounter = 0;
-
 	mark = J->gcmark = J->gcmark == 1 ? 2 : 1;
 
 	/* Add initial roots. */
@@ -238,10 +236,17 @@
 		++nstr;
 	}
 
+	unsigned int ntot = nenv + nfun + nobj + nstr + nprop;
+	unsigned int gtot = genv + gfun + gobj + gstr + gprop;
+	unsigned int remaining = ntot - gtot;
+
+	J->gccounter = remaining;
+	J->gcthresh = remaining * JS_GCFACTOR;
+
 	if (report) {
 		char buf[256];
-		snprintf(buf, sizeof buf, "garbage collected: %d/%d envs, %d/%d funs, %d/%d objs, %d/%d props, %d/%d strs",
-			genv, nenv, gfun, nfun, gobj, nobj, gprop, nprop, gstr, nstr);
+		snprintf(buf, sizeof buf, "garbage collected (%d%%): %d/%d envs, %d/%d funs, %d/%d objs, %d/%d props, %d/%d strs",
+			100*gtot/ntot, genv, nenv, gfun, nfun, gobj, nobj, gprop, nprop, gstr, nstr);
 		js_report(J, buf);
 	}
 }
--- a/jsi.h
+++ b/jsi.h
@@ -78,8 +78,16 @@
 #ifndef JS_TRYLIMIT
 #define JS_TRYLIMIT 64		/* exception stack size */
 #endif
-#ifndef JS_GCLIMIT
-#define JS_GCLIMIT 10000	/* run gc cycle every N allocations */
+#ifndef JS_GCFACTOR
+/*
+ * GC will try to trigger when memory usage is this value times the minimum
+ * needed memory. E.g. if there are 100 remaining objects after GC and this
+ * value is 5.0, then the next GC will trigger when the overall number is 500.
+ * I.e. a value of 5.0 aims at 80% garbage, 20% remain-used on each GC.
+ * The bigger the value the less impact GC has on overall performance, but more
+ * memory is used and individual GC pauses are longer (but fewer).
+ */
+#define JS_GCFACTOR 5.0		/* memory overhead factor >= 1.0 */
 #endif
 #ifndef JS_ASTLIMIT
 #define JS_ASTLIMIT 100		/* max nested expressions */
@@ -233,7 +241,7 @@
 	/* garbage collector list */
 	int gcpause;
 	int gcmark;
-	int gccounter;
+	unsigned int gccounter, gcthresh;
 	js_Environment *gcenv;
 	js_Function *gcfun;
 	js_Object *gcobj;
--- a/jsrun.c
+++ b/jsrun.c
@@ -1356,7 +1356,7 @@
 	J->strict = F->strict;
 
 	while (1) {
-		if (J->gccounter > JS_GCLIMIT)
+		if (J->gccounter > J->gcthresh)
 			js_gc(J, 0);
 
 		J->trace[J->tracetop].line = *pc++;
--- a/jsstate.c
+++ b/jsstate.c
@@ -285,6 +285,7 @@
 
 	J->gcmark = 1;
 	J->nextref = 0;
+	J->gcthresh = 0; /* reaches stability within ~ 2-5 GC cycles */
 
 	J->R = jsV_newobject(J, JS_COBJECT, NULL);
 	J->G = jsV_newobject(J, JS_COBJECT, NULL);