1
1
#!/usr/bin/env python3
2
2
3
3
import argparse
4
+ import enum
4
5
import json
5
6
import os
6
- import subprocess
7
+ import re
8
+ import shlex
7
9
import statistics
10
+ import subprocess
8
11
import sys
9
12
from tabulate import tabulate
10
13
11
- def run_benchmark (executable , executable_arguments , suite , test_file , iterations , index , total , suppress_output = False ):
12
- times = []
14
+ FLOAT_RE = re .compile (r"([0-9]*\.[0-9]+|[0-9]+)" )
15
+
16
+ class ScoreMetric (enum .Enum ):
17
+ time = "time"
18
+ output = "reported_score"
19
+
20
+ def run_benchmark (executable , executable_arguments , suite , test_file , score_metric , iterations , index , total , suppress_output = False ):
21
+ unit = "s" if score_metric == ScoreMetric .time else ""
22
+ measures = { k :[] for k in ScoreMetric }
23
+
13
24
for i in range (iterations ):
14
25
if not suppress_output :
15
- print (f"[{ index } /{ total } ] { suite } /{ test_file } (Iteration { i + 1 } /{ iterations } , Avg: { statistics .mean (times ):.3f} s )" if times else f"[{ index } /{ total } ] { suite } /{ test_file } (Iteration { i + 1 } /{ iterations } )" , end = "\r " )
26
+ print (f"[{ index } /{ total } ] { suite } /{ test_file } (Iteration { i + 1 } /{ iterations } , Avg: { statistics .mean (measures [ score_metric ] ):.3f} { unit } )" if measures [ score_metric ] else f"[{ index } /{ total } ] { suite } /{ test_file } (Iteration { i + 1 } /{ iterations } )" , end = "\r " )
16
27
sys .stdout .flush ()
17
28
18
- result = subprocess .run ([f"time -p { executable } { ' ' .join (executable_arguments )} { suite } /{ test_file } " ], shell = True , stderr = subprocess .PIPE , stdout = subprocess .DEVNULL , text = True , executable = "/bin/bash" )
29
+ result = subprocess .run ([f"time -p { shlex . quote ( executable ) } { ' ' .join (shlex . quote ( arg ) for arg in executable_arguments )} { suite } /{ test_file } " ], shell = True , stderr = subprocess .PIPE , stdout = subprocess .DEVNULL if score_metric == ScoreMetric . time else subprocess . PIPE , text = True , executable = "/bin/bash" )
19
30
result .check_returncode ()
20
31
21
32
time_output = result .stderr .split ("\n " )
22
33
real_time_line = [line for line in time_output if "real" in line ][0 ]
23
34
time_taken = float (real_time_line .split (" " )[- 1 ])
24
- times .append (time_taken )
25
-
26
- mean = statistics .mean (times )
27
- stdev = statistics .stdev (times ) if len (times ) > 1 else 0
28
- min_time = min (times )
29
- max_time = max (times )
35
+ measures [ScoreMetric .time ].append (time_taken )
36
+
37
+ if score_metric == ScoreMetric .output :
38
+ output = result .stdout .split ("\n " )
39
+ value = None
40
+ for line in output :
41
+ if match := FLOAT_RE .search (line ):
42
+ value = float (match [1 ])
43
+ assert value is not None , "Expected a float in the benchmark output"
44
+ measures [ScoreMetric .output ].append (value )
45
+
46
+ means = { key :statistics .mean (values ) if len (values ) > 0 else None for key , values in measures .items () }
47
+ stdevs = { key :statistics .stdev (values ) if len (values ) > 1 else 0 for key , values in measures .items () }
48
+ min_values = { key :min (values ) if len (values ) > 0 else None for key , values in measures .items () }
49
+ max_values = { key :max (values ) if len (values ) > 0 else None for key , values in measures .items () }
30
50
if not suppress_output :
31
- print (f"[{ index } /{ total } ] { suite } /{ test_file } completed. Mean: { mean :.3f} s ± { stdev :.3f} s , Range: { min_time :.3f} s … { max_time :.3f} s \033 [K" )
51
+ print (f"[{ index } /{ total } ] { suite } /{ test_file } completed. Mean: { means [ score_metric ] :.3f} { unit } ± { stdevs [ score_metric ] :.3f} { unit } , Range: { min_values [ score_metric ] :.3f} { unit } … { max_values [ score_metric ] :.3f} { unit } \033 [K" )
32
52
sys .stdout .flush ()
33
53
34
- return mean , stdev , min_time , max_time , times
54
+ return means , stdevs , min_values , max_values , measures
35
55
36
56
def main ():
37
57
parser = argparse .ArgumentParser (description = "Run JavaScript benchmarks." )
@@ -44,7 +64,7 @@ def main():
44
64
args = parser .parse_args ()
45
65
46
66
if args .suites == "all" :
47
- suites = ["SunSpider" , "Kraken" , "Octane" , "JetStream" , "JetStream3" , "RegExp" , "MicroBench" , "WasmMicroBench" ]
67
+ suites = ["SunSpider" , "Kraken" , "Octane" , "JetStream" , "JetStream3" , "RegExp" , "MicroBench" , "WasmMicroBench" , "WasmCoremark" ]
48
68
else :
49
69
suites = args .suites .split ("," )
50
70
@@ -54,7 +74,7 @@ def main():
54
74
for test_file in sorted (os .listdir ("SunSpider" )):
55
75
if not test_file .endswith (".js" ):
56
76
continue
57
- run_benchmark (args .executable , [], "SunSpider" , test_file , 1 , 0 , 0 , suppress_output = True )
77
+ run_benchmark (args .executable , [], "SunSpider" , ScoreMetric . time , test_file , 1 , 0 , 0 , suppress_output = True )
58
78
59
79
results = {}
60
80
table_data = []
@@ -64,32 +84,41 @@ def main():
64
84
for suite in suites :
65
85
results [suite ] = {}
66
86
is_wasm_bench = suite == "WasmMicroBench"
87
+ is_wasm_coremark = suite == "WasmCoremark"
67
88
68
89
executable = ""
69
90
executable_arguments = []
91
+ score_metric = ScoreMetric .time
70
92
if (is_wasm_bench ):
71
93
executable = args .wasm_executable
72
94
executable_arguments = ["-e" , "run_microbench" ]
95
+ elif is_wasm_coremark :
96
+ executable = args .wasm_executable
97
+ executable_arguments = ["-e" , "run" , "--export-js" , "env.clock_ms:i64=BigInt(+new Date)" ]
98
+ score_metric = ScoreMetric .output
73
99
else :
74
100
executable = args .executable
75
101
76
102
for test_file in sorted (os .listdir (suite )):
77
- if (is_wasm_bench ):
103
+ if (is_wasm_bench or is_wasm_coremark ):
78
104
if not test_file .endswith (".wasm" ):
79
105
continue
80
106
else :
81
107
if not test_file .endswith (".js" ):
82
108
continue
83
109
84
- mean , stdev , min_time , max_time , runs = run_benchmark (executable , executable_arguments , suite , test_file , args .iterations , current_test , total_tests )
110
+ stats = run_benchmark (executable , executable_arguments , suite , test_file , score_metric , args .iterations , current_test , total_tests )
85
111
results [suite ][test_file ] = {
86
- "mean" : mean ,
87
- "stdev" : stdev ,
88
- "min" : min_time ,
89
- "max" : max_time ,
90
- "runs" : runs
112
+ key .value : {
113
+ "mean" : mean ,
114
+ "stdev" : stdev ,
115
+ "min" : min_val ,
116
+ "max" : max_val ,
117
+ "runs" : runs ,
118
+ } for key , (mean , stdev , min_val , max_val , runs ) in zip (stats [0 ].keys (), zip (* (x .values () for x in stats ))) if runs
91
119
}
92
- table_data .append ([suite , test_file , f"{ mean :.3f} ± { stdev :.3f} " , f"{ min_time :.3f} … { max_time :.3f} " ])
120
+ mean , stdev , min_val , max_val , _ = (stat [score_metric ] for stat in stats )
121
+ table_data .append ([suite , test_file , f"{ mean :.3f} ± { stdev :.3f} " , f"{ min_val :.3f} … { max_val :.3f} " ])
93
122
current_test += 1
94
123
95
124
print (tabulate (table_data , headers = ["Suite" , "Test" , "Mean ± σ" , "Range (min … max)" ]))
0 commit comments