Improve microbench.js

- ensure handler behavior does not depend on n argument
- load and save reference file in node.js
- add -s filename option to name the output reference file
- add targets in Makefile for tests and bencharks using node.js
- fix incorrect timings when not using high resolution timer
- use performance timer in node.js
- output performance factor instead of percentage
- use smaller threshold by default
- add benchmarks for:
    date_parse(), prop_update(), prop_clone(), array_slice()
    global_func_call(),
This commit is contained in:
Charlie Gordon 2024-02-26 00:14:31 +01:00
parent 78db49cf95
commit 8180d3dd87
2 changed files with 293 additions and 101 deletions

View File

@ -525,10 +525,25 @@ testall-32: all test-32 microbench-32 test2o-32 test2-32
testall-complete: testall testall-32 testall-complete: testall testall-32
node-test:
node tests/test_closure.js
node tests/test_language.js
node tests/test_builtin.js
node tests/test_loop.js
node tests/test_bignum.js
node-microbench:
node tests/microbench.js -s microbench-node.txt
node --jitless tests/microbench.js -s microbench-node-jitless.txt
bench-v8: qjs bench-v8: qjs
make -C tests/bench-v8 make -C tests/bench-v8
./qjs -d tests/bench-v8/combined.js ./qjs -d tests/bench-v8/combined.js
node-bench-v8:
make -C tests/bench-v8
node --jitless tests/bench-v8/combined.js
tests/bjson.so: $(OBJDIR)/tests/bjson.pic.o tests/bjson.so: $(OBJDIR)/tests/bjson.pic.o
$(CC) $(LDFLAGS) -shared -o $@ $^ $(LIBS) $(CC) $(LDFLAGS) -shared -o $@ $^ $(LIBS)

View File

@ -23,6 +23,10 @@
* THE SOFTWARE. * THE SOFTWARE.
*/ */
if (typeof require !== 'undefined') {
var fs = require('fs');
}
function pad(str, n) { function pad(str, n) {
str += ""; str += "";
while (str.length < n) while (str.length < n)
@ -63,9 +67,9 @@ function toPrec(n, prec) {
var ref_data; var ref_data;
var log_data; var log_data;
var heads = [ "TEST", "N", "TIME (ns)", "REF (ns)", "SCORE (%)" ]; var heads = [ "TEST", "N", "TIME (ns)", "REF (ns)", "SCORE (1000)" ];
var widths = [ 22, 10, 9, 9, 9 ]; var widths = [ 22, 10, 9, 9, 9 ];
var precs = [ 0, 0, 2, 2, 2 ]; var precs = [ 0, 0, 2, 2, 0 ];
var total = [ 0, 0, 0, 0, 0 ]; var total = [ 0, 0, 0, 0, 0 ];
var total_score = 0; var total_score = 0;
var total_scale = 0; var total_scale = 0;
@ -89,13 +93,27 @@ function log_line() {
} }
var clocks_per_sec = 1000; var clocks_per_sec = 1000;
var max_iterations = 10; var max_iterations = 100;
var clock_threshold = 100; /* favoring short measuring spans */ var clock_threshold = 2; /* favoring short measuring spans */
var min_n_argument = 1; var min_n_argument = 1;
var get_clock = Date.now; var get_clock;
if (typeof(os) !== "undefined") { if (typeof performance !== "undefined") {
// use more precise clock on NodeJS
// need a method call on performance object
get_clock = () => performance.now();
} else
if (typeof os !== "undefined") {
// use more precise clock on QuickJS // use more precise clock on QuickJS
get_clock = os.now; get_clock = os.now;
} else {
// use Date.now and round up to the next millisecond
get_clock = () => {
var t0 = Date.now();
var t;
while ((t = Date.now()) == t0)
continue;
return t;
}
} }
function log_one(text, n, ti) { function log_one(text, n, ti) {
@ -109,7 +127,7 @@ function log_one(text, n, ti) {
ti = Math.round(ti * 100) / 100; ti = Math.round(ti * 100) / 100;
log_data[text] = ti; log_data[text] = ti;
if (typeof ref === "number") { if (typeof ref === "number") {
log_line(text, n, ti, ref, ti * 100 / ref); log_line(text, n, ti, ref, Math.round(ref * 1000 / ti));
total_score += ti * 100 / ref; total_score += ti * 100 / ref;
total_scale += 100; total_scale += 100;
} else { } else {
@ -121,28 +139,27 @@ function log_one(text, n, ti) {
function bench(f, text) function bench(f, text)
{ {
var i, j, n, t, t1, ti, nb_its, ref, ti_n, ti_n1, min_ti; var i, j, n, t, ti, nb_its, ref, ti_n, ti_n1;
nb_its = n = 1; nb_its = n = 1;
if (f.bench) { if (f.bench) {
ti_n = f(text); ti_n = f(text);
} else { } else {
// measure ti_n: the shortest time for an individual operation
ti_n = 1000000000; ti_n = 1000000000;
min_ti = clock_threshold / 10;
for(i = 0; i < 30; i++) { for(i = 0; i < 30; i++) {
// measure ti: the shortest time for max_iterations iterations
ti = 1000000000; ti = 1000000000;
for (j = 0; j < max_iterations; j++) { for (j = 0; j < max_iterations; j++) {
t = get_clock(); t = get_clock();
while ((t1 = get_clock()) == t)
continue;
nb_its = f(n); nb_its = f(n);
t = get_clock() - t;
if (nb_its < 0) if (nb_its < 0)
return; // test failure return; // test failure
t1 = get_clock() - t1; if (ti > t)
if (ti > t1) ti = t;
ti = t1;
} }
if (ti >= min_ti) { if (ti >= clock_threshold / 10) {
ti_n1 = ti / nb_its; ti_n1 = ti / nb_its;
if (ti_n > ti_n1) if (ti_n > ti_n1)
ti_n = ti_n1; ti_n = ti_n1;
@ -196,6 +213,32 @@ function date_now(n) {
return n; return n;
} }
function date_parse(n) {
var x0 = 0, dx = 0;
var j;
for(j = 0; j < n; j++) {
var x1 = x0 - x0 % 1000;
var x2 = -x0;
var x3 = -x1;
var d0 = new Date(x0);
var d1 = new Date(x1);
var d2 = new Date(x2);
var d3 = new Date(x3);
if (Date.parse(d0.toISOString()) != x0
|| Date.parse(d1.toGMTString()) != x1
|| Date.parse(d1.toString()) != x1
|| Date.parse(d2.toISOString()) != x2
|| Date.parse(d3.toGMTString()) != x3
|| Date.parse(d3.toString()) != x3) {
console.log("Date.parse error for " + x0);
return -1;
}
dx = (dx * 1.1 + 1) >> 0;
x0 = (x0 + dx) % 8.64e15;
}
return n * 6;
}
function prop_read(n) function prop_read(n)
{ {
var obj, sum, j; var obj, sum, j;
@ -224,30 +267,78 @@ function prop_write(n)
return n * 4; return n * 4;
} }
function prop_create(n) function prop_update(n)
{ {
var obj, j; var obj, j;
obj = {a: 1, b: 2, c:3, d:4 };
for(j = 0; j < n; j++) { for(j = 0; j < n; j++) {
obj = new Object(); obj.a += j;
obj.a = 1; obj.b += j;
obj.b = 2; obj.c += j;
obj.c = 3; obj.d += j;
obj.d = 4;
} }
return n * 4; return n * 4;
} }
function prop_create(n)
{
var obj, i, j;
for(j = 0; j < n; j++) {
obj = {};
obj.a = 1;
obj.b = 2;
obj.c = 3;
obj.d = 4;
obj.e = 5;
obj.f = 6;
obj.g = 7;
obj.h = 8;
obj.i = 9;
obj.j = 10;
for(i = 0; i < 10; i++) {
obj[i] = i;
}
}
return n * 20;
}
function prop_clone(n)
{
var ref, obj, j, k;
ref = { a:1, b:2, c:3, d:4, e:5, f:6, g:7, h:8, i:9, j:10 };
for(k = 0; k < 10; k++) {
ref[k] = k;
}
for (j = 0; j < n; j++) {
global_res = { ...ref };
}
return n * 20;
}
function prop_delete(n) function prop_delete(n)
{ {
var obj, j; var ref, obj, j, k;
obj = {}; ref = { a:1, b:2, c:3, d:4, e:5, f:6, g:7, h:8, i:9, j:10 };
for(j = 0; j < n; j++) { for(k = 0; k < 10; k++) {
obj[j] = 1; ref[k] = k;
} }
for(j = 0; j < n; j++) { for (j = 0; j < n; j++) {
delete obj[j]; obj = { ...ref };
delete obj.a;
delete obj.b;
delete obj.c;
delete obj.d;
delete obj.e;
delete obj.f;
delete obj.g;
delete obj.h;
delete obj.i;
delete obj.j;
for(k = 0; k < 10; k++) {
delete obj[k];
}
} }
return n; return n * 20;
} }
function array_read(n) function array_read(n)
@ -308,15 +399,32 @@ function array_prop_create(n)
return len * n; return len * n;
} }
function array_slice(n)
{
var ref, a, i, j, len;
len = 1000;
ref = [];
for(i = 0; i < len; i++)
ref[i] = i;
for(j = 0; j < n; j++) {
ref[0] = j;
a = ref.slice();
a[0] = 0;
global_res = a;
}
return len * n;
}
function array_length_decr(n) function array_length_decr(n)
{ {
var tab, i, j, len; var tab, ref, i, j, len;
len = 1000; len = 1000;
tab = []; ref = [];
for(i = 0; i < len; i++) for(i = 0; i < len; i++)
tab[i] = i; ref[i] = i;
for(j = 0; j < n; j++) { for(j = 0; j < n; j++) {
for(i = len - 1; i >= 0; i--) tab = ref.slice();
for(i = len; i --> 0;)
tab.length = i; tab.length = i;
} }
return len * n; return len * n;
@ -324,15 +432,16 @@ function array_length_decr(n)
function array_hole_length_decr(n) function array_hole_length_decr(n)
{ {
var tab, i, j, len; var tab, ref, i, j, len;
len = 1000; len = 1000;
tab = []; ref = [];
for(i = 0; i < len; i++) { for(i = 0; i < len; i++) {
if (i != 3) if (i % 10 == 9)
tab[i] = i; ref[i] = i;
} }
for(j = 0; j < n; j++) { for(j = 0; j < n; j++) {
for(i = len - 1; i >= 0; i--) tab = ref.slice();
for(i = len; i --> 0;)
tab.length = i; tab.length = i;
} }
return len * n; return len * n;
@ -352,12 +461,13 @@ function array_push(n)
function array_pop(n) function array_pop(n)
{ {
var tab, i, j, len, sum; var tab, ref, i, j, len, sum;
len = 500; len = 500;
ref = [];
for(i = 0; i < len; i++)
ref[i] = i;
for(j = 0; j < n; j++) { for(j = 0; j < n; j++) {
tab = []; tab = ref.slice();
for(i = 0; i < len; i++)
tab[i] = i;
sum = 0; sum = 0;
for(i = 0; i < len; i++) for(i = 0; i < len; i++)
sum += tab.pop(); sum += tab.pop();
@ -429,6 +539,7 @@ function global_read(n)
return n * 4; return n * 4;
} }
// non strict version
var global_write = var global_write =
(1, eval)(`(function global_write(n) (1, eval)(`(function global_write(n)
{ {
@ -471,6 +582,7 @@ function local_destruct(n)
var global_v1, global_v2, global_v3, global_v4; var global_v1, global_v2, global_v3, global_v4;
var global_a, global_b, global_c, global_d; var global_a, global_b, global_c, global_d;
// non strict version
var global_destruct = var global_destruct =
(1, eval)(`(function global_destruct(n) (1, eval)(`(function global_destruct(n)
{ {
@ -498,6 +610,25 @@ function global_destruct_strict(n)
return n * 8; return n * 8;
} }
function g(a)
{
return 1;
}
function global_func_call(n)
{
var j, sum;
sum = 0;
for(j = 0; j < n; j++) {
sum += g(j);
sum += g(j);
sum += g(j);
sum += g(j);
}
global_res = sum;
return n * 4;
}
function func_call(n) function func_call(n)
{ {
function f(a) function f(a)
@ -517,7 +648,7 @@ function func_call(n)
return n * 4; return n * 4;
} }
function closure_var(n) function func_closure_call(n)
{ {
function f(a) function f(a)
{ {
@ -622,8 +753,8 @@ function bigint256_arith(n)
function set_collection_add(n) function set_collection_add(n)
{ {
var s, i, j, len = 100; var s, i, j, len = 100;
s = new Set();
for(j = 0; j < n; j++) { for(j = 0; j < n; j++) {
s = new Set();
for(i = 0; i < len; i++) { for(i = 0; i < len; i++) {
s.add(String(i), i); s.add(String(i), i);
} }
@ -637,25 +768,25 @@ function set_collection_add(n)
function array_for(n) function array_for(n)
{ {
var r, i, j, sum; var r, i, j, sum, len = 100;
r = []; r = [];
for(i = 0; i < 100; i++) for(i = 0; i < len; i++)
r[i] = i; r[i] = i;
for(j = 0; j < n; j++) { for(j = 0; j < n; j++) {
sum = 0; sum = 0;
for(i = 0; i < 100; i++) { for(i = 0; i < len; i++) {
sum += r[i]; sum += r[i];
} }
global_res = sum; global_res = sum;
} }
return n * 100; return n * len;
} }
function array_for_in(n) function array_for_in(n)
{ {
var r, i, j, sum; var r, i, j, sum, len = 100;
r = []; r = [];
for(i = 0; i < 100; i++) for(i = 0; i < len; i++)
r[i] = i; r[i] = i;
for(j = 0; j < n; j++) { for(j = 0; j < n; j++) {
sum = 0; sum = 0;
@ -664,14 +795,14 @@ function array_for_in(n)
} }
global_res = sum; global_res = sum;
} }
return n * 100; return n * len;
} }
function array_for_of(n) function array_for_of(n)
{ {
var r, i, j, sum; var r, i, j, sum, len = 100;
r = []; r = [];
for(i = 0; i < 100; i++) for(i = 0; i < len; i++)
r[i] = i; r[i] = i;
for(j = 0; j < n; j++) { for(j = 0; j < n; j++) {
sum = 0; sum = 0;
@ -680,7 +811,7 @@ function array_for_of(n)
} }
global_res = sum; global_res = sum;
} }
return n * 100; return n * len;
} }
function math_min(n) function math_min(n)
@ -700,11 +831,11 @@ function regexp_ascii(n)
var i, j, r, s; var i, j, r, s;
s = "the quick brown fox jumped over the lazy dog" s = "the quick brown fox jumped over the lazy dog"
for(j = 0; j < n; j++) { for(j = 0; j < n; j++) {
for(i = 0; i < 10000; i++) for(i = 0; i < 1000; i++)
r = /the quick brown fox/.exec(s) r = /the quick brown fox/.exec(s)
global_res = r; global_res = r;
} }
return n * 10000; return n * 1000;
} }
function regexp_utf16(n) function regexp_utf16(n)
@ -712,91 +843,91 @@ function regexp_utf16(n)
var i, j, r, s; var i, j, r, s;
s = "the quick brown ᶠᵒˣ jumped over the lazy ᵈᵒᵍ" s = "the quick brown ᶠᵒˣ jumped over the lazy ᵈᵒᵍ"
for(j = 0; j < n; j++) { for(j = 0; j < n; j++) {
for(i = 0; i < 10000; i++) for(i = 0; i < 1000; i++)
r = /the quick brown ᶠᵒˣ/.exec(s) r = /the quick brown ᶠᵒˣ/.exec(s)
global_res = r; global_res = r;
} }
return n * 10000; return n * 1000;
} }
/* incremental string contruction as local var */ /* incremental string contruction as local var */
function string_build1(n) function string_build1(n)
{ {
var i, j, r; var i, j, r;
r = "";
for(j = 0; j < n; j++) { for(j = 0; j < n; j++) {
for(i = 0; i < 100; i++) r = "";
for(i = 0; i < 1000; i++)
r += "x"; r += "x";
global_res = r; global_res = r;
} }
return n * 100; return n * 1000;
} }
/* incremental string contruction using + */ /* incremental string contruction using + */
function string_build1x(n) function string_build1x(n)
{ {
var i, j, r; var i, j, r;
r = "";
for(j = 0; j < n; j++) { for(j = 0; j < n; j++) {
for(i = 0; i < 100; i++) r = "";
for(i = 0; i < 1000; i++)
r = r + "x"; r = r + "x";
global_res = r; global_res = r;
} }
return n * 100; return n * 1000;
} }
/* incremental string contruction using +2c */ /* incremental string contruction using +2c */
function string_build2c(n) function string_build2c(n)
{ {
var i, j; var i, j;
var r = "";
for(j = 0; j < n; j++) { for(j = 0; j < n; j++) {
for(i = 0; i < 100; i++) var r = "";
for(i = 0; i < 1000; i++)
r += "xy"; r += "xy";
global_res = r; global_res = r;
} }
return n * 100; return n * 1000;
} }
/* incremental string contruction as arg */ /* incremental string contruction as arg */
function string_build2(n, r) function string_build2(n, r)
{ {
var i, j; var i, j;
r = "";
for(j = 0; j < n; j++) { for(j = 0; j < n; j++) {
for(i = 0; i < 100; i++) r = "";
for(i = 0; i < 1000; i++)
r += "x"; r += "x";
global_res = r; global_res = r;
} }
return n * 100; return n * 1000;
} }
/* incremental string contruction by prepending */ /* incremental string contruction by prepending */
function string_build3(n) function string_build3(n)
{ {
var i, j, r; var i, j, r;
r = "";
for(j = 0; j < n; j++) { for(j = 0; j < n; j++) {
for(i = 0; i < 100; i++) r = "";
for(i = 0; i < 1000; i++)
r = "x" + r; r = "x" + r;
global_res = r; global_res = r;
} }
return n * 100; return n * 1000;
} }
/* incremental string contruction with multiple reference */ /* incremental string contruction with multiple reference */
function string_build4(n) function string_build4(n)
{ {
var i, j, r, s; var i, j, r, s;
r = "";
for(j = 0; j < n; j++) { for(j = 0; j < n; j++) {
for(i = 0; i < 100; i++) { r = "";
for(i = 0; i < 1000; i++) {
s = r; s = r;
r += "x"; r += "x";
} }
global_res = r; global_res = r;
} }
return n * 100; return n * 1000;
} }
/* sort bench */ /* sort bench */
@ -940,21 +1071,22 @@ sort_bench.verbose = false;
function int_to_string(n) function int_to_string(n)
{ {
var s, r, j; var s, j;
r = 0;
for(j = 0; j < n; j++) { for(j = 0; j < n; j++) {
s = (j + 1).toString(); s = (j % 1000).toString();
s = (1234000 + j % 1000).toString();
} }
return n; global_res = s;
return n * 2;
} }
function float_to_string(n) function float_to_string(n)
{ {
var s, r, j; var s, j;
r = 0;
for(j = 0; j < n; j++) { for(j = 0; j < n; j++) {
s = (j + 0.1).toString(); s = (j + 0.1).toString();
} }
global_res = s;
return n; return n;
} }
@ -963,7 +1095,6 @@ function string_to_int(n)
var s, r, j; var s, r, j;
r = 0; r = 0;
s = "12345"; s = "12345";
r = 0;
for(j = 0; j < n; j++) { for(j = 0; j < n; j++) {
r += (s | 0); r += (s | 0);
} }
@ -976,7 +1107,6 @@ function string_to_float(n)
var s, r, j; var s, r, j;
r = 0; r = 0;
s = "12345.6"; s = "12345.6";
r = 0;
for(j = 0; j < n; j++) { for(j = 0; j < n; j++) {
r -= s; r -= s;
} }
@ -986,32 +1116,70 @@ function string_to_float(n)
function load_result(filename) function load_result(filename)
{ {
var f, str, res; var has_filename = filename;
if (typeof std === "undefined") var has_error = false;
var str, res;
if (!filename)
filename = "microbench.txt";
if (typeof fs !== "undefined") {
// read the file in Node.js
try {
str = fs.readFileSync(filename, { encoding: "utf8" });
} catch {
has_error = true;
}
} else
if (typeof std !== "undefined") {
// read the file in QuickJS
var f = std.open(filename, "r");
if (!f) {
has_error = true;
}
str = f.readAsString();
f.close();
} else {
return null; return null;
f = std.open(filename ? filename : "microbench.txt", "r"); }
if (!f) { if (has_error) {
if (filename) { if (has_filename) {
// Should throw exception? // Should throw exception?
console.log("cannot load " + filename); console.log("cannot load " + filename);
} }
return null; return null;
} }
str = f.readAsString();
res = JSON.parse(str); res = JSON.parse(str);
f.close();
return res; return res;
} }
function save_result(filename, obj) function save_result(filename, obj)
{ {
var f; var str = JSON.stringify(obj, null, 2) + "\n";
if (typeof std === "undefined") var has_error = false;
if (typeof fs !== "undefined") {
// save the file in Node.js
try {
str = fs.writeFileSync(filename, str, { encoding: "utf8" });
} catch {
has_error = true;
}
} else
if (typeof std !== "undefined") {
// save the file in QuickJS
var f = std.open(filename, "w");
if (f) {
f.puts(str);
f.close();
} else {
has_error = 'true';
}
} else {
return; return;
f = std.open(filename, "w"); }
f.puts(JSON.stringify(obj, null, 2)); if (has_error)
f.puts("\n"); console.log("cannot save " + filename);
f.close();
} }
function main(argc, argv, g) function main(argc, argv, g)
@ -1022,13 +1190,17 @@ function main(argc, argv, g)
empty_down_loop2, empty_down_loop2,
empty_do_loop, empty_do_loop,
date_now, date_now,
date_parse,
prop_read, prop_read,
prop_write, prop_write,
prop_update,
prop_create, prop_create,
prop_clone,
prop_delete, prop_delete,
array_read, array_read,
array_write, array_write,
array_prop_create, array_prop_create,
array_slice,
array_length_decr, array_length_decr,
array_hole_length_decr, array_hole_length_decr,
array_push, array_push,
@ -1041,8 +1213,9 @@ function main(argc, argv, g)
local_destruct, local_destruct,
global_destruct, global_destruct,
global_destruct_strict, global_destruct_strict,
global_func_call,
func_call, func_call,
closure_var, func_closure_call,
int_arith, int_arith,
float_arith, float_arith,
set_collection_add, set_collection_add,
@ -1065,7 +1238,7 @@ function main(argc, argv, g)
]; ];
var tests = []; var tests = [];
var i, j, n, f, name, found; var i, j, n, f, name, found;
var ref_file; var ref_file, new_ref_file = "microbench-new.txt";
if (typeof BigInt === "function") { if (typeof BigInt === "function") {
/* BigInt test */ /* BigInt test */
@ -1101,6 +1274,10 @@ function main(argc, argv, g)
ref_file = argv[i++]; ref_file = argv[i++];
continue; continue;
} }
if (name == "-s") {
new_ref_file = argv[i++];
continue;
}
for (j = 0, found = false; j < test_list.length; j++) { for (j = 0, found = false; j < test_list.length; j++) {
f = test_list[j]; f = test_list[j];
if (f.name.startsWith(name)) { if (f.name.startsWith(name)) {
@ -1128,12 +1305,12 @@ function main(argc, argv, g)
n++; n++;
} }
if (ref_data) if (ref_data)
log_line("total", "", total[2], total[3], total_score * 100 / total_scale); log_line("total", "", total[2], total[3], Math.round(total_scale * 1000 / total_score));
else else
log_line("total", "", total[2]); log_line("total", "", total[2]);
if (tests == test_list) if (tests == test_list && new_ref_file)
save_result("microbench-new.txt", log_data); save_result(new_ref_file, log_data);
} }
if (typeof scriptArgs === "undefined") { if (typeof scriptArgs === "undefined") {