From 8180d3dd879aeafa9fcd479ad174de88e73b3d15 Mon Sep 17 00:00:00 2001 From: Charlie Gordon Date: Mon, 26 Feb 2024 00:14:31 +0100 Subject: [PATCH] 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(), --- Makefile | 15 ++ tests/microbench.js | 379 ++++++++++++++++++++++++++++++++------------ 2 files changed, 293 insertions(+), 101 deletions(-) diff --git a/Makefile b/Makefile index 17a0b8e..0270a6a 100644 --- a/Makefile +++ b/Makefile @@ -525,10 +525,25 @@ testall-32: all test-32 microbench-32 test2o-32 test2-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 make -C tests/bench-v8 ./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 $(CC) $(LDFLAGS) -shared -o $@ $^ $(LIBS) diff --git a/tests/microbench.js b/tests/microbench.js index 6bf0b83..b70e3fb 100644 --- a/tests/microbench.js +++ b/tests/microbench.js @@ -23,6 +23,10 @@ * THE SOFTWARE. */ +if (typeof require !== 'undefined') { + var fs = require('fs'); +} + function pad(str, n) { str += ""; while (str.length < n) @@ -63,9 +67,9 @@ function toPrec(n, prec) { var ref_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 precs = [ 0, 0, 2, 2, 2 ]; +var precs = [ 0, 0, 2, 2, 0 ]; var total = [ 0, 0, 0, 0, 0 ]; var total_score = 0; var total_scale = 0; @@ -89,13 +93,27 @@ function log_line() { } var clocks_per_sec = 1000; -var max_iterations = 10; -var clock_threshold = 100; /* favoring short measuring spans */ +var max_iterations = 100; +var clock_threshold = 2; /* favoring short measuring spans */ var min_n_argument = 1; -var get_clock = Date.now; -if (typeof(os) !== "undefined") { +var get_clock; +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 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) { @@ -109,7 +127,7 @@ function log_one(text, n, ti) { ti = Math.round(ti * 100) / 100; log_data[text] = ti; 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_scale += 100; } else { @@ -121,28 +139,27 @@ function log_one(text, n, ti) { 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; if (f.bench) { ti_n = f(text); } else { + // measure ti_n: the shortest time for an individual operation ti_n = 1000000000; - min_ti = clock_threshold / 10; for(i = 0; i < 30; i++) { + // measure ti: the shortest time for max_iterations iterations ti = 1000000000; for (j = 0; j < max_iterations; j++) { t = get_clock(); - while ((t1 = get_clock()) == t) - continue; nb_its = f(n); + t = get_clock() - t; if (nb_its < 0) return; // test failure - t1 = get_clock() - t1; - if (ti > t1) - ti = t1; + if (ti > t) + ti = t; } - if (ti >= min_ti) { + if (ti >= clock_threshold / 10) { ti_n1 = ti / nb_its; if (ti_n > ti_n1) ti_n = ti_n1; @@ -196,6 +213,32 @@ function date_now(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) { var obj, sum, j; @@ -224,30 +267,78 @@ function prop_write(n) return n * 4; } -function prop_create(n) +function prop_update(n) { var obj, j; + obj = {a: 1, b: 2, c:3, d:4 }; for(j = 0; j < n; j++) { - obj = new Object(); - obj.a = 1; - obj.b = 2; - obj.c = 3; - obj.d = 4; + obj.a += j; + obj.b += j; + obj.c += j; + obj.d += j; } 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) { - var obj, j; - obj = {}; - for(j = 0; j < n; j++) { - obj[j] = 1; + 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++) { - delete obj[j]; + for (j = 0; j < n; 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) @@ -308,15 +399,32 @@ function array_prop_create(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) { - var tab, i, j, len; + var tab, ref, i, j, len; len = 1000; - tab = []; + ref = []; for(i = 0; i < len; i++) - tab[i] = i; + ref[i] = i; for(j = 0; j < n; j++) { - for(i = len - 1; i >= 0; i--) + tab = ref.slice(); + for(i = len; i --> 0;) tab.length = i; } return len * n; @@ -324,15 +432,16 @@ function array_length_decr(n) function array_hole_length_decr(n) { - var tab, i, j, len; + var tab, ref, i, j, len; len = 1000; - tab = []; + ref = []; for(i = 0; i < len; i++) { - if (i != 3) - tab[i] = i; + if (i % 10 == 9) + ref[i] = i; } for(j = 0; j < n; j++) { - for(i = len - 1; i >= 0; i--) + tab = ref.slice(); + for(i = len; i --> 0;) tab.length = i; } return len * n; @@ -352,12 +461,13 @@ function array_push(n) function array_pop(n) { - var tab, i, j, len, sum; + var tab, ref, i, j, len, sum; len = 500; + ref = []; + for(i = 0; i < len; i++) + ref[i] = i; for(j = 0; j < n; j++) { - tab = []; - for(i = 0; i < len; i++) - tab[i] = i; + tab = ref.slice(); sum = 0; for(i = 0; i < len; i++) sum += tab.pop(); @@ -429,6 +539,7 @@ function global_read(n) return n * 4; } +// non strict version var global_write = (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_a, global_b, global_c, global_d; +// non strict version var global_destruct = (1, eval)(`(function global_destruct(n) { @@ -498,6 +610,25 @@ function global_destruct_strict(n) 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 f(a) @@ -517,7 +648,7 @@ function func_call(n) return n * 4; } -function closure_var(n) +function func_closure_call(n) { function f(a) { @@ -622,8 +753,8 @@ function bigint256_arith(n) function set_collection_add(n) { var s, i, j, len = 100; - s = new Set(); for(j = 0; j < n; j++) { + s = new Set(); for(i = 0; i < len; i++) { s.add(String(i), i); } @@ -637,25 +768,25 @@ function set_collection_add(n) function array_for(n) { - var r, i, j, sum; + var r, i, j, sum, len = 100; r = []; - for(i = 0; i < 100; i++) + for(i = 0; i < len; i++) r[i] = i; for(j = 0; j < n; j++) { sum = 0; - for(i = 0; i < 100; i++) { + for(i = 0; i < len; i++) { sum += r[i]; } global_res = sum; } - return n * 100; + return n * len; } function array_for_in(n) { - var r, i, j, sum; + var r, i, j, sum, len = 100; r = []; - for(i = 0; i < 100; i++) + for(i = 0; i < len; i++) r[i] = i; for(j = 0; j < n; j++) { sum = 0; @@ -664,14 +795,14 @@ function array_for_in(n) } global_res = sum; } - return n * 100; + return n * len; } function array_for_of(n) { - var r, i, j, sum; + var r, i, j, sum, len = 100; r = []; - for(i = 0; i < 100; i++) + for(i = 0; i < len; i++) r[i] = i; for(j = 0; j < n; j++) { sum = 0; @@ -680,7 +811,7 @@ function array_for_of(n) } global_res = sum; } - return n * 100; + return n * len; } function math_min(n) @@ -700,11 +831,11 @@ function regexp_ascii(n) var i, j, r, s; s = "the quick brown fox jumped over the lazy dog" 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) global_res = r; } - return n * 10000; + return n * 1000; } function regexp_utf16(n) @@ -712,91 +843,91 @@ function regexp_utf16(n) var i, j, r, s; s = "the quick brown ᶠᵒˣ jumped over the lazy ᵈᵒᵍ" for(j = 0; j < n; j++) { - for(i = 0; i < 10000; i++) + for(i = 0; i < 1000; i++) r = /the quick brown ᶠᵒˣ/.exec(s) global_res = r; } - return n * 10000; + return n * 1000; } /* incremental string contruction as local var */ function string_build1(n) { var i, j, r; - r = ""; for(j = 0; j < n; j++) { - for(i = 0; i < 100; i++) + r = ""; + for(i = 0; i < 1000; i++) r += "x"; global_res = r; } - return n * 100; + return n * 1000; } /* incremental string contruction using + */ function string_build1x(n) { var i, j, r; - r = ""; for(j = 0; j < n; j++) { - for(i = 0; i < 100; i++) + r = ""; + for(i = 0; i < 1000; i++) r = r + "x"; global_res = r; } - return n * 100; + return n * 1000; } /* incremental string contruction using +2c */ function string_build2c(n) { var i, j; - var r = ""; for(j = 0; j < n; j++) { - for(i = 0; i < 100; i++) + var r = ""; + for(i = 0; i < 1000; i++) r += "xy"; global_res = r; } - return n * 100; + return n * 1000; } /* incremental string contruction as arg */ function string_build2(n, r) { var i, j; - r = ""; for(j = 0; j < n; j++) { - for(i = 0; i < 100; i++) + r = ""; + for(i = 0; i < 1000; i++) r += "x"; global_res = r; } - return n * 100; + return n * 1000; } /* incremental string contruction by prepending */ function string_build3(n) { var i, j, r; - r = ""; for(j = 0; j < n; j++) { - for(i = 0; i < 100; i++) + r = ""; + for(i = 0; i < 1000; i++) r = "x" + r; global_res = r; } - return n * 100; + return n * 1000; } /* incremental string contruction with multiple reference */ function string_build4(n) { var i, j, r, s; - r = ""; for(j = 0; j < n; j++) { - for(i = 0; i < 100; i++) { + r = ""; + for(i = 0; i < 1000; i++) { s = r; r += "x"; } global_res = r; } - return n * 100; + return n * 1000; } /* sort bench */ @@ -940,21 +1071,22 @@ sort_bench.verbose = false; function int_to_string(n) { - var s, r, j; - r = 0; + var s, 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) { - var s, r, j; - r = 0; + var s, j; for(j = 0; j < n; j++) { s = (j + 0.1).toString(); } + global_res = s; return n; } @@ -963,7 +1095,6 @@ function string_to_int(n) var s, r, j; r = 0; s = "12345"; - r = 0; for(j = 0; j < n; j++) { r += (s | 0); } @@ -976,7 +1107,6 @@ function string_to_float(n) var s, r, j; r = 0; s = "12345.6"; - r = 0; for(j = 0; j < n; j++) { r -= s; } @@ -986,32 +1116,70 @@ function string_to_float(n) function load_result(filename) { - var f, str, res; - if (typeof std === "undefined") + var has_filename = filename; + 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; - f = std.open(filename ? filename : "microbench.txt", "r"); - if (!f) { - if (filename) { + } + if (has_error) { + if (has_filename) { // Should throw exception? console.log("cannot load " + filename); } return null; } - str = f.readAsString(); res = JSON.parse(str); - f.close(); return res; } function save_result(filename, obj) { - var f; - if (typeof std === "undefined") + var str = JSON.stringify(obj, null, 2) + "\n"; + 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; - f = std.open(filename, "w"); - f.puts(JSON.stringify(obj, null, 2)); - f.puts("\n"); - f.close(); + } + if (has_error) + console.log("cannot save " + filename); } function main(argc, argv, g) @@ -1022,13 +1190,17 @@ function main(argc, argv, g) empty_down_loop2, empty_do_loop, date_now, + date_parse, prop_read, prop_write, + prop_update, prop_create, + prop_clone, prop_delete, array_read, array_write, array_prop_create, + array_slice, array_length_decr, array_hole_length_decr, array_push, @@ -1041,8 +1213,9 @@ function main(argc, argv, g) local_destruct, global_destruct, global_destruct_strict, + global_func_call, func_call, - closure_var, + func_closure_call, int_arith, float_arith, set_collection_add, @@ -1065,7 +1238,7 @@ function main(argc, argv, g) ]; var tests = []; var i, j, n, f, name, found; - var ref_file; + var ref_file, new_ref_file = "microbench-new.txt"; if (typeof BigInt === "function") { /* BigInt test */ @@ -1101,6 +1274,10 @@ function main(argc, argv, g) ref_file = argv[i++]; continue; } + if (name == "-s") { + new_ref_file = argv[i++]; + continue; + } for (j = 0, found = false; j < test_list.length; j++) { f = test_list[j]; if (f.name.startsWith(name)) { @@ -1128,12 +1305,12 @@ function main(argc, argv, g) n++; } 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 log_line("total", "", total[2]); - if (tests == test_list) - save_result("microbench-new.txt", log_data); + if (tests == test_list && new_ref_file) + save_result(new_ref_file, log_data); } if (typeof scriptArgs === "undefined") {