Skip to content

Commit 5d4a714

Browse files
committed
[GR-68708] Add startup-imports benchmark.
PullRequest: graalpython/4273
2 parents 8b8e0cd + ed74ea4 commit 5d4a714

File tree

6 files changed

+192
-76
lines changed

6 files changed

+192
-76
lines changed
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
2+
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
3+
#
4+
# The Universal Permissive License (UPL), Version 1.0
5+
#
6+
# Subject to the condition set forth below, permission is hereby granted to any
7+
# person obtaining a copy of this software, associated documentation and/or
8+
# data (collectively the "Software"), free of charge and under any and all
9+
# copyright rights in the Software, and any and all patent rights owned or
10+
# freely licensable by each licensor hereunder covering either (i) the
11+
# unmodified Software as contributed to or provided by such licensor, or (ii)
12+
# the Larger Works (as defined below), to deal in both
13+
#
14+
# (a) the Software, and
15+
#
16+
# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
17+
# one is included with the Software each a "Larger Work" to which the Software
18+
# is contributed by such licensors),
19+
#
20+
# without restriction, including without limitation the rights to copy, create
21+
# derivative works of, display, perform, and distribute the Software and make,
22+
# use, sell, offer for sale, import, export, have made, and have sold the
23+
# Software and the Larger Work(s), and to sublicense the foregoing rights on
24+
# either these or other terms.
25+
#
26+
# This license is subject to the following condition:
27+
#
28+
# The above copyright notice and either this complete permission notice or at a
29+
# minimum a reference to the UPL must be included in all copies or substantial
30+
# portions of the Software.
31+
#
32+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
33+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
34+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
35+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
36+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
37+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
38+
# SOFTWARE.
39+
40+
# Utilities for benchmarking GraalPy startup. Creates a C benchmark runner that spawns
41+
# the subprocesses to avoid counting subprocess module overhead into the benchmark
42+
import os
43+
import subprocess
44+
import sys
45+
import tempfile
46+
from pathlib import Path
47+
48+
RUNNER_CODE = '''
49+
#include <stdio.h>
50+
#include <stdlib.h>
51+
#include <unistd.h>
52+
#include <sys/wait.h>
53+
54+
int main(int argc, char *argv[]) {
55+
if (argc < 3) {
56+
return 1;
57+
}
58+
int n = atoi(argv[1]);
59+
if (n <= 0) {
60+
return 1;
61+
}
62+
char **cmd_argv = &argv[2];
63+
for (int i = 0; i < n; ++i) {
64+
pid_t pid = fork();
65+
if (pid < 0) {
66+
perror("fork");
67+
return 1;
68+
} else if (pid == 0) {
69+
execvp(cmd_argv[0], cmd_argv);
70+
perror("execvp");
71+
exit(127); // If exec fails
72+
} else {
73+
int status;
74+
if (waitpid(pid, &status, 0) < 0) {
75+
perror("waitpid");
76+
return 1;
77+
}
78+
}
79+
}
80+
return 0;
81+
}
82+
'''
83+
84+
TMPDIR = tempfile.TemporaryDirectory()
85+
RUNNER_EXE = None
86+
ORIG_ARGV = None
87+
88+
89+
def setup():
90+
global RUNNER_EXE
91+
tmpdir = Path(TMPDIR.name)
92+
runner_c = tmpdir / 'runner.c'
93+
runner_c.write_text(RUNNER_CODE)
94+
RUNNER_EXE = tmpdir / 'runner'
95+
subprocess.check_call([os.environ.get('CC', 'gcc'), runner_c, '-O2', '-o', RUNNER_EXE])
96+
97+
global ORIG_ARGV
98+
ORIG_ARGV = sys.orig_argv
99+
for i, arg in enumerate(ORIG_ARGV):
100+
if arg.endswith('.py'):
101+
ORIG_ARGV = ORIG_ARGV[:i]
102+
break
103+
try:
104+
ORIG_ARGV.remove('-snapshot-startup')
105+
except ValueError:
106+
pass
107+
108+
109+
def teardown():
110+
TMPDIR.cleanup()
111+
112+
113+
def run(num, code):
114+
subprocess.check_call([
115+
str(RUNNER_EXE),
116+
str(num),
117+
*ORIG_ARGV,
118+
"-I", # isolate from environment
119+
"-S", # do not import site
120+
"-B", # do not attempt to write pyc files
121+
"-u", # do not add buffering wrappers around output streams
122+
"-c",
123+
code
124+
])
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
2+
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
3+
#
4+
# The Universal Permissive License (UPL), Version 1.0
5+
#
6+
# Subject to the condition set forth below, permission is hereby granted to any
7+
# person obtaining a copy of this software, associated documentation and/or
8+
# data (collectively the "Software"), free of charge and under any and all
9+
# copyright rights in the Software, and any and all patent rights owned or
10+
# freely licensable by each licensor hereunder covering either (i) the
11+
# unmodified Software as contributed to or provided by such licensor, or (ii)
12+
# the Larger Works (as defined below), to deal in both
13+
#
14+
# (a) the Software, and
15+
#
16+
# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
17+
# one is included with the Software each a "Larger Work" to which the Software
18+
# is contributed by such licensors),
19+
#
20+
# without restriction, including without limitation the rights to copy, create
21+
# derivative works of, display, perform, and distribute the Software and make,
22+
# use, sell, offer for sale, import, export, have made, and have sold the
23+
# Software and the Larger Work(s), and to sublicense the foregoing rights on
24+
# either these or other terms.
25+
#
26+
# This license is subject to the following condition:
27+
#
28+
# The above copyright notice and either this complete permission notice or at a
29+
# minimum a reference to the UPL must be included in all copies or substantial
30+
# portions of the Software.
31+
#
32+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
33+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
34+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
35+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
36+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
37+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
38+
# SOFTWARE.
39+
import os
40+
import subprocess
41+
import sys
42+
import fast_subprocess
43+
44+
def __setup__(*args):
45+
fast_subprocess.setup()
46+
47+
48+
def __teardown__():
49+
fast_subprocess.teardown()
50+
51+
52+
def __benchmark__(num=1000000):
53+
fast_subprocess.run(num, "import threading;import contextlib;import contextvars;import decimal;"
54+
"import ast;import asyncio;import argparse;import base64;import bisect;import calendar;"
55+
"import configparser;import copyreg;import dataclasses;import enum;import fractions;"
56+
"import glob;import hashlib;import os;import typing;import tomllib")
Lines changed: 5 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
1+
# Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
22
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
33
#
44
# The Universal Permissive License (UPL), Version 1.0
@@ -39,84 +39,15 @@
3939
import os
4040
import subprocess
4141
import sys
42-
import tempfile
43-
from pathlib import Path
44-
45-
# Use a C runner to spawn the subprocesses to avoid counting subprocess module overhead into the benchmark
46-
RUNNER_CODE = '''
47-
#include <stdio.h>
48-
#include <stdlib.h>
49-
#include <unistd.h>
50-
#include <sys/wait.h>
51-
52-
int main(int argc, char *argv[]) {
53-
if (argc < 3) {
54-
return 1;
55-
}
56-
int n = atoi(argv[1]);
57-
if (n <= 0) {
58-
return 1;
59-
}
60-
char **cmd_argv = &argv[2];
61-
for (int i = 0; i < n; ++i) {
62-
pid_t pid = fork();
63-
if (pid < 0) {
64-
perror("fork");
65-
return 1;
66-
} else if (pid == 0) {
67-
execvp(cmd_argv[0], cmd_argv);
68-
perror("execvp");
69-
exit(127); // If exec fails
70-
} else {
71-
int status;
72-
if (waitpid(pid, &status, 0) < 0) {
73-
perror("waitpid");
74-
return 1;
75-
}
76-
}
77-
}
78-
return 0;
79-
}
80-
'''
81-
82-
TMPDIR = tempfile.TemporaryDirectory()
83-
RUNNER_EXE = None
84-
ORIG_ARGV = None
85-
42+
import fast_subprocess
8643

8744
def __setup__(*args):
88-
global RUNNER_EXE
89-
tmpdir = Path(TMPDIR.name)
90-
runner_c = tmpdir / 'runner.c'
91-
runner_c.write_text(RUNNER_CODE)
92-
RUNNER_EXE = tmpdir / 'runner'
93-
subprocess.check_call([os.environ.get('CC', 'gcc'), runner_c, '-O2', '-o', RUNNER_EXE])
94-
95-
global ORIG_ARGV
96-
ORIG_ARGV = sys.orig_argv
97-
for i, arg in enumerate(ORIG_ARGV):
98-
if arg.endswith('.py'):
99-
ORIG_ARGV = ORIG_ARGV[:i]
100-
break
101-
try:
102-
ORIG_ARGV.remove('-snapshot-startup')
103-
except ValueError:
104-
pass
45+
fast_subprocess.setup()
10546

10647

10748
def __teardown__():
108-
TMPDIR.cleanup()
49+
fast_subprocess.teardown()
10950

11051

11152
def __benchmark__(num=1000000):
112-
subprocess.check_call([
113-
str(RUNNER_EXE),
114-
str(num),
115-
*ORIG_ARGV,
116-
"-I", # isolate from environment
117-
"-S", # do not import site
118-
"-B", # do not attempt to write pyc files
119-
"-u", # do not add buffering wrappers around output streams
120-
"-c",
121-
"1"
122-
])
53+
fast_subprocess.run(num, "1")

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/superobject/SuperBuiltins.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@
125125
import com.oracle.truffle.api.nodes.Node;
126126
import com.oracle.truffle.api.profiles.BranchProfile;
127127
import com.oracle.truffle.api.profiles.InlinedConditionProfile;
128+
import com.oracle.truffle.api.profiles.InlinedIntValueProfile;
128129
import com.oracle.truffle.api.strings.TruffleString;
129130

130131
@CoreFunctions(extendClasses = PythonBuiltinClassType.Super)
@@ -517,6 +518,7 @@ Object get(VirtualFrame frame, SuperObject self, Object attr,
517518
@Cached TruffleString.EqualNode equalNode,
518519
@Cached GetObjectTypeNode getObjectType,
519520
@Cached CastToTruffleStringChecked1Node castToTruffleStringNode,
521+
@Cached InlinedIntValueProfile mroLenProfile,
520522
@Cached InlinedConditionProfile hasDescrGetProfile,
521523
@Cached InlinedConditionProfile getObjectIsStartObjectProfile,
522524
@Cached IsForeignObjectNode isForeignObjectNode,
@@ -545,7 +547,7 @@ Object get(VirtualFrame frame, SuperObject self, Object attr,
545547
PythonAbstractClass[] mro = getMro(startType);
546548
/* No need to check the last one: it's gonna be skipped anyway. */
547549
int i = 0;
548-
int n = mro.length;
550+
int n = mroLenProfile.profile(inliningTarget, mro.length);
549551
for (i = 0; i + 1 < n; i++) {
550552
if (isSameType(type, mro[i])) {
551553
break;

mx.graalpython/mx_graalpython.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -962,7 +962,7 @@ def graalvm_jdk(enterprise=False):
962962
if not DISABLE_REBUILD:
963963
run_mx(mx_args + ["build", "--dep", f"GRAALVM_{edition}JAVA{jdk_major_version}"], env={**os.environ, **LATEST_JAVA_HOME})
964964
out = mx.OutputCapture()
965-
run_mx(mx_args + ["graalvm-home"], out=out)
965+
run_mx(["--quiet"] + mx_args + ["graalvm-home"], out=out)
966966
return out.data.splitlines()[-1].strip()
967967

968968
def get_maven_cache():

mx.graalpython/mx_graalpython_bench_param.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@
121121
'virtualize-in-try-catch-oom': ITER_10,
122122
'phase_shift_warmup_baseline': ITER_5 + ['--self-measurement'] + ['500'],
123123
'phase_shift_warmup': ITER_3 + ['--self-measurement'] + ['1600', '500'],
124+
'startup': ITER_5 + ['50'],
125+
'startup-imports': ITER_5 + ['20'],
124126
}
125127

126128
# For benchmarking the interpreter with --engine.Compilation=false
@@ -190,6 +192,7 @@
190192
'c-call-method-int-float': ITER_5 + ['500000'],
191193
'regexp': ITER_5 + WARMUP_2,
192194
'startup': ITER_5 + ['50'],
195+
'startup-imports': ITER_5 + ['10'],
193196
}
194197

195198
def _pickling_benchmarks(module='pickle'):

0 commit comments

Comments
 (0)