diff --git a/.jules/bolt.md b/.jules/bolt.md index 254b8d5f..886629bc 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -13,3 +13,7 @@ ## 2025-02-18 - Regex Pre-compilation in Hot Paths **Learning:** Re-compiling regexes inside a frequently called function (like `latex_escape` which runs for every string) creates significant overhead. Pre-compiling them at module level yielded a ~3.2x speedup. **Action:** Always look for regex compilations inside loops or frequently called functions and move them to module level constants. + +## 2026-07-25 - Redundant Lowercasing in Generator Expressions +**Learning:** In Python, calling methods like `.lower()` on strings inside a comprehension or generator expression that iterates over the same text multiple times (e.g., `[kw for kw in items if kw in text.lower()]`) results in the `.lower()` operation being evaluated repeatedly for every iteration. This causes unnecessary $O(N)$ memory allocations and CPU overhead, especially on large strings. +**Action:** Cache the result of such operations outside the loop or generator expression (e.g., `text_lower = text.lower()`) and use the cached variable inside the comprehension. diff --git a/cli/generators/ats_generator.py b/cli/generators/ats_generator.py index ef9d8d53..4a5b89da 100644 --- a/cli/generators/ats_generator.py +++ b/cli/generators/ats_generator.py @@ -407,7 +407,9 @@ def _check_readability(self, resume_data: Dict[str, Any]) -> ATSCategoryScore: "improved", "achieved", ] - action_verb_count = sum(1 for verb in action_verbs if verb in all_text.lower()) + # Cache lowercased strings outside loops to prevent redundant O(N) allocations + # Note: all_text is already lowercased by _get_all_text + action_verb_count = sum(1 for verb in action_verbs if verb in all_text) if action_verb_count >= 3: details.append(f"✓ Uses action verbs ({action_verb_count} found)") diff --git a/cli/utils/yaml_parser.py b/cli/utils/yaml_parser.py index ec974162..4286fe1c 100644 --- a/cli/utils/yaml_parser.py +++ b/cli/utils/yaml_parser.py @@ -182,8 +182,11 @@ def _prioritize_skills(self, skills: Dict[str, list], technologies: list) -> Dic for skill in section_skills: skill_name = skill if isinstance(skill, str) else skill.get("name", "") + # Cache lowercased strings outside loops to prevent redundant O(N) allocations + skill_name_lower = skill_name.lower() + # Check if skill matches any of the technologies - if any(tech in skill_name.lower() for tech in tech_lower): + if any(tech in skill_name_lower for tech in tech_lower): matching.append(skill) else: non_matching.append(skill) @@ -215,6 +218,9 @@ def get_experience(self, variant: Optional[str] = None) -> list: max_bullets = variant_config.get("max_bullets_per_job", 4) emphasize_keywords = variant_config.get("emphasize_keywords", []) + # Cache lowercased strings outside loops to prevent redundant O(N) allocations + emphasize_keywords_lower = [kw.lower() for kw in emphasize_keywords] + filtered_exp = [] for job in experience: if not isinstance(job, dict): @@ -230,9 +236,12 @@ def get_experience(self, variant: Optional[str] = None) -> list: emphasize_for = bullet.get("emphasize_for", []) text = bullet.get("text", "") + # Cache lowercased strings outside loops to prevent redundant O(N) allocations + text_lower = text.lower() + # Include if variant is emphasized or keywords match if variant in emphasize_for or any( - kw.lower() in text.lower() for kw in emphasize_keywords + kw in text_lower for kw in emphasize_keywords_lower ): filtered_bullets.append(bullet)