| # This script is best run with pystats enabled to help visualize the shape of the traces. |
| # ./configure --enable-experimental-jit=interpreter -C --with-pydebug --enable-pystats |
| |
| # The resulting images can be visualize on linux as follows: |
| # $ cd folder_with_gv_files |
| # $ dot -Tsvg -Osvg *.gv |
| # $ firefox *.gv.svg |
| |
| # type: ignore |
| |
| import sys |
| import os.path |
| from types import FunctionType |
| |
| # All functions declared in this module will be run to generate |
| # a .gv file of the executors, unless the name starts with an underscore. |
| |
| |
| def _gen(n): |
| for _ in range(n): |
| yield n |
| |
| |
| def gen_in_loop(n): |
| t = 0 |
| for n in _gen(n): |
| t += n |
| return n |
| |
| |
| def short_loop(n): |
| t = 0 |
| for _ in range(n): |
| t += 1 |
| t += 1 |
| t += 1 |
| t += 1 |
| t += 1 |
| return t |
| |
| |
| exec( |
| "\n".join( |
| ["def mid_loop(n):"] |
| + [" t = 0"] |
| + [" for _ in range(n):"] |
| + [" t += 1"] * 20 |
| + [" return t"] |
| ), |
| globals(), |
| ) |
| |
| exec( |
| "\n".join( |
| ["def long_loop(n):"] |
| + [" t = 0"] |
| + [" for _ in range(n):"] |
| + [" t += 1"] * 100 |
| + [" return t"] |
| ), |
| globals(), |
| ) |
| |
| |
| def _add(a, b): |
| return a + b |
| |
| |
| def short_loop_with_calls(n): |
| t = 0 |
| for _ in range(n): |
| t = _add(t, 1) |
| t = _add(t, 1) |
| t = _add(t, 1) |
| t = _add(t, 1) |
| t = _add(t, 1) |
| return t |
| |
| |
| exec( |
| "\n".join( |
| ["def mid_loop_with_calls(n):"] |
| + [" t = 0"] |
| + [" for _ in range(n):"] |
| + [" t = _add(t, 1)"] * 20 |
| + [" return t"] |
| ), |
| globals(), |
| ) |
| |
| exec( |
| "\n".join( |
| ["def long_loop_with_calls(n):"] |
| + [" t = 0"] |
| + [" for _ in range(n):"] |
| + [" t = _add(t, 1)"] * 100 |
| + [" return t"] |
| ), |
| globals(), |
| ) |
| |
| |
| def short_loop_with_side_exits(n): |
| t = 0 |
| for i in range(n): |
| if t < 0: |
| break |
| t += 1 |
| if t < 0: |
| break |
| t += 1 |
| if t < 0: |
| break |
| t += 1 |
| if t < 0: |
| break |
| t += 1 |
| if t < 0: |
| break |
| t += 1 |
| return t |
| |
| |
| exec( |
| "\n".join( |
| ["def mid_loop_with_side_exits(n):"] |
| + [" t = 0"] |
| + [" for _ in range(n):"] |
| + [" if t < 0:", " break", " t += 1"] * 20 |
| + [" return t"] |
| ), |
| globals(), |
| ) |
| |
| exec( |
| "\n".join( |
| ["def long_loop_with_side_exits(n):"] |
| + [" t = 0"] |
| + [" for _ in range(n):"] |
| + [" if t < 0:", " break", " t += 1"] * 100 |
| + [" return t"] |
| ), |
| globals(), |
| ) |
| |
| |
| def short_branchy_loop(n): |
| # Branches are correlated and exit 1 time in 4. |
| t = 0 |
| for i in range(n): |
| # Start with a few operations to form a viable trace |
| t += 1 |
| t += 1 |
| t += 1 |
| if not t & 6: |
| continue |
| t += 1 |
| if not t & 12: |
| continue |
| t += 1 |
| if not t & 24: |
| continue |
| t += 1 |
| if not t & 48: |
| continue |
| t += 1 |
| return t |
| |
| |
| def _run_and_dump(func, n, outdir): |
| sys._clear_internal_caches() |
| func(n) |
| sys._dump_tracelets(os.path.join(outdir, f"{func.__name__}.gv")) |
| |
| |
| def _main(): |
| if len(sys.argv) < 2 or len(sys.argv) > 3: |
| print(f"Usage: {sys.argv[0] if sys.argv else " "} OUTDIR [loops]") |
| outdir = sys.argv[1] |
| n = int(sys.argv[2]) if len(sys.argv) > 2 else 5000 |
| functions = [ |
| func |
| for func in globals().values() |
| if isinstance(func, FunctionType) and not func.__name__.startswith("_") |
| ] |
| for func in functions: |
| _run_and_dump(func, n, outdir) |
| |
| |
| if __name__ == "__main__": |
| _main() |