summaryrefslogtreecommitdiffstats
path: root/third_party/rust/jsparagus/benchmarks/compare-spidermonkey-parsers.js
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/jsparagus/benchmarks/compare-spidermonkey-parsers.js')
-rw-r--r--third_party/rust/jsparagus/benchmarks/compare-spidermonkey-parsers.js315
1 files changed, 315 insertions, 0 deletions
diff --git a/third_party/rust/jsparagus/benchmarks/compare-spidermonkey-parsers.js b/third_party/rust/jsparagus/benchmarks/compare-spidermonkey-parsers.js
new file mode 100644
index 0000000000..d449bffda7
--- /dev/null
+++ b/third_party/rust/jsparagus/benchmarks/compare-spidermonkey-parsers.js
@@ -0,0 +1,315 @@
+// This script runs multipe parsers from a single engine.
+"use strict";
+
+// Directory where to find the list of JavaScript sources to be used for
+// benchmarking.
+var dir = ".";
+
+// Skip list cache to be used to be able to compare profiles. Without a skip
+// list which ensure that only runnable test cases are used, the profile would
+// not represent the actual values reported by this script.
+var skipList = [], skipFile = "", skipLen = 0;
+
+// Handle command line arguments.
+for (var i = 0; i < scriptArgs.length; i++) {
+ switch (scriptArgs[i]) {
+ case "--dir":
+ if (++i >= scriptArgs.length) {
+ throw Error("--dir expects a path.");
+ }
+ dir = scriptArgs[i];
+ break;
+ case "--skip-file":
+ if (++i >= scriptArgs.length) {
+ throw Error("--skip-file expects a path.");
+ }
+ skipFile = scriptArgs[i];
+ try {
+ skipList = eval(os.file.readFile(skipFile));
+ } catch (e) {
+ // ignore errors
+ }
+ skipLen = skipList.length;
+ break;
+ }
+}
+
+// Execution mode of the parser, either "script" or "module".
+var mode = "script";
+
+// Number of times each JavaScript source is used for benchmarking.
+var runs_per_script = 10;
+
+// First parser
+var name_1 = "SpiderMonkey parser";
+function parse_1(path) {
+ var start = performance.now();
+ parse(path, { module: mode == "module", smoosh: false });
+ return performance.now() - start;
+}
+
+// Second parser
+var name_2 = "SmooshMonkey parser";
+function parse_2(path) {
+ var start = performance.now();
+ parse(path, { module: mode == "module", smoosh: true });
+ return performance.now() - start;
+}
+
+// For a given `parse` function, execute it with the content of each file in
+// `dir`. This process is repeated `N` times and the results are added to the
+// `result` argument using the `prefix` key for the filenames.
+function for_all_files(parse, N = 1, prefix = "", result = {}) {
+ var path = "", content = "";
+ var t = 0;
+ var list = os.file.listDir(dir);
+ for (var file of list) {
+ try {
+ path = os.path.join(dir, file);
+ content = os.file.readRelativeToScript(path);
+ try {
+ t = 0;
+ for (var n = 0; n < N; n++)
+ t += parse(content);
+ result[prefix + path] = { time: t / N, bytes: content.length };
+ } catch (e) {
+ // ignore all errors for now.
+ result[prefix + path] = { time: null, bytes: content.length };
+ }
+ } catch (e) {
+ // ignore all read errors.
+ }
+ }
+ return result;
+}
+
+// Compare the results of 2 parser runs and compute the speed ratio between the
+// 2 parsers. Results from both parsers are assuming to be comparing the same
+// things if they have the same property name.
+//
+// The aggregated results is returned as an object, which reports the total time
+// for each parser, the quantity of bytes parsed and skipped and an array of
+// speed ratios for each file tested.
+function compare(name1, res1, name2, res2) {
+ var result = {
+ name1: name1,
+ name2: name2,
+ time1: 0,
+ time2: 0,
+ parsed_files: 0,
+ parsed_bytes: 0,
+ skipped_files: 0,
+ skipped_bytes: 0,
+ ratios_2over1: [],
+ };
+ for (var path of Object.keys(res1)) {
+ if (!(path in res1 && path in res2)) {
+ continue;
+ }
+ var p1 = res1[path];
+ var p2 = res2[path];
+ if (p1.time !== null && p2.time !== null) {
+ result.time1 += p1.time;
+ result.time2 += p2.time;
+ result.parsed_files += 1;
+ result.parsed_bytes += p1.bytes;
+ result.ratios_2over1.push(p2.time / p1.time);
+ } else {
+ result.skipped_files += 1;
+ result.skipped_bytes += p1.bytes;
+ }
+ }
+ return result;
+}
+
+function print_result(result) {
+ print(result.name1, "\t", result.time1, "ms\t", 1e6 * result.time1 / result.parsed_bytes, 'ns/byte\t', result.parsed_bytes / (1e6 * result.time1), 'bytes/ns\t');
+ print(result.name2, "\t", result.time2, "ms\t", 1e6 * result.time2 / result.parsed_bytes, 'ns/byte\t', result.parsed_bytes / (1e6 * result.time2), 'bytes/ns\t');
+ print("Total parsed (scripts:", result.parsed_files, ", bytes:", result.parsed_bytes, ")");
+ print("Total skipped (scripts:", result.skipped_files, ", bytes:", result.skipped_bytes, ")");
+ print(result.name2, "/", result.name1, ":", result.time2 / result.time1);
+ print(result.name2, "/", result.name1, ":", spread(result.ratios_2over1, 0, 5, 0.05));
+}
+
+// Given a `table` of speed ratios, display a distribution chart of speed
+// ratios. This is useful to check if the data is noisy, bimodal, and to easily
+// eye-ball characteristics of the distribution.
+function spread(table, min, max, step) {
+ // var chars = ["\xa0", "\u2591", "\u2592", "\u2593", "\u2588"];
+ var chars = ["\xa0", "\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
+ var s = ["\xa0", "\xa0", "" + min, "\xa0", "\xa0"];
+ var ending = ["\xa0", "\xa0", "" + max, "\xa0", "\xa0"];
+ var scale = "\xa0\xa0";
+ var scale_values = ["⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹"];
+ var ranges = [];
+ var vmax = table.length / 10;
+ for (var i = min; i < max; i += step) {
+ ranges.push(0);
+ var decimal = i - Math.trunc(i);
+ var error = Math.abs(decimal - Math.round(10 * decimal) / 10);
+ decimal = Math.round(decimal * 10) % 10;
+ if (error < step / 2)
+ scale += scale_values[decimal];
+ else
+ scale += "\xa0";
+ }
+ for (var x of table) {
+ if (x < min || max < x) continue;
+ var idx = ((x - min) / step)|0;
+ ranges[idx] += 1;
+ }
+ var max_index = chars.length * s.length;
+ var ratio = max_index / vmax;
+ for (i = 0; i < s.length; i++)
+ s[i] += "\xa0\u2595";
+ for (var v of ranges) {
+ var d = Math.min((v * ratio)|0, max_index - 1);
+ var offset = max_index;
+ for (i = 0; i < s.length; i++) {
+ offset -= chars.length;
+ var c = Math.max(0, Math.min(d - offset, chars.length - 1));
+ s[i] += chars[c];
+ }
+ }
+ for (i = 0; i < s.length; i++)
+ s[i] += "\u258f\xa0" + ending[i];
+ var res = "";
+ for (i = 0; i < s.length; i++)
+ res += "\n" + s[i];
+ res += "\n" + scale;
+ return res;
+}
+
+// NOTE: We have multiple strategies depending whether we want to check the
+// throughput of the parser assuming the parser is cold/hot in memory, the data is
+// cold/hot in the cache, and the adaptive CPU throttle is low/high.
+//
+// Ideally we should be comparing comparable things, but due to the adaptive
+// behavior of CPU and Disk, we can only approximate it while keeping results
+// comparable to what users might see.
+
+// Compare Hot-parsers on cold data.
+function strategy_1() {
+ var res1 = for_all_files(parse_1, runs_per_script);
+ var res2 = for_all_files(parse_2, runs_per_script);
+ return compare(name_1, res1, name_2, res2);
+}
+
+// Compare Hot-parsers on cold data, and swap parse order.
+function strategy_2() {
+ var res2 = for_all_files(parse_2, runs_per_script);
+ var res1 = for_all_files(parse_1, runs_per_script);
+ return compare(name_1, res1, name_2, res2);
+}
+
+// Interleaves N hot-parser results. (if N=1, then strategy_3 is identical to strategy_1)
+//
+// At the moment, this is assumed to be the best approach which might mimic how
+// a helper-thread would behave if it was saturated with content to be parsed.
+function strategy_3() {
+ var res1 = {};
+ var res2 = {};
+ var N = runs_per_script;
+ for (var n = 0; n < N; n++) {
+ for_all_files(parse_1, 1, "" + n, res1);
+ for_all_files(parse_2, 1, "" + n, res2);
+ }
+ return compare(name_1, res1, name_2, res2);
+}
+
+// Compare cold parsers, with alternatetively cold/hot data.
+//
+// By swapping parser order of execution after each file, we expect that the
+// previous parser execution would be enough to evict the other from the L2
+// cache, and as such cause the other parser to hit cold instruction cache where
+// the instruction have to be reloaded.
+//
+// At the moment, this is assumed to be the best approach which might mimic how
+// parsers are effectively used on the main thread.
+function strategy_0() {
+ var path = "", content = "";
+ var t_1= 0, t_2 = 0, time_1 = 0, time_2 = 0;
+ var count = 0, count_bytes = 0, skipped = 0, skipped_bytes = 0;
+ var parse1_first = false;
+ var list = os.file.listDir(dir);
+ var ratios_2over1 = [];
+ var parse1_first = true;
+ for (var file of list) {
+ path = os.path.join(dir, file);
+ if (skipList.includes(path)) {
+ continue;
+ }
+ content = "";
+ try {
+ // print(Math.round(100 * f / list.length), file);
+ content = os.file.readRelativeToScript(path);
+ parse1_first = !parse1_first; // Math.random() > 0.5;
+ for (var i = 0; i < runs_per_script; i++) {
+ // Randomize the order in which parsers are executed as they are
+ // executed in the same process and the parsed content might be
+ // faster to load for the second parser as it is already in memory.
+ if (parse1_first) {
+ t_1 = parse_1(content);
+ t_2 = parse_2(content);
+ } else {
+ t_2 = parse_2(content);
+ t_1 = parse_1(content);
+ }
+ time_1 += t_1;
+ time_2 += t_2;
+ ratios_2over1.push(t_2 / t_1);
+ }
+ count++;
+ count_bytes += content.length;
+ } catch (e) {
+ // ignore all errors for now.
+ skipped++;
+ skipped_bytes += content.length;
+ skipList.push(path);
+ }
+ }
+
+ return {
+ name1: name_1,
+ name2: name_2,
+ time1: time_1,
+ time2: time_2,
+ parsed_files: count * runs_per_script,
+ parsed_bytes: count_bytes * runs_per_script,
+ skipped_files: skipped * runs_per_script,
+ skipped_bytes: skipped_bytes * runs_per_script,
+ ratios_2over1: ratios_2over1,
+ };
+}
+
+var outputJSON = os.getenv("SMOOSH_BENCH_AS_JSON") !== undefined;
+if (!outputJSON) {
+ print("Main thread comparison:");
+}
+var main_thread_result = strategy_0();
+if (!outputJSON) {
+ print_result(main_thread_result);
+ print("");
+ print("Off-thread comparison:");
+}
+var off_thread_result = strategy_3();
+if (!outputJSON) {
+ print_result(off_thread_result);
+}
+
+if (outputJSON) {
+ print(JSON.stringify({
+ main_thread: main_thread_result,
+ off_thread: main_thread_result
+ }));
+}
+
+if (skipFile && skipList.length > skipLen) {
+ var content = `[${skipList.map(s => `"${s}"`).join(",")}]`;
+ var data = new ArrayBuffer(content.length);
+ var view = new Uint8Array(data);
+ for (var i = 0; i < content.length; i++) {
+ view[i] = content.charCodeAt(i);
+ }
+ os.file.writeTypedArrayToFile(skipFile, view);
+}