99from collections import defaultdict
1010import csv
1111import functools
12+ import json
1213from operator import itemgetter
1314from pathlib import Path
1415import re
4041 "_PyPegen_.+" ,
4142 "_PyStack_.+" ,
4243 "_PyVectorcall_.+" ,
44+ "_TAIL_CALL_.+" ,
4345 "advance" ,
4446 "call_instrumentation_vector.*" ,
4547 "initialize_locals" ,
@@ -414,6 +416,52 @@ def plot_pie(categories: list[tuple[float, str]], output_filename: PathLike):
414416 fig .savefig (output_filename , dpi = 200 )
415417
416418
419+ def handle_tail_call_stats (
420+ categories : defaultdict [str , defaultdict [tuple [str , str ], float ]],
421+ output_prefix : Path ,
422+ ):
423+ tail_call_stats = defaultdict (float )
424+ total_time = 0.0
425+ for (_ , sym ), self_time in categories ["interpreter" ].items ():
426+ if (bytecode := sym .removeprefix ("_TAIL_CALL_" )) != sym :
427+ tail_call_stats [bytecode ] += self_time
428+ total_time += self_time
429+
430+ with Path ("pystats.json" ).open () as fd :
431+ pystats = json .load (fd )
432+
433+ pystats_bytecodes = defaultdict (int )
434+ total_count = 0
435+ for key , val in pystats .items ():
436+ if match := re .match (r"opcode\[(.+)\]\.execution_count" , key ):
437+ pystats_bytecodes [match .group (1 )] += val
438+ total_count += val
439+
440+ if len (tail_call_stats ) == 0 :
441+ return
442+
443+ with open (output_prefix .with_suffix (".tail_calls.csv" ), "w" ) as csvfile :
444+ writer = csv .writer (csvfile , dialect = "unix" )
445+ writer .writerow (
446+ ["Bytecode" , "% time" , "count" , "% count" , "time per count (ns)" ]
447+ )
448+ for bytecode , seconds in sorted (
449+ tail_call_stats .items (), key = itemgetter (1 ), reverse = True
450+ ):
451+ count = pystats_bytecodes [bytecode ]
452+ if count == 0 :
453+ continue
454+ writer .writerow (
455+ [
456+ bytecode ,
457+ f"{ seconds / total_time :.02%} " ,
458+ count ,
459+ f"{ count / total_count :.02%} " ,
460+ f"{ (seconds / count ) * 1e9 :03f} " ,
461+ ]
462+ )
463+
464+
417465def _main (input_dir : PathLike , output_prefix : PathLike ):
418466 input_dir = Path (input_dir )
419467 output_prefix = Path (output_prefix )
@@ -427,6 +475,8 @@ def _main(input_dir: PathLike, output_prefix: PathLike):
427475
428476 with output_prefix .with_suffix (".md" ).open ("w" ) as md :
429477 for csv_path in sorted (input_dir .glob ("*.csv" )):
478+ if "tail_calls.csv" in csv_path .name :
479+ continue
430480 handle_benchmark (csv_path , md , results , categories )
431481
432482 sorted_categories = sorted (
@@ -454,6 +504,8 @@ def _main(input_dir: PathLike, output_prefix: PathLike):
454504 plot_bargraph (results , sorted_categories , output_prefix .with_suffix (".svg" ))
455505 plot_pie (sorted_categories , output_prefix .with_suffix (".pie.svg" ))
456506
507+ handle_tail_call_stats (categories , output_prefix )
508+
457509
458510def main ():
459511 parser = argparse .ArgumentParser (
0 commit comments