Skip to content

⚡️ Speed up method Algorithms.register by 93% - #24

Open
codeflash-ai[bot] wants to merge 1 commit into
mainfrom
codeflash/optimize-Algorithms.register-mihb7ofk
Open

⚡️ Speed up method Algorithms.register by 93%#24
codeflash-ai[bot] wants to merge 1 commit into
mainfrom
codeflash/optimize-Algorithms.register-mihb7ofk

Conversation

@codeflash-ai

@codeflash-ai codeflash-ai Bot commented Nov 27, 2025

Copy link
Copy Markdown

📄 93% (0.93x) speedup for Algorithms.register in src/titiler/core/titiler/core/algorithm/__init__.py

⏱️ Runtime : 194 microseconds 101 microseconds (best of 250 runs)

📝 Explanation and details

The optimization replaces an O(n) loop-based duplicate checking approach with O(1) set intersection operations, achieving a 92% speedup.

Key optimization: Instead of checking name in self.data for each algorithm individually (which requires n dictionary lookups), the optimized code uses self.data.keys() & algorithms.keys() to find all overlapping keys in a single set intersection operation.

Why this is faster:

  • Original approach: For each of the n algorithms being registered, performs a dictionary membership test (name in self.data), resulting in O(n) operations
  • Optimized approach: Uses set intersection (&) which is implemented in C and operates on hash tables, finding all duplicates in effectively O(1) average-case time for typical workloads

Performance impact by test case:

  • Small registrations (1-10 algorithms): Slight overhead (~10-20% slower) due to set operation setup cost
  • Large registrations (500+ algorithms): Massive gains (169-189% faster) where the O(n) → O(1) optimization really pays off
  • Overwrite scenarios: Significant improvements (30-44% faster) since duplicate checking is bypassed entirely when overwrite=True

The line profiler confirms this: the original code spent 91.9% of time in the loop checking duplicates (for name, _algo + if name in self.data), while the optimized version spends only 9.1% checking the overwrite condition and 17.6% doing the set intersection.

Behavior preservation: The optimization maintains identical exception messages and error-on-first-duplicate behavior, making it a drop-in performance improvement.

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 🔘 None Found
🌀 Generated Regression Tests 32 Passed
⏪ Replay Tests 🔘 None Found
🔎 Concolic Coverage Tests 🔘 None Found
📊 Tests Coverage 100.0%
🌀 Generated Regression Tests and Runtime
from copy import copy
from typing import Dict, Type

# function to test
import attr
# imports
import pytest
from titiler.core.algorithm.__init__ import Algorithms

# Minimal stubs for BaseAlgorithm and subclasses for testing
class BaseAlgorithm:
    pass

class DummyAlgorithmA(BaseAlgorithm):
    pass

class DummyAlgorithmB(BaseAlgorithm):
    pass

class DummyAlgorithmC(BaseAlgorithm):
    pass

# Simulate default_algorithms as in the original code
default_algorithms: Dict[str, Type[BaseAlgorithm]] = {
    "algoA": DummyAlgorithmA,
    "algoB": DummyAlgorithmB,
}
from titiler.core.algorithm.__init__ import Algorithms

# Helper function to create a fresh Algorithms instance for each test
def fresh_algorithms():
    return Algorithms(copy(default_algorithms))

# --------------------------
# Basic Test Cases
# --------------------------

def test_register_new_algorithm():
    # Register a new algorithm with a unique name
    algos = fresh_algorithms()
    new_algo = {"algoC": DummyAlgorithmC}
    codeflash_output = algos.register(new_algo); result = codeflash_output # 1.40μs -> 1.78μs (21.3% slower)

def test_register_multiple_new_algorithms():
    # Register multiple new algorithms at once
    algos = fresh_algorithms()
    new_algos = {
        "algoC": DummyAlgorithmC,
        "algoD": DummyAlgorithmA,  # reuse class for simplicity
    }
    codeflash_output = algos.register(new_algos); result = codeflash_output # 1.63μs -> 1.85μs (11.9% slower)

def test_register_returns_new_instance():
    # Ensure that register returns a new Algorithms instance (immutability)
    algos = fresh_algorithms()
    new_algo = {"algoC": DummyAlgorithmC}
    codeflash_output = algos.register(new_algo); result = codeflash_output # 1.42μs -> 1.70μs (16.6% slower)

# --------------------------
# Edge Test Cases
# --------------------------

def test_register_existing_algorithm_without_overwrite_raises():
    # Try to register an algorithm with a name that already exists, without overwrite
    algos = fresh_algorithms()
    with pytest.raises(Exception) as excinfo:
        algos.register({"algoA": DummyAlgorithmC}) # 1.61μs -> 1.99μs (19.1% slower)

def test_register_existing_algorithm_with_overwrite():
    # Register an algorithm with a name that already exists, with overwrite=True
    algos = fresh_algorithms()
    codeflash_output = algos.register({"algoA": DummyAlgorithmC}, overwrite=True); result = codeflash_output # 1.80μs -> 1.36μs (32.7% faster)

def test_register_empty_dict_returns_new_instance():
    # Registering an empty dict should return a new instance with same data
    algos = fresh_algorithms()
    codeflash_output = algos.register({}); result = codeflash_output # 1.22μs -> 1.56μs (21.7% slower)

def test_register_with_non_algorithm_value():
    # Register with a value that is not a subclass of BaseAlgorithm
    algos = fresh_algorithms()
    class NotAnAlgorithm:
        pass
    # No type checking in function, but let's simulate a check
    # If we want to enforce, add an explicit check in register
    # For now, just test that it stores whatever is given
    codeflash_output = algos.register({"notalgo": NotAnAlgorithm}); result = codeflash_output # 1.85μs -> 2.15μs (13.9% slower)

def test_register_with_duplicate_keys_in_input():
    # Python dicts can't have duplicate keys, but test that last one wins
    algos = fresh_algorithms()
    # This is just a sanity check: {"x": A, "x": B} => {"x": B}
    codeflash_output = algos.register({"algoC": DummyAlgorithmA, "algoC": DummyAlgorithmB}); result = codeflash_output # 1.48μs -> 1.68μs (11.6% slower)

# --------------------------
# Large Scale Test Cases
# --------------------------

def test_register_many_algorithms():
    # Register a large number of new algorithms
    algos = fresh_algorithms()
    n = 500
    new_algos = {f"algo_{i}": DummyAlgorithmA for i in range(n)}
    codeflash_output = algos.register(new_algos); result = codeflash_output # 29.8μs -> 11.1μs (169% faster)
    for i in range(n):
        pass

def test_register_overwrite_many_algorithms():
    # Overwrite a large number of existing algorithms
    n = 500
    # Prepopulate with many algorithms
    base_algos = {f"algo_{i}": DummyAlgorithmA for i in range(n)}
    algos = Algorithms({**copy(default_algorithms), **base_algos})
    # Overwrite all with a different class
    overwrite_algos = {f"algo_{i}": DummyAlgorithmB for i in range(n)}
    codeflash_output = algos.register(overwrite_algos, overwrite=True); result = codeflash_output # 36.8μs -> 13.4μs (176% faster)
    for i in range(n):
        pass

def test_register_large_then_small():
    # Register a large batch, then a small batch
    algos = fresh_algorithms()
    n = 500
    new_algos = {f"algo_{i}": DummyAlgorithmA for i in range(n)}
    codeflash_output = algos.register(new_algos); result = codeflash_output # 29.0μs -> 10.5μs (176% faster)
    # Now register a small batch
    codeflash_output = result.register({"special_algo": DummyAlgorithmB}); result2 = codeflash_output # 2.15μs -> 2.04μs (5.18% faster)
    # Ensure all large batch still present
    for i in range(n):
        pass

def test_register_does_not_mutate_original():
    # Ensure that the original data dict is not mutated
    algos = fresh_algorithms()
    original_data = copy(algos.data)
    codeflash_output = algos.register({"algoC": DummyAlgorithmC}); result = codeflash_output # 1.72μs -> 2.06μs (16.6% slower)

def test_register_case_sensitivity():
    # Algorithm names are case-sensitive
    algos = fresh_algorithms()
    codeflash_output = algos.register({"AlgoA": DummyAlgorithmC}); result = codeflash_output # 1.50μs -> 1.70μs (11.7% slower)
    # They are treated as different keys

def test_register_with_none_value():
    # Register with None as value (should be allowed by current implementation)
    algos = fresh_algorithms()
    codeflash_output = algos.register({"algoNone": None}); result = codeflash_output # 1.41μs -> 1.78μs (20.7% slower)
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.
from copy import copy
from typing import Dict, Type

# function to test (from titiler/core/algorithm/__init__.py)
import attr
# imports
import pytest  # used for our unit tests
from titiler.core.algorithm.__init__ import Algorithms

# Dummy base class for algorithms
class BaseAlgorithm:
    pass

# Dummy algorithm classes for testing
class DummyAlgorithmA(BaseAlgorithm):
    pass

class DummyAlgorithmB(BaseAlgorithm):
    pass

class DummyAlgorithmC(BaseAlgorithm):
    pass

# Default algorithms for testing
default_algorithms: Dict[str, Type[BaseAlgorithm]] = {
    "algoA": DummyAlgorithmA,
    "algoB": DummyAlgorithmB,
}
from titiler.core.algorithm.__init__ import Algorithms

# Helper function to get a fresh Algorithms instance
def get_algorithms():
    return Algorithms(copy(default_algorithms))

# ------------------- UNIT TESTS -------------------

# 1. Basic Test Cases

def test_register_new_algorithm():
    # Register a new algorithm not in default_algorithms
    algos = get_algorithms()
    new_algo = {"algoC": DummyAlgorithmC}
    codeflash_output = algos.register(new_algo); result = codeflash_output # 1.41μs -> 1.60μs (12.1% slower)

def test_register_multiple_new_algorithms():
    # Register multiple new algorithms at once
    algos = get_algorithms()
    new_algos = {"algoC": DummyAlgorithmC, "algoD": DummyAlgorithmA}
    codeflash_output = algos.register(new_algos); result = codeflash_output # 1.57μs -> 1.62μs (2.72% slower)

def test_register_with_overwrite_true():
    # Overwrite an existing algorithm
    algos = get_algorithms()
    new_algo = {"algoA": DummyAlgorithmC}
    codeflash_output = algos.register(new_algo, overwrite=True); result = codeflash_output # 1.76μs -> 1.35μs (30.8% faster)

def test_register_with_overwrite_false_raises():
    # Attempt to register an existing algorithm without overwrite
    algos = get_algorithms()
    new_algo = {"algoA": DummyAlgorithmC}
    with pytest.raises(Exception) as excinfo:
        algos.register(new_algo) # 1.57μs -> 2.01μs (21.9% slower)

def test_register_empty_dict():
    # Register with empty dict should return unchanged instance
    algos = get_algorithms()
    codeflash_output = algos.register({}); result = codeflash_output # 1.25μs -> 1.56μs (20.2% slower)

# 2. Edge Test Cases

def test_register_algorithm_with_empty_string_name():
    # Edge case: algorithm name is empty string
    algos = get_algorithms()
    new_algo = {"": DummyAlgorithmC}
    codeflash_output = algos.register(new_algo); result = codeflash_output # 1.41μs -> 1.62μs (13.0% slower)

def test_register_algorithm_with_non_string_key():
    # Edge case: algorithm name is not a string
    algos = get_algorithms()
    new_algo = {123: DummyAlgorithmC}
    codeflash_output = algos.register(new_algo); result = codeflash_output # 1.69μs -> 1.88μs (10.1% slower)

def test_register_algorithm_with_none_key():
    # Edge case: algorithm name is None
    algos = get_algorithms()
    new_algo = {None: DummyAlgorithmC}
    codeflash_output = algos.register(new_algo); result = codeflash_output # 1.64μs -> 1.81μs (9.18% slower)

def test_register_algorithm_with_none_value():
    # Edge case: algorithm value is None (should allow, since type checking is not enforced)
    algos = get_algorithms()
    new_algo = {"algoC": None}
    codeflash_output = algos.register(new_algo); result = codeflash_output # 1.42μs -> 1.61μs (12.0% slower)

def test_register_algorithm_with_duplicate_keys():
    # Edge case: duplicate keys in input dict (Python dict can't have duplicate keys, so last wins)
    algos = get_algorithms()
    new_algo = {"algoC": DummyAlgorithmA, "algoC": DummyAlgorithmB}
    codeflash_output = algos.register(new_algo); result = codeflash_output # 1.34μs -> 1.55μs (13.5% slower)

def test_register_algorithm_with_non_class_value():
    # Edge case: value is not a class (should allow, since type checking is not enforced)
    algos = get_algorithms()
    new_algo = {"algoC": 42}
    codeflash_output = algos.register(new_algo); result = codeflash_output # 1.36μs -> 1.50μs (9.45% slower)

def test_register_algorithm_with_overwrite_false_and_multiple_conflicts():
    # Multiple keys, some conflicting, some not
    algos = get_algorithms()
    new_algos = {"algoA": DummyAlgorithmC, "algoC": DummyAlgorithmB}
    with pytest.raises(Exception) as excinfo:
        algos.register(new_algos) # 1.58μs -> 1.96μs (19.4% slower)
    # Should not register any algorithms if one fails

def test_register_algorithm_with_overwrite_true_and_multiple_conflicts():
    # Multiple keys, some conflicting, some not, with overwrite=True
    algos = get_algorithms()
    new_algos = {"algoA": DummyAlgorithmC, "algoC": DummyAlgorithmB}
    codeflash_output = algos.register(new_algos, overwrite=True); result = codeflash_output # 1.97μs -> 1.37μs (43.7% faster)

def test_register_algorithm_with_case_sensitive_names():
    # Register algorithms with names differing only by case
    algos = get_algorithms()
    new_algos = {"AlgoA": DummyAlgorithmC}
    codeflash_output = algos.register(new_algos); result = codeflash_output # 1.39μs -> 1.61μs (13.8% slower)

def test_register_algorithm_with_special_characters_in_name():
    # Register algorithm with special characters in name
    algos = get_algorithms()
    new_algos = {"@special!": DummyAlgorithmC}
    codeflash_output = algos.register(new_algos); result = codeflash_output # 1.40μs -> 1.53μs (8.30% slower)

# 3. Large Scale Test Cases

def test_register_many_algorithms():
    # Register a large number of algorithms (up to 1000)
    algos = get_algorithms()
    many_algos = {f"algo_{i}": DummyAlgorithmA for i in range(1000)}
    codeflash_output = algos.register(many_algos); result = codeflash_output # 56.7μs -> 19.6μs (189% faster)
    for i in range(1000):
        pass

def test_register_many_algorithms_with_conflicts_and_overwrite_false():
    # Register many algorithms, some conflicting, without overwrite
    algos = get_algorithms()
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.

To edit these changes git checkout codeflash/optimize-Algorithms.register-mihb7ofk and push.

Codeflash Static Badge

The optimization replaces an O(n) loop-based duplicate checking approach with O(1) set intersection operations, achieving a **92% speedup**.

**Key optimization:** Instead of checking `name in self.data` for each algorithm individually (which requires n dictionary lookups), the optimized code uses `self.data.keys() & algorithms.keys()` to find all overlapping keys in a single set intersection operation.

**Why this is faster:**
- **Original approach:** For each of the n algorithms being registered, performs a dictionary membership test (`name in self.data`), resulting in O(n) operations
- **Optimized approach:** Uses set intersection (`&`) which is implemented in C and operates on hash tables, finding all duplicates in effectively O(1) average-case time for typical workloads

**Performance impact by test case:**
- **Small registrations (1-10 algorithms):** Slight overhead (~10-20% slower) due to set operation setup cost
- **Large registrations (500+ algorithms):** Massive gains (169-189% faster) where the O(n) → O(1) optimization really pays off
- **Overwrite scenarios:** Significant improvements (30-44% faster) since duplicate checking is bypassed entirely when `overwrite=True`

The line profiler confirms this: the original code spent 91.9% of time in the loop checking duplicates (`for name, _algo` + `if name in self.data`), while the optimized version spends only 9.1% checking the overwrite condition and 17.6% doing the set intersection.

**Behavior preservation:** The optimization maintains identical exception messages and error-on-first-duplicate behavior, making it a drop-in performance improvement.
@codeflash-ai
codeflash-ai Bot requested a review from mashraf-222 November 27, 2025 10:47
@codeflash-ai codeflash-ai Bot added ⚡️ codeflash Optimization PR opened by Codeflash AI 🎯 Quality: High Optimization Quality according to Codeflash labels Nov 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⚡️ codeflash Optimization PR opened by Codeflash AI 🎯 Quality: High Optimization Quality according to Codeflash

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants