Skip to content

Commit f93834f

Browse files
authored
GH-146073: Add example script for dumping JIT traces (GH-148840)
1 parent 858e69e commit f93834f

2 files changed

Lines changed: 196 additions & 0 deletions

File tree

Tools/jit/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,8 @@ If you're looking for information on how to update the JIT build dependencies, s
8686
[^pep-744]: [PEP 744](https://peps.python.org/pep-0744/)
8787

8888
[^why-llvm]: Clang is specifically needed because it's the only C compiler with support for guaranteed tail calls (`musttail`), which are required by CPython's continuation-passing-style approach to JIT compilation. Since LLVM also includes other functionalities we need (namely, object file parsing and disassembly), it's convenient to only support one toolchain at this time.
89+
90+
### Understanding JIT behavior
91+
92+
The [example_trace_dump.py](./example_trace_dump.py) script will (when configured as described in the script) dump out the
93+
executors for a range of tiny programs to show the behavior of the JIT front-end.

Tools/jit/example_trace_dump.py

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
# This script is best run with pystats enabled to help visualize the shape of the traces.
2+
# ./configure --enable-experimental-jit=interpreter -C --with-pydebug --enable-pystats
3+
4+
# The resulting images can be visualize on linux as follows:
5+
# $ cd folder_with_gv_files
6+
# $ dot -Tsvg -Osvg *.gv
7+
# $ firefox *.gv.svg
8+
9+
# type: ignore
10+
11+
import sys
12+
import os.path
13+
from types import FunctionType
14+
15+
# All functions declared in this module will be run to generate
16+
# a .gv file of the executors, unless the name starts with an underscore.
17+
18+
19+
def _gen(n):
20+
for _ in range(n):
21+
yield n
22+
23+
24+
def gen_in_loop(n):
25+
t = 0
26+
for n in _gen(n):
27+
t += n
28+
return n
29+
30+
31+
def short_loop(n):
32+
t = 0
33+
for _ in range(n):
34+
t += 1
35+
t += 1
36+
t += 1
37+
t += 1
38+
t += 1
39+
return t
40+
41+
42+
exec(
43+
"\n".join(
44+
["def mid_loop(n):"]
45+
+ [" t = 0"]
46+
+ [" for _ in range(n):"]
47+
+ [" t += 1"] * 20
48+
+ [" return t"]
49+
),
50+
globals(),
51+
)
52+
53+
exec(
54+
"\n".join(
55+
["def long_loop(n):"]
56+
+ [" t = 0"]
57+
+ [" for _ in range(n):"]
58+
+ [" t += 1"] * 100
59+
+ [" return t"]
60+
),
61+
globals(),
62+
)
63+
64+
65+
def _add(a, b):
66+
return a + b
67+
68+
69+
def short_loop_with_calls(n):
70+
t = 0
71+
for _ in range(n):
72+
t = _add(t, 1)
73+
t = _add(t, 1)
74+
t = _add(t, 1)
75+
t = _add(t, 1)
76+
t = _add(t, 1)
77+
return t
78+
79+
80+
exec(
81+
"\n".join(
82+
["def mid_loop_with_calls(n):"]
83+
+ [" t = 0"]
84+
+ [" for _ in range(n):"]
85+
+ [" t = _add(t, 1)"] * 20
86+
+ [" return t"]
87+
),
88+
globals(),
89+
)
90+
91+
exec(
92+
"\n".join(
93+
["def long_loop_with_calls(n):"]
94+
+ [" t = 0"]
95+
+ [" for _ in range(n):"]
96+
+ [" t = _add(t, 1)"] * 100
97+
+ [" return t"]
98+
),
99+
globals(),
100+
)
101+
102+
103+
def short_loop_with_side_exits(n):
104+
t = 0
105+
for i in range(n):
106+
if t < 0:
107+
break
108+
t += 1
109+
if t < 0:
110+
break
111+
t += 1
112+
if t < 0:
113+
break
114+
t += 1
115+
if t < 0:
116+
break
117+
t += 1
118+
if t < 0:
119+
break
120+
t += 1
121+
return t
122+
123+
124+
exec(
125+
"\n".join(
126+
["def mid_loop_with_side_exits(n):"]
127+
+ [" t = 0"]
128+
+ [" for _ in range(n):"]
129+
+ [" if t < 0:", " break", " t += 1"] * 20
130+
+ [" return t"]
131+
),
132+
globals(),
133+
)
134+
135+
exec(
136+
"\n".join(
137+
["def long_loop_with_side_exits(n):"]
138+
+ [" t = 0"]
139+
+ [" for _ in range(n):"]
140+
+ [" if t < 0:", " break", " t += 1"] * 100
141+
+ [" return t"]
142+
),
143+
globals(),
144+
)
145+
146+
147+
def short_branchy_loop(n):
148+
# Branches are correlated and exit 1 time in 4.
149+
t = 0
150+
for i in range(n):
151+
# Start with a few operations to form a viable trace
152+
t += 1
153+
t += 1
154+
t += 1
155+
if not t & 6:
156+
continue
157+
t += 1
158+
if not t & 12:
159+
continue
160+
t += 1
161+
if not t & 24:
162+
continue
163+
t += 1
164+
if not t & 48:
165+
continue
166+
t += 1
167+
return t
168+
169+
170+
def _run_and_dump(func, n, outdir):
171+
sys._clear_internal_caches()
172+
func(n)
173+
sys._dump_tracelets(os.path.join(outdir, f"{func.__name__}.gv"))
174+
175+
176+
def _main():
177+
if len(sys.argv) < 2 or len(sys.argv) > 3:
178+
print(f"Usage: {sys.argv[0] if sys.argv else " "} OUTDIR [loops]")
179+
outdir = sys.argv[1]
180+
n = int(sys.argv[2]) if len(sys.argv) > 2 else 5000
181+
functions = [
182+
func
183+
for func in globals().values()
184+
if isinstance(func, FunctionType) and not func.__name__.startswith("_")
185+
]
186+
for func in functions:
187+
_run_and_dump(func, n, outdir)
188+
189+
190+
if __name__ == "__main__":
191+
_main()

0 commit comments

Comments
 (0)