Skip to content

Commit 3cfc3fc

Browse files
committed
[GR-44232] Regularly collect and use PGO profiles.
PullRequest: graalpython/3931
2 parents f8de01a + f83bce0 commit 3cfc3fc

File tree

5 files changed

+138
-12
lines changed

5 files changed

+138
-12
lines changed

ci.jsonnet

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,15 @@
230230
"python-svm-build": gpgate + platform_spec(no_jobs) + platform_spec({
231231
"linux:amd64:jdk-latest" : tier2 + provide(GPY_NATIVE_STANDALONE),
232232
}),
233+
"python-pgo-profile": gpgate_ee + platform_spec(no_jobs) + platform_spec({
234+
"linux:amd64:jdk-latest" : weekly + t("01:30:00") + task_spec({
235+
run: [["mx", "python-native-pgo"]],
236+
logs+: [
237+
"default.iprof.gz",
238+
"default.lcov",
239+
],
240+
}),
241+
}),
233242
"python-svm-unittest": gpgate + platform_spec(no_jobs) + platform_spec({
234243
"linux:amd64:jdk-latest" : tier2 + require(GPY_NATIVE_STANDALONE),
235244
"linux:aarch64:jdk-latest" : tier3 + provide(GPY_NATIVE_STANDALONE),

graalpython/com.oracle.graal.python.test/src/tests/test_venv.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,4 +110,3 @@ def test_create_and_use_venv_with_pip(self):
110110
run_output = err.output.decode(errors="replace")
111111
assert run, run_output
112112
assert "pip" in run, run
113-
# assert "setuptools" in run, run

mx.graalpython/mx_graalpython.py

Lines changed: 119 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import datetime
2929
import fnmatch
3030
import glob
31+
import gzip
3132
import itertools
3233
import os
3334
import pathlib
@@ -41,15 +42,12 @@
4142
from pathlib import Path
4243
from textwrap import dedent
4344

44-
from typing import cast
45+
from typing import cast, Union
4546

4647
import downstream_tests
4748
import mx_graalpython_benchmark
4849
import mx_urlrewrites
4950

50-
if sys.version_info[0] < 3:
51-
raise RuntimeError("The build scripts are no longer compatible with Python 2")
52-
5351
import tempfile
5452
from argparse import ArgumentParser
5553
from dataclasses import dataclass
@@ -274,9 +272,104 @@ def libpythonvm_build_args():
274272
build_args += bytecode_dsl_build_args()
275273
if mx_sdk_vm_ng.is_nativeimage_ee() and mx.get_os() == 'linux' and 'NATIVE_IMAGE_AUXILIARY_ENGINE_CACHE' not in os.environ:
276274
build_args += ['--gc=G1', '-H:-ProtectionKeys']
275+
if not os.environ.get("GRAALPY_PGO_PROFILE") and mx.suite('graalpython-enterprise', fatalIfMissing=False):
276+
cmd = mx.command_function('python-get-latest-profile', fatalIfMissing=False)
277+
if cmd:
278+
profile = None
279+
try:
280+
profile = cmd([])
281+
except BaseException:
282+
pass
283+
if profile and os.path.exists(profile):
284+
mx.log(f"Using PGO profile {profile}")
285+
build_args += [
286+
f"--pgo={profile}",
287+
"-H:+UnlockExperimentalVMOptions",
288+
"-H:+PGOPrintProfileQuality",
289+
"-H:-UnlockExperimentalVMOptions",
290+
]
291+
else:
292+
mx.log(f"Not using any PGO profile")
277293
return build_args
278294

279295

296+
def graalpy_native_pgo_build_and_test(_):
297+
"""
298+
Builds a PGO-instrumented GraalPy native standalone, runs the unittests to generate a profile,
299+
then builds a PGO-optimized GraalPy native standalone with the collected profile.
300+
The profile file will be named 'default.iprof' in native image build directory.
301+
"""
302+
with set_env(GRAALPY_PGO_PROFILE=""):
303+
mx.log(mx.colorize("[PGO] Building PGO-instrumented native image", color="yellow"))
304+
build_home = graalpy_standalone_home('native', enterprise=True, build=True)
305+
instrumented_home = build_home + "_PGO_INSTRUMENTED"
306+
shutil.rmtree(instrumented_home, ignore_errors=True)
307+
shutil.copytree(build_home, instrumented_home, symlinks=True, ignore_dangling_symlinks=True)
308+
instrumented_launcher = os.path.join(instrumented_home, 'bin', _graalpy_launcher())
309+
310+
mx.log(mx.colorize(f"[PGO] Instrumented build complete: {instrumented_home}", color="yellow"))
311+
312+
mx.log(mx.colorize(f"[PGO] Running graalpytest with instrumented binary: {instrumented_launcher}", color="yellow"))
313+
with tempfile.TemporaryDirectory() as d:
314+
with set_env(
315+
GRAALPYTEST_ALLOW_NO_JAVA_ASSERTIONS="true",
316+
GRAAL_PYTHON_VM_ARGS="\v".join([
317+
f"--vm.XX:ProfilesDumpFile={os.path.join(d, '$UUID$.iprof')}",
318+
f"--vm.XX:ProfilesLCOVFile={os.path.join(d, '$UUID$.info')}",
319+
]),
320+
GRAALPY_HOME=instrumented_home,
321+
):
322+
graalpytest(["--python", instrumented_launcher, "test_venv.py"])
323+
mx.command_function('benchmark')(["meso-small:*", "--", "--python-vm", "graalpython", "--python-vm-config", "custom"])
324+
325+
iprof_path = Path(SUITE.dir) / 'default.iprof'
326+
lcov_path = Path(SUITE.dir) / 'default.lcov'
327+
328+
run([
329+
os.path.join(
330+
graalvm_jdk(enterprise=True),
331+
"bin",
332+
f"native-image-configure{'.exe' if mx.is_windows() else ''}",
333+
),
334+
"merge-pgo-profiles",
335+
f"--input-dir={d}",
336+
f"--output-file={iprof_path}"
337+
])
338+
run([
339+
"/usr/bin/env",
340+
"lcov",
341+
"-o", str(lcov_path),
342+
*itertools.chain.from_iterable([
343+
["-a", f.absolute().as_posix()] for f in Path(d).glob("*.info")
344+
])
345+
], nonZeroIsFatal=False)
346+
run([
347+
"/usr/bin/env",
348+
"genhtml",
349+
"--source-directory", str(Path(SUITE.dir) / "com.oracle.graal.python" / "src"),
350+
"--source-directory", str(Path(SUITE.dir) / "com.oracle.graal.python.pegparser" / "src"),
351+
"--source-directory", str(Path(SUITE.get_output_root()) / "com.oracle.graal.python" / "src_gen"),
352+
"--include", "com/oracle/graal/python",
353+
"--keep-going",
354+
"-o", "lcov_html",
355+
str(lcov_path),
356+
], nonZeroIsFatal=False)
357+
358+
if not os.path.isfile(iprof_path):
359+
mx.abort(f"[PGO] Could not find profile file at expected location: {iprof_path}")
360+
361+
with set_env(GRAALPY_PGO_PROFILE=str(iprof_path)):
362+
mx.log(mx.colorize("[PGO] Building optimized native image with collected profile", color="yellow"))
363+
native_bin = graalpy_standalone('native', enterprise=True, build=True)
364+
365+
mx.log(mx.colorize(f"[PGO] Optimized PGO build complete: {native_bin}", color="yellow"))
366+
367+
iprof_gz_path = str(iprof_path) + '.gz'
368+
with open(iprof_path, 'rb') as f_in, gzip.open(iprof_gz_path, 'wb') as f_out:
369+
shutil.copyfileobj(f_in, f_out)
370+
mx.log(mx.colorize(f"[PGO] Gzipped profile at: {iprof_gz_path}", color="yellow"))
371+
372+
280373
def full_python(args, env=None):
281374
"""Run python from standalone build (unless kwargs are given). Does not build GraalPython sources automatically."""
282375

@@ -666,6 +759,19 @@ def graalpy_standalone_home(standalone_type, enterprise=False, dev=False, build=
666759
if BUILD_NATIVE_IMAGE_WITH_ASSERTIONS:
667760
mx_args.append("--extra-image-builder-argument=-ea")
668761

762+
pgo_profile = os.environ.get("GRAALPY_PGO_PROFILE")
763+
if pgo_profile is not None:
764+
if not enterprise or standalone_type != "native":
765+
mx.abort("PGO is only supported on enterprise NI")
766+
if pgo_profile:
767+
mx_args.append(f"--extra-image-builder-argument=--pgo={pgo_profile}")
768+
mx_args.append(f"--extra-image-builder-argument=-H:+UnlockExperimentalVMOptions")
769+
mx_args.append(f"--extra-image-builder-argument=-H:+PGOPrintProfileQuality")
770+
else:
771+
mx_args.append(f"--extra-image-builder-argument=--pgo-instrument")
772+
mx_args.append(f"--extra-image-builder-argument=-H:+UnlockExperimentalVMOptions")
773+
mx_args.append(f"--extra-image-builder-argument=-H:+ProfilingLCOV")
774+
669775
if mx_gate.get_jacoco_agent_args() or (build and not DISABLE_REBUILD):
670776
mx_build_args = mx_args
671777
if BYTECODE_DSL_INTERPRETER:
@@ -908,7 +1014,9 @@ def graalpytest(args):
9081014
python_binary = graalpy_standalone_native()
9091015
elif 'graalpy' in os.path.basename(python_binary) or 'mxbuild' in python_binary:
9101016
is_graalpy = True
911-
gp_args = ["--vm.ea", "--vm.esa", "--experimental-options=true", "--python.EnableDebuggingBuiltins"]
1017+
gp_args = ["--experimental-options=true", "--python.EnableDebuggingBuiltins"]
1018+
if env.get("GRAALPYTEST_ALLOW_NO_JAVA_ASSERTIONS") != "true":
1019+
gp_args += ["--vm.ea", "--vm.esa"]
9121020
mx.log(f"Executable seems to be GraalPy, prepending arguments: {gp_args}")
9131021
python_args += gp_args
9141022
if is_graalpy and BYTECODE_DSL_INTERPRETER:
@@ -963,7 +1071,7 @@ def is_included(path):
9631071

9641072
def run_python_unittests(python_binary, args=None, paths=None, exclude=None, env=None,
9651073
use_pytest=False, cwd=None, lock=None, out=None, err=None, nonZeroIsFatal=True, timeout=None,
966-
report=False, parallel=None, runner_args=None):
1074+
report: Union[Task, bool, None] = False, parallel=None, runner_args=None):
9671075
if lock:
9681076
lock.acquire()
9691077

@@ -1039,7 +1147,7 @@ def run_python_unittests(python_binary, args=None, paths=None, exclude=None, env
10391147
return result
10401148

10411149

1042-
def run_hpy_unittests(python_binary, args=None, env=None, nonZeroIsFatal=True, timeout=None, report=False):
1150+
def run_hpy_unittests(python_binary, args=None, env=None, nonZeroIsFatal=True, timeout=None, report: Union[Task, bool, None] = False):
10431151
t0 = time.time()
10441152
result = downstream_tests.downstream_test_hpy(python_binary, args=args, env=env, check=nonZeroIsFatal, timeout=timeout)
10451153
if report:
@@ -1051,7 +1159,7 @@ def run_hpy_unittests(python_binary, args=None, env=None, nonZeroIsFatal=True, t
10511159

10521160

10531161
def run_tagged_unittests(python_binary, env=None, cwd=None, nonZeroIsFatal=True, checkIfWithGraalPythonEE=False,
1054-
report=False, parallel=8, exclude=None, paths=()):
1162+
report: Union[Task, bool, None] = False, parallel=8, exclude=None, paths=()):
10551163

10561164
if checkIfWithGraalPythonEE:
10571165
mx.run([python_binary, "-c", "import sys; print(sys.version)"])
@@ -1323,13 +1431,13 @@ def graalpython_gate_runner(args, tasks):
13231431
if task:
13241432
run_mx([
13251433
"--dy", "graalpython,/substratevm",
1326-
"-p", os.path.join(mx.suite("truffle"), "..", "vm"),
1434+
"-p", os.path.join(mx.suite("truffle").dir, "..", "vm"),
13271435
"--native-images=",
13281436
"build",
13291437
], env={**os.environ, **LATEST_JAVA_HOME})
13301438
run_mx([
13311439
"--dy", "graalpython,/substratevm",
1332-
"-p", os.path.join(mx.suite("truffle"), "..", "vm"),
1440+
"-p", os.path.join(mx.suite("truffle").dir, "..", "vm"),
13331441
"--native-images=",
13341442
"gate", "svm-truffle-tck-python",
13351443
])
@@ -2554,4 +2662,5 @@ def run_downstream_test(args):
25542662
'graalpy-jmh': [graalpy_jmh, ''],
25552663
'deploy-local-maven-repo': [deploy_local_maven_repo_wrapper, ''],
25562664
'downstream-test': [run_downstream_test, ''],
2665+
'python-native-pgo': [graalpy_native_pgo_build_and_test, 'Build PGO-instrumented native image, run tests, then build PGO-optimized native image'],
25572666
})

mx.graalpython/mx_graalpython_benchmark.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262

6363
PYTHON_VM_REGISTRY_NAME = "Python"
6464
CONFIGURATION_DEFAULT = "default"
65+
CONFIGURATION_CUSTOM = "custom"
6566
CONFIGURATION_INTERPRETER = "interpreter"
6667
CONFIGURATION_NATIVE_INTERPRETER = "native-interpreter"
6768
CONFIGURATION_DEFAULT_MULTI = "default-multi"
@@ -279,6 +280,13 @@ def launcher_type(self):
279280
@property
280281
@functools.lru_cache
281282
def interpreter(self):
283+
if self.config_name() == CONFIGURATION_CUSTOM:
284+
home = mx.get_env("GRAALPY_HOME")
285+
if not home:
286+
mx.abort("The custom benchmark config for graalpy is to run with a custom GRAALPY_HOME locally")
287+
launcher = join(home, "bin", "graalpy")
288+
mx.log(f"Using {launcher} based on GRAALPY_HOME environment for custom config.")
289+
return launcher
282290
from mx_graalpython import graalpy_standalone
283291
launcher = graalpy_standalone(self.launcher_type, build=False)
284292
mx.log(f"Using {launcher} based on enabled/excluded GraalPy standalone build targets.")
@@ -1029,6 +1037,7 @@ def add_graalpy_vm(name, *extra_polyglot_args):
10291037
python_vm_registry.add_vm(GraalPythonVm(config_name=name, extra_polyglot_args=extra_polyglot_args), suite, 10)
10301038

10311039
# GraalPy VMs:
1040+
add_graalpy_vm(CONFIGURATION_CUSTOM)
10321041
add_graalpy_vm(CONFIGURATION_DEFAULT)
10331042
add_graalpy_vm(CONFIGURATION_INTERPRETER, '--experimental-options', '--engine.Compilation=false')
10341043
add_graalpy_vm(CONFIGURATION_DEFAULT_MULTI, '--experimental-options', '-multi-context')

mx.graalpython/native-ee

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
DYNAMIC_IMPORTS=/tools,/graal-enterprise,/truffle-enterprise,/vm-enterprise,/substratevm-enterprise,substratevm-enterprise-gcs
1+
DYNAMIC_IMPORTS=/tools,/graalpython-enterprise,/graal-enterprise,/truffle-enterprise,/vm-enterprise,/substratevm-enterprise,substratevm-enterprise-gcs
22
BUILD_TARGETS=GRAALPY_NATIVE_STANDALONE
33
COMPONENTS=SubstrateVM Enterprise,Truffle SVM Macro Enterprise,suite:substratevm-enterprise-gcs
44
NATIVE_IMAGES=lib:pythonvm

0 commit comments

Comments
 (0)