-
-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Expand file tree
/
Copy pathprofile_check.py
More file actions
145 lines (116 loc) · 4.99 KB
/
profile_check.py
File metadata and controls
145 lines (116 loc) · 4.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
"""Compile mypy using mypyc and profile type checking using perf.
By default does a self check.
Notes:
- Only Linux is supported for now (TODO: add support for other profilers)
- The profile is collected at C level
- It includes C functions compiled by mypyc and CPython runtime functions
- The names of mypy functions are mangled to C names, but usually it's clear what they mean
- Generally CPyDef_ prefix for native functions and CPyPy_ prefix for wrapper functions
- It's important to compile CPython using special flags (see below) to get good results
- Generally use the latest Python feature release (or the most recent beta if supported by mypyc)
- The tool prints a command that can be used to analyze the profile afterwards
You may need to adjust kernel parameters temporarily, e.g. this (note that this has security
implications):
sudo sysctl kernel.perf_event_paranoid=-1
This is the recommended way to configure CPython for profiling:
./configure \
--enable-optimizations \
--with-lto \
CFLAGS="-O2 -g -fno-omit-frame-pointer"
"""
from __future__ import annotations
import argparse
import glob
import os
import shutil
import subprocess
import sys
import time
from perf_compare import build_mypy, clone
# Use these C compiler flags when compiling mypy (important). Note that it's strongly recommended
# to also compile CPython using similar flags, but we don't enforce it in this script.
CFLAGS = "-O2 -fno-omit-frame-pointer -g"
# Generated files, including binaries, go under this directory to avoid overwriting user state.
TARGET_DIR = "mypy.profile.tmpdir"
def _profile_type_check(target_dir: str, code: str | None) -> None:
cache_dir = os.path.join(target_dir, ".mypy_cache")
if os.path.exists(cache_dir):
shutil.rmtree(cache_dir)
args = []
if code is None:
args.extend(["--config-file", "mypy_self_check.ini"])
for pat in "mypy/*.py", "mypy/*/*.py", "mypyc/*.py", "mypyc/test/*.py":
args.extend(glob.glob(pat))
else:
args.extend(["-c", code])
check_cmd = ["python", "-m", "mypy"] + args
cmdline = ["perf", "record", "-g"] + check_cmd
t0 = time.time()
subprocess.run(cmdline, cwd=target_dir, check=True)
elapsed = time.time() - t0
print(f"{elapsed:.2f}s elapsed")
def profile_type_check(target_dir: str, code: str | None) -> None:
try:
_profile_type_check(target_dir, code)
except subprocess.CalledProcessError:
print("\nProfiling failed! You may missing some permissions.")
print("\nThis may help (note that it has security implications):")
print(" sudo sysctl kernel.perf_event_paranoid=-1")
sys.exit(1)
def check_requirements() -> None:
if sys.platform != "linux":
# TODO: How to make this work on other platforms?
sys.exit("error: Only Linux is supported")
else: # fun fact/todo: we have to use else here, because of https://github.com/python/mypy/issues/10773
try:
subprocess.run(["perf", "-h"], capture_output=True)
except (subprocess.CalledProcessError, FileNotFoundError):
print("error: The 'perf' profiler is not installed")
sys.exit(1)
try:
subprocess.run(["clang", "--version"], capture_output=True)
except (subprocess.CalledProcessError, FileNotFoundError):
print("error: The clang compiler is not installed")
sys.exit(1)
if not os.path.isfile("mypy_self_check.ini"):
print("error: Run this in the mypy repository root")
sys.exit(1)
def main() -> None:
check_requirements()
parser = argparse.ArgumentParser(
description="Compile mypy and profile type checking using 'perf' (by default, self check)."
)
parser.add_argument(
"--multi-file",
action="store_true",
help="compile mypy into one C file per module (to reduce RAM use during compilation)",
)
parser.add_argument(
"--skip-compile", action="store_true", help="use compiled mypy from previous run"
)
parser.add_argument(
"-c",
metavar="CODE",
default=None,
type=str,
help="profile type checking Python code fragment instead of mypy self-check",
)
args = parser.parse_args()
multi_file: bool = args.multi_file
skip_compile: bool = args.skip_compile
code: str | None = args.c
target_dir = TARGET_DIR
if not skip_compile:
clone(target_dir, "HEAD")
print(f"Building mypy in {target_dir}...")
build_mypy(target_dir, multi_file, cflags=CFLAGS)
elif not os.path.isdir(target_dir):
sys.exit("error: Can't find compile mypy from previous run -- can't use --skip-compile")
profile_type_check(target_dir, code)
print()
print('NOTE: Compile CPython using CFLAGS="-O2 -g -fno-omit-frame-pointer" for good results')
print()
print("CPU profile collected. You can now analyze the profile:")
print(f" perf report -i {target_dir}/perf.data ")
if __name__ == "__main__":
main()