Skip to content

Commit c342dcd

Browse files
committed
[GR-26821] Add compiler warmup benchmarks.
PullRequest: graalpython/1359
2 parents 3f41de3 + 706fd79 commit c342dcd

File tree

13 files changed

+779
-11
lines changed

13 files changed

+779
-11
lines changed

ci.jsonnet

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{ "overlay": "90640c941cf82e5f7167b7c48a909e1d65b2b6d2" }
1+
{ "overlay": "cde0760728616ed04bef0f4a44ab2454efd457eb" }

graalpython/com.oracle.graal.python.benchmarks/python/harness.py

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,27 @@
3737
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
3838
# SOFTWARE.
3939

40+
from time import time
41+
42+
# Capture module load time as soon as possible for other engines. The unit is seconds.
43+
_module_start_time = time()
44+
4045
import _io
4146
import os
4247
import sys
4348
import types
44-
from time import time
45-
4649

50+
GRAALPYTHON = sys.implementation.name == "graalpython"
51+
52+
# Try to use the timer with best accuracy. Unfortunately, 'monotonic_ns' is not available everywhere.
53+
if GRAALPYTHON:
54+
from time import monotonic_ns
55+
monotonic_best_accuracy = monotonic_ns
56+
UNITS_PER_SECOND = 1e9
57+
else:
58+
monotonic_best_accuracy = time
59+
UNITS_PER_SECOND = 1.0
60+
4761
_HRULE = '-'.join(['' for i in range(80)])
4862

4963
#: this function is used to pre-process the arguments as expected by the __benchmark__ and __setup__ entry points
@@ -58,6 +72,13 @@
5872
ATTR_TEARDOWN = '__teardown__'
5973

6074

75+
def get_seconds_since_startup(cur_time):
76+
if GRAALPYTHON and __graalpython__.startup_nano != -1:
77+
return (cur_time - __graalpython__.startup_nano) / UNITS_PER_SECOND
78+
# note: the unit of _module_start_time is seconds
79+
return cur_time / UNITS_PER_SECOND - _module_start_time
80+
81+
6182
# ----------------------------------------------------------------------------------------------------------------------
6283
#
6384
# the CUSUM method adapted for warmup detection within a given threshold (initial iterations)
@@ -195,7 +216,7 @@ def _as_int(value):
195216

196217

197218
class BenchRunner(object):
198-
def __init__(self, bench_file, bench_args=None, iterations=1, warmup=-1, warmup_runs=0):
219+
def __init__(self, bench_file, bench_args=None, iterations=1, warmup=-1, warmup_runs=0, startup=None):
199220
assert isinstance(iterations, int), \
200221
"BenchRunner iterations argument must be an int, got %s instead" % iterations
201222
assert isinstance(warmup, int), \
@@ -213,6 +234,7 @@ def __init__(self, bench_file, bench_args=None, iterations=1, warmup=-1, warmup_
213234
self.iterations = 1 if self._run_once else _iterations
214235
self.warmup_runs = warmup_runs if warmup_runs > 0 else 0
215236
self.warmup = warmup if warmup > 0 else -1
237+
self.startup = startup
216238

217239
@staticmethod
218240
def get_bench_module(bench_file):
@@ -271,7 +293,12 @@ def run(self):
271293
self._call_attr(ATTR_SETUP, *args)
272294
print("### start benchmark ... ")
273295

296+
report_startup = self.startup
297+
274298
bench_func = self._get_attr(ATTR_BENCHMARK)
299+
startup_s = -1.0
300+
early_warmup_s = -1.0
301+
late_warmup_s = -1.0
275302
durations = []
276303
if bench_func and hasattr(bench_func, '__call__'):
277304
if self.warmup_runs:
@@ -283,7 +310,14 @@ def run(self):
283310
for iteration in range(self.iterations):
284311
start = time()
285312
bench_func(*args)
313+
cur_time = monotonic_best_accuracy()
286314
duration = time() - start
315+
if report_startup and startup_s < 0.0 and iteration == self.startup[0] - 1:
316+
startup_s = get_seconds_since_startup(cur_time)
317+
if report_startup and early_warmup_s < 0.0 and iteration == self.startup[1] - 1:
318+
early_warmup_s = get_seconds_since_startup(cur_time)
319+
if report_startup and late_warmup_s < 0 and iteration == self.startup[2] - 1:
320+
late_warmup_s = get_seconds_since_startup(cur_time)
287321
durations.append(duration)
288322
duration_str = "%.3f" % duration
289323
self._call_attr(ATTR_CLEANUP, *args)
@@ -311,6 +345,11 @@ def run(self):
311345
print(_HRULE)
312346

313347
# summary
348+
# We can do that only on Graalpython
349+
if report_startup:
350+
print("### STARTUP at iteration: %d, duration: %.3f" % (self.startup[0], startup_s))
351+
print("### EARLY WARMUP at iteration: %d, duration: %.3f" % (self.startup[1], early_warmup_s))
352+
print("### LATE WARMUP at iteration: %d, duration: %.3f" % (self.startup[2], late_warmup_s))
314353
if self._run_once:
315354
print("### SINGLE RUN duration: %.3f s" % durations[0])
316355
else:
@@ -333,6 +372,9 @@ def run(self):
333372
else:
334373
print("### WARMUP iteration not specified or could not be detected")
335374

375+
if GRAALPYTHON and self.startup and __graalpython__.startup_nano == -1:
376+
print("### NOTE: enable startup time snapshotting to increase accuracy.")
377+
336378
print(_HRULE)
337379
print("### RAW DURATIONS: %s" % str(durations))
338380
print(_HRULE)
@@ -342,6 +384,7 @@ def run_benchmark(args):
342384
warmup = -1
343385
warmup_runs = 0
344386
iterations = 1
387+
startup = None
345388
bench_file = None
346389
bench_args = []
347390
paths = []
@@ -367,6 +410,13 @@ def run_benchmark(args):
367410
elif arg.startswith("--warmup-runs"):
368411
warmup_runs = _as_int(arg.split("=")[1])
369412

413+
elif arg.startswith('--startup'):
414+
try:
415+
itrs = arg.split("=")[1].split(",")
416+
startup = (int(itrs[0]), int(itrs[1]), int(itrs[2]))
417+
except:
418+
raise TypeError("incorrect argument; must be in form of '-s 1,10,100'")
419+
370420
elif arg == '-p':
371421
i += 1
372422
paths = args[i].split(",")
@@ -379,6 +429,11 @@ def run_benchmark(args):
379429
bench_args.append(arg)
380430
i += 1
381431

432+
min_required_iterations = max(startup) if startup else 0
433+
if startup and iterations < min_required_iterations:
434+
print("### WARNING: you've specified less iterations than required to measure the startup. Overriding iterations with %d" % min_required_iterations)
435+
iterations = min_required_iterations
436+
382437
# set the paths if specified
383438
print(_HRULE)
384439
sys.path.append(os.path.split(bench_file)[0])
@@ -389,7 +444,7 @@ def run_benchmark(args):
389444
else:
390445
print("### no extra module search paths specified")
391446

392-
BenchRunner(bench_file, bench_args=bench_args, iterations=iterations, warmup=warmup, warmup_runs=warmup_runs).run()
447+
BenchRunner(bench_file, bench_args=bench_args, iterations=iterations, warmup=warmup, warmup_runs=warmup_runs, startup=startup).run()
393448

394449

395450
if __name__ == '__main__':
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#!/usr/bin/env python
2+
# Copyright 2008-2010 Isaac Gouy
3+
# Copyright (c) 2013, 2014, Regents of the University of California
4+
# Copyright (c) 2017, 2020, Oracle and/or its affiliates.
5+
# All rights reserved.
6+
#
7+
# Revised BSD license
8+
#
9+
# This is a specific instance of the Open Source Initiative (OSI) BSD license
10+
# template http://www.opensource.org/licenses/bsd-license.php
11+
#
12+
# Redistribution and use in source and binary forms, with or without
13+
# modification, are permitted provided that the following conditions are met:
14+
#
15+
# Redistributions of source code must retain the above copyright notice, this
16+
# list of conditions and the following disclaimer.
17+
#
18+
# Redistributions in binary form must reproduce the above copyright notice,
19+
# this list of conditions and the following disclaimer in the documentation
20+
# and/or other materials provided with the distribution.
21+
#
22+
# Neither the name of "The Computer Language Benchmarks Game" nor the name of
23+
# "The Computer Language Shootout Benchmarks" nor the name "nanobench" nor the
24+
# name "bencher" nor the names of its contributors may be used to endorse or
25+
# promote products derived from this software without specific prior written
26+
# permission.
27+
#
28+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
29+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
30+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
31+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
32+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
33+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
34+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
35+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
36+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38+
39+
# The Computer Language Benchmarks Game
40+
# http://shootout.alioth.debian.org/
41+
#
42+
# contributed by Antoine Pitrou
43+
# modified by Dominique Wahli
44+
# modified by Heinrich Acker
45+
46+
47+
def make_tree(item, depth):
48+
if not depth: return item, None, None
49+
item2 = item + item
50+
depth -= 1
51+
return item, make_tree(item2 - 1, depth), make_tree(item2, depth)
52+
53+
54+
def check_tree(xxx_todo_changeme):
55+
(item, left, right) = xxx_todo_changeme
56+
if not left: return item
57+
return item + check_tree(left) - check_tree(right)
58+
59+
60+
def main(num):
61+
min_depth = 4
62+
max_depth = max(min_depth + 2, num)
63+
stretch_depth = max_depth + 1
64+
65+
print("stretch tree of depth %d\t check:" % stretch_depth, check_tree(make_tree(0, stretch_depth)))
66+
67+
long_lived_tree = make_tree(0, max_depth)
68+
69+
iterations = 2**max_depth
70+
71+
for depth in range(min_depth, stretch_depth, 2):
72+
73+
check = 0
74+
for i in range(1, iterations + 1):
75+
check += check_tree(make_tree(i, depth)) + check_tree(make_tree(-i, depth))
76+
77+
print("%d\t trees of depth %d\t check:" % (iterations * 2, depth), check)
78+
iterations //= 4
79+
80+
print("long lived tree of depth %d\t check:" % max_depth, check_tree(long_lived_tree))
81+
82+
83+
def __benchmark__(num=10):
84+
main(num)

0 commit comments

Comments
 (0)