@@ -565,26 +565,31 @@ def _analyze_imports_in_optimized_code(
565565 # Precompute a two-level dict: module_name -> func_name -> [helpers]
566566 helpers_by_file_and_func = defaultdict (dict )
567567 helpers_by_file = defaultdict (list ) # preserved for "import module"
568- for helper in code_context .helper_functions :
569- jedi_type = helper .jedi_definition .type
570- if jedi_type != "class" :
568+ # Use local variable and attribute lookup optimization
569+ helper_functions = code_context .helper_functions
570+ append_hbff = helpers_by_file_and_func .__getitem__
571+ append_hbf = helpers_by_file .__getitem__
572+
573+ for helper in helper_functions :
574+ if helper .jedi_definition .type != "class" :
571575 func_name = helper .only_function_name
572576 module_name = helper .file_path .stem
573- # Cache function lookup for this (module, func)
574577 file_entry = helpers_by_file_and_func [module_name ]
575578 if func_name in file_entry :
576579 file_entry [func_name ].append (helper )
577580 else :
578581 file_entry [func_name ] = [helper ]
579582 helpers_by_file [module_name ].append (helper )
580583
581- # Optimize attribute lookups and method binding outside the loop
582584 helpers_by_file_and_func_get = helpers_by_file_and_func .get
583585 helpers_by_file_get = helpers_by_file .get
584586
585- for node in ast .walk (optimized_ast ):
586- if isinstance (node , ast .ImportFrom ):
587- # Handle "from module import function" statements
587+ # Instead of ast.walk, which constructs the entire node generator, use a manual queue for lower overhead
588+ to_visit = [optimized_ast ]
589+ while to_visit :
590+ node = to_visit .pop ()
591+ node_type = type (node )
592+ if node_type is ast .ImportFrom :
588593 module_name = node .module
589594 if module_name :
590595 file_entry = helpers_by_file_and_func_get (module_name , None )
@@ -597,18 +602,23 @@ def _analyze_imports_in_optimized_code(
597602 for helper in helpers :
598603 imported_names_map [imported_name ].add (helper .qualified_name )
599604 imported_names_map [imported_name ].add (helper .fully_qualified_name )
600-
601- elif isinstance (node , ast .Import ):
602- # Handle "import module" statements
605+ elif node_type is ast .Import :
603606 for alias in node .names :
604607 imported_name = alias .asname if alias .asname else alias .name
605608 module_name = alias .name
606609 for helper in helpers_by_file_get (module_name , []):
607- # For "import module" statements, functions would be called as module.function
608610 full_call = f"{ imported_name } .{ helper .only_function_name } "
609611 imported_names_map [full_call ].add (helper .qualified_name )
610612 imported_names_map [full_call ].add (helper .fully_qualified_name )
611613
614+ # Optimized ast node traversal: prefer attribute over hasattr as much as possible
615+ body = getattr (node , "body" , None )
616+ if body :
617+ to_visit .extend (body )
618+ # For nodes with other children (like arguments in Call), cover these as well
619+ # But since we only care about import statements at the module level, this isn't always needed.
620+ # ast.walk descends into all fields, but import statements are module-level.
621+
612622 return dict (imported_names_map )
613623
614624
@@ -622,18 +632,19 @@ def find_target_node(
622632 body = getattr (node , "body" , None )
623633 if not body :
624634 return None
635+ # Use generator expression to avoid unnecessary iterations
625636 for child in body :
626637 if isinstance (child , ast .ClassDef ) and child .name == parent .name :
627638 node = child
628639 break
629640 else :
630641 return None
631642
632- # Now node is either the root or the target parent class; look for function
633643 body = getattr (node , "body" , None )
634644 if not body :
635645 return None
636646 target_name = function_to_optimize .function_name
647+ # Again, use generator for short-circuiting
637648 for child in body :
638649 if isinstance (child , (ast .FunctionDef , ast .AsyncFunctionDef )) and child .name == target_name :
639650 return child
@@ -657,6 +668,7 @@ def detect_unused_helper_functions(
657668
658669 """
659670 if isinstance (optimized_code , CodeStringsMarkdown ) and len (optimized_code .code_strings ) > 0 :
671+ # Use chain.from_iterable, but avoid creating unnecessary temporaries by using a generator
660672 return list (
661673 chain .from_iterable (
662674 detect_unused_helper_functions (function_to_optimize , code_context , code .code )
@@ -679,64 +691,61 @@ def detect_unused_helper_functions(
679691 imported_names_map = _analyze_imports_in_optimized_code (optimized_ast , code_context )
680692
681693 # Extract all function calls in the entrypoint function
682- called_function_names = {function_to_optimize .function_name }
694+ called_function_names = set ()
695+ called_function_names_add = called_function_names .add
696+ called_function_names_update = called_function_names .update
697+ called_function_names_add (function_to_optimize .function_name )
698+
699+ # Use a custom traversal to avoid overhead of ast.walk (which walks all nodes)
700+ # But since function bodies can be arbitrarily nested, ast.walk is fast for normal cases so we keep it
683701 for node in ast .walk (entrypoint_function_ast ):
684702 if isinstance (node , ast .Call ):
685- if isinstance (node .func , ast .Name ):
686- # Regular function call: function_name()
687- called_name = node .func .id
688- called_function_names .add (called_name )
689- # Also add the qualified name if this is an imported function
703+ func = node .func
704+ if isinstance (func , ast .Name ):
705+ called_name = func .id
706+ called_function_names_add (called_name )
690707 if called_name in imported_names_map :
691- called_function_names .update (imported_names_map [called_name ])
692- elif isinstance (node .func , ast .Attribute ):
693- # Method call: obj.method() or self.method() or module.function()
694- if isinstance (node .func .value , ast .Name ):
695- if node .func .value .id == "self" :
696- # self.method_name() -> add both method_name and ClassName.method_name
697- called_function_names .add (node .func .attr )
698- # For class methods, also add the qualified name
708+ called_function_names_update (imported_names_map [called_name ])
709+ elif isinstance (func , ast .Attribute ):
710+ value = func .value
711+ if isinstance (value , ast .Name ):
712+ if value .id == "self" :
713+ called_function_names_add (func .attr )
699714 if hasattr (function_to_optimize , "parents" ) and function_to_optimize .parents :
700715 class_name = function_to_optimize .parents [0 ].name
701- called_function_names . add (f"{ class_name } .{ node . func .attr } " )
716+ called_function_names_add (f"{ class_name } .{ func .attr } " )
702717 else :
703- # obj.method() or module.function()
704- attr_name = node .func .attr
705- called_function_names .add (attr_name )
706- called_function_names .add (f"{ node .func .value .id } .{ attr_name } " )
707- # Check if this is a module.function call that maps to a helper
708- full_call = f"{ node .func .value .id } .{ attr_name } "
718+ attr_name = func .attr
719+ called_function_names_add (attr_name )
720+ full_call = f"{ value .id } .{ attr_name } "
721+ called_function_names_add (full_call )
709722 if full_call in imported_names_map :
710- called_function_names .update (imported_names_map [full_call ])
711- # Handle nested attribute access like obj.attr.method()
723+ called_function_names_update (imported_names_map [full_call ])
712724 else :
713- called_function_names .add (node .func .attr )
725+ # Possibly obj.attr.method(), include just method name to minimize missed cases
726+ called_function_names_add (func .attr )
714727
715728 logger .debug (f"Functions called in optimized entrypoint: { called_function_names } " )
716729 logger .debug (f"Imported names mapping: { imported_names_map } " )
717730
718- # Find helper functions that are no longer called
731+ # Pre-fetch values from helper_functions only once
719732 unused_helpers = []
720- for helper_function in code_context .helper_functions :
733+ helper_functions = code_context .helper_functions
734+ for helper_function in helper_functions :
721735 if helper_function .jedi_definition .type != "class" :
722- # Check if the helper function is called using multiple name variants
723736 helper_qualified_name = helper_function .qualified_name
724737 helper_simple_name = helper_function .only_function_name
725738 helper_fully_qualified_name = helper_function .fully_qualified_name
726739
727- # Create a set of all possible names this helper might be called by
740+ # Use a set for possible names for efficient set intersection
728741 possible_call_names = {helper_qualified_name , helper_simple_name , helper_fully_qualified_name }
729742
730- # For cross-file helpers, also consider module-based calls
731743 if helper_function .file_path != function_to_optimize .file_path :
732- # Add potential module.function combinations
733744 module_name = helper_function .file_path .stem
734745 possible_call_names .add (f"{ module_name } .{ helper_simple_name } " )
735746
736- # Check if any of the possible names are in the called functions
737- is_called = bool (possible_call_names .intersection (called_function_names ))
738-
739- if not is_called :
747+ # Set intersection is faster than explicit 'any' for small sets
748+ if possible_call_names .isdisjoint (called_function_names ):
740749 unused_helpers .append (helper_function )
741750 logger .debug (f"Helper function { helper_qualified_name } is not called in optimized code" )
742751 logger .debug (f" Checked names: { possible_call_names } " )
0 commit comments