Skip to content

Commit 32752eb

Browse files
authored
[mypyc] Add script to compile mypy with trace logging and run mypy (#19475)
Trace logging helps when analyzing low-level performance issues in mypy.
1 parent 8bd159b commit 32752eb

File tree

3 files changed

+98
-2
lines changed

3 files changed

+98
-2
lines changed

misc/log_trace_check.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
"""Compile mypy using mypyc with trace logging enabled, and collect a trace.
2+
3+
The trace log can be used to analyze low-level performance bottlenecks.
4+
5+
By default does a self check as the workload.
6+
7+
This works on all supported platforms, unlike some of our other performance tools.
8+
"""
9+
10+
from __future__ import annotations
11+
12+
import argparse
13+
import glob
14+
import os
15+
import shutil
16+
import subprocess
17+
import sys
18+
import time
19+
20+
from perf_compare import build_mypy, clone
21+
22+
# Generated files, including binaries, go under this directory to avoid overwriting user state.
23+
TARGET_DIR = "mypy.log_trace.tmpdir"
24+
25+
26+
def perform_type_check(target_dir: str, code: str | None) -> None:
27+
cache_dir = os.path.join(target_dir, ".mypy_cache")
28+
if os.path.exists(cache_dir):
29+
shutil.rmtree(cache_dir)
30+
args = []
31+
if code is None:
32+
args.extend(["--config-file", "mypy_self_check.ini"])
33+
for pat in "mypy/*.py", "mypy/*/*.py", "mypyc/*.py", "mypyc/test/*.py":
34+
args.extend(glob.glob(pat))
35+
else:
36+
args.extend(["-c", code])
37+
check_cmd = ["python", "-m", "mypy"] + args
38+
t0 = time.time()
39+
subprocess.run(check_cmd, cwd=target_dir, check=True)
40+
elapsed = time.time() - t0
41+
print(f"{elapsed:.2f}s elapsed")
42+
43+
44+
def main() -> None:
45+
parser = argparse.ArgumentParser(
46+
description="Compile mypy and collect a trace log while type checking (by default, self check)."
47+
)
48+
parser.add_argument(
49+
"--multi-file",
50+
action="store_true",
51+
help="compile mypy into one C file per module (to reduce RAM use during compilation)",
52+
)
53+
parser.add_argument(
54+
"--skip-compile", action="store_true", help="use compiled mypy from previous run"
55+
)
56+
parser.add_argument(
57+
"-c",
58+
metavar="CODE",
59+
default=None,
60+
type=str,
61+
help="type check Python code fragment instead of mypy self-check",
62+
)
63+
args = parser.parse_args()
64+
multi_file: bool = args.multi_file
65+
skip_compile: bool = args.skip_compile
66+
code: str | None = args.c
67+
68+
target_dir = TARGET_DIR
69+
70+
if not skip_compile:
71+
clone(target_dir, "HEAD")
72+
73+
print(f"Building mypy in {target_dir} with trace logging enabled...")
74+
build_mypy(target_dir, multi_file, log_trace=True, opt_level="0")
75+
elif not os.path.isdir(target_dir):
76+
sys.exit("error: Can't find compile mypy from previous run -- can't use --skip-compile")
77+
78+
perform_type_check(target_dir, code)
79+
80+
trace_fnam = os.path.join(target_dir, "mypyc_trace.txt")
81+
print(f"Generated event trace log in {trace_fnam}")
82+
83+
84+
if __name__ == "__main__":
85+
main()

misc/perf_compare.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,22 @@ def heading(s: str) -> None:
3737
print()
3838

3939

40-
def build_mypy(target_dir: str, multi_file: bool, *, cflags: str | None = None) -> None:
40+
def build_mypy(
41+
target_dir: str,
42+
multi_file: bool,
43+
*,
44+
cflags: str | None = None,
45+
log_trace: bool = False,
46+
opt_level: str = "2",
47+
) -> None:
4148
env = os.environ.copy()
4249
env["CC"] = "clang"
43-
env["MYPYC_OPT_LEVEL"] = "2"
50+
env["MYPYC_OPT_LEVEL"] = opt_level
4451
env["PYTHONHASHSEED"] = "1"
4552
if multi_file:
4653
env["MYPYC_MULTI_FILE"] = "1"
54+
if log_trace:
55+
env["MYPYC_LOG_TRACE"] = "1"
4756
if cflags is not None:
4857
env["CFLAGS"] = cflags
4958
cmd = [sys.executable, "setup.py", "--use-mypyc", "build_ext", "--inplace"]

setup.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,13 +145,15 @@ def run(self) -> None:
145145
opt_level = os.getenv("MYPYC_OPT_LEVEL", "3")
146146
debug_level = os.getenv("MYPYC_DEBUG_LEVEL", "1")
147147
force_multifile = os.getenv("MYPYC_MULTI_FILE", "") == "1"
148+
log_trace = bool(int(os.getenv("MYPYC_LOG_TRACE", "0")))
148149
ext_modules = mypycify(
149150
mypyc_targets + ["--config-file=mypy_bootstrap.ini"],
150151
opt_level=opt_level,
151152
debug_level=debug_level,
152153
# Use multi-file compilation mode on windows because without it
153154
# our Appveyor builds run out of memory sometimes.
154155
multi_file=sys.platform == "win32" or force_multifile,
156+
log_trace=log_trace,
155157
)
156158

157159
else:

0 commit comments

Comments
 (0)