Skip to content

⚡️ Speed up function __getattr__ by 22% - #1

Open
codeflash-ai[bot] wants to merge 1 commit into
mainfrom
codeflash/optimize-__getattr__-mgqmvh7h
Open

⚡️ Speed up function __getattr__ by 22%#1
codeflash-ai[bot] wants to merge 1 commit into
mainfrom
codeflash/optimize-__getattr__-mgqmvh7h

Conversation

@codeflash-ai

@codeflash-ai codeflash-ai Bot commented Oct 14, 2025

Copy link
Copy Markdown

📄 22% (0.22x) speedup for __getattr__ in src/spdl/io/utils/__init__.py

⏱️ Runtime : 16.8 microseconds 13.7 microseconds (best of 65 runs)

📝 Explanation and details

The optimized code introduces a lazy-initialized cache that dramatically reduces redundant work in module attribute lookups.

Key Optimization: Instead of iterating through _mods and checking each module's __all__ list on every __getattr__ call, the optimization builds a one-time mapping (__mod_attr_map) that directly maps attribute names to their containing modules.

Specific Changes:

  • Cache initialization: On first access, builds a dictionary mapping all attributes to their respective modules
  • Cache storage: Stores the mapping as a function attribute using setattr(__getattr__, "__mod_attr_map", mapping)
  • Direct lookup: Subsequent calls use O(1) dictionary lookup instead of O(n*m) nested iteration

Performance Analysis:

  • Original: 7,471 loop iterations + 5,604 __all__ membership checks per call
  • Optimized: Just 1,868 dictionary lookups after initial cache build
  • Cache build cost: Only 4 module iterations + 14 attribute iterations (one-time overhead)

Test Case Performance:
The optimization is particularly effective for:

  • Repeated lookups (21-31% faster across all test cases)
  • Non-existent attributes (22-31% speedup) - avoids full module traversal
  • Large-scale scenarios (30.7% improvement with 1000+ attributes)

The cache pays for itself immediately since most real-world usage involves multiple attribute lookups, converting expensive linear searches into constant-time operations.

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 🔘 None Found
🌀 Generated Regression Tests 14 Passed
⏪ Replay Tests 🔘 None Found
🔎 Concolic Coverage Tests 2 Passed
📊 Tests Coverage 80.0%
🌀 Generated Regression Tests and Runtime
import pytest
from spdl.io.utils.__init__ import __getattr__

# --- Begin: Dummy module definitions for testing purposes ---

class DummyModule:
    """A simple dummy module to simulate _build, _ffmpeg, _tracing."""
    def __init__(self, name, attrs):
        self.__name__ = name
        self.__all__ = list(attrs.keys())
        for k, v in attrs.items():
            setattr(self, k, v)

# Create dummy modules with diverse attributes
_build = DummyModule("_build", {
    "alpha": 1,
    "beta": lambda x: x + 1,
    "gamma": "build",
})
_ffmpeg = DummyModule("_ffmpeg", {
    "delta": 42,
    "epsilon": [1, 2, 3],
    "zeta": {"a": 10},
})
_tracing = DummyModule("_tracing", {
    "eta": None,
    "theta": (1, 2),
    "iota": 3.14,
})

# --- End: Dummy module definitions ---

# --- Begin: Function under test ---

_mods = [
    _build,
    _ffmpeg,
    _tracing,
]

__all__ = sorted(item for mod in _mods for item in mod.__all__)

def __dir__() -> list[str]:
    return __all__
from spdl.io.utils.__init__ import __getattr__

# --- End: Function under test ---

# --- Begin: Unit tests ---

# Basic Test Cases





def test_edge_nonexistent_attribute():
    """Test that non-existent attributes raise AttributeError."""
    with pytest.raises(AttributeError):
        __getattr__("not_in_any_module") # 1.89μs -> 1.55μs (21.6% faster)

def test_edge_case_sensitive():
    """Test that attribute lookup is case-sensitive."""
    with pytest.raises(AttributeError):
        __getattr__("ALPHA") # 1.65μs -> 1.35μs (22.0% faster)

def test_edge_empty_string():
    """Test that empty string as attribute raises AttributeError."""
    with pytest.raises(AttributeError):
        __getattr__("") # 1.53μs -> 1.27μs (19.9% faster)


def test_edge_dir_functionality():
    """Test that __dir__ returns all attributes sorted."""
    all_attrs = __dir__()
    # Should contain all attributes from all modules
    expected = sorted(_build.__all__ + _ffmpeg.__all__ + _tracing.__all__)


def test_edge_attribute_type_preservation():
    """Test that the type of returned attribute matches the source."""

# Large Scale Test Cases



def test_large_scale_attribute_not_found():
    """Test that lookup for a non-existent attribute in a large dataset raises AttributeError."""
    # Add 1000 attributes to _ffmpeg
    for i in range(1000):
        name = f"ffmpeg_attr_{i}"
        _ffmpeg.__all__.append(name)
        setattr(_ffmpeg, name, i)
    with pytest.raises(AttributeError):
        __getattr__("ffmpeg_attr_1000") # 2.23μs -> 1.71μs (30.7% faster)
    # Clean up
    for i in range(1000):
        name = f"ffmpeg_attr_{i}"
        _ffmpeg.__all__.remove(name)
        delattr(_ffmpeg, name)

def test_large_scale_dir_returns_all():
    """Test that __dir__ returns all attributes after large scale additions."""
    # Add 100 attributes to each module
    for i in range(100):
        _build.__all__.append(f"b_{i}")
        setattr(_build, f"b_{i}", i)
        _ffmpeg.__all__.append(f"f_{i}")
        setattr(_ffmpeg, f"f_{i}", i)
        _tracing.__all__.append(f"t_{i}")
        setattr(_tracing, f"t_{i}", i)
    all_attrs = __dir__()
    # Check that all new attributes are present
    for i in range(100):
        pass
    # Clean up
    for i in range(100):
        _build.__all__.remove(f"b_{i}")
        delattr(_build, f"b_{i}")
        _ffmpeg.__all__.remove(f"f_{i}")
        delattr(_ffmpeg, f"f_{i}")
        _tracing.__all__.remove(f"t_{i}")
        delattr(_tracing, f"t_{i}")
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.
#------------------------------------------------
import pytest
from spdl.io.utils.__init__ import __getattr__

# --- Begin: Dummy module classes to simulate _build, _ffmpeg, _tracing ---

class DummyModule:
    """A dummy module to simulate submodules with __all__ and attributes."""
    def __init__(self, name, all_names_and_values):
        # all_names_and_values: list of (name, value) tuples
        self.__all__ = [name for name, _ in all_names_and_values]
        for name, value in all_names_and_values:
            setattr(self, name, value)

# Simulate three modules with various __all__ and attributes
_build = DummyModule('_build', [
    ('foo', 123),
    ('bar', lambda: "build-bar"),
    ('shared', 'from_build'),
])

_ffmpeg = DummyModule('_ffmpeg', [
    ('baz', 456),
    ('qux', lambda: "ffmpeg-qux"),
    ('shared', 'from_ffmpeg'),
])

_tracing = DummyModule('_tracing', [
    ('trace', 789),
    ('trace_func', lambda: "tracing-func"),
    ('shared', 'from_tracing'),
])

# --- End: Dummy module classes ---

# function to test (copied from user, with dummy _mods)
_mods = [
    _build,
    _ffmpeg,
    _tracing,
]

__all__ = sorted(item for mod in _mods for item in mod.__all__)

def __dir__() -> list[str]:
    return __all__
from spdl.io.utils.__init__ import __getattr__

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

# 1. Basic Test Cases



def test_basic_dir_lists_all_attributes():
    # __dir__ should return all attributes from all modules, sorted
    expected = sorted(_build.__all__ + _ffmpeg.__all__ + _tracing.__all__)

# 2. Edge Test Cases

def test_getattr_raises_for_nonexistent_attribute():
    # Should raise AttributeError for missing attribute
    with pytest.raises(AttributeError) as excinfo:
        __getattr__('not_present') # 1.90μs -> 1.55μs (22.9% faster)

def test_getattr_raises_for_empty_string():
    # Should raise AttributeError for empty string
    with pytest.raises(AttributeError) as excinfo:
        __getattr__('') # 1.70μs -> 1.46μs (16.2% faster)



def test_dir_with_duplicate_names():
    # __dir__ should include all names, including duplicates, but sorted and deduplicated
    # Our __all__ is built by flattening all __all__s, then sorted
    # But if names are duplicated, they will appear only once in __all__
    # Let's check that
    all_names = _build.__all__ + _ffmpeg.__all__ + _tracing.__all__
    expected = sorted(set(all_names))

def test_getattr_case_sensitivity():
    # Attribute names are case-sensitive
    with pytest.raises(AttributeError):
        __getattr__('Foo') # 1.70μs -> 1.52μs (11.5% faster)
    with pytest.raises(AttributeError):
        __getattr__('BAR') # 1.09μs -> 1.00μs (8.90% faster)




def test_large_dir_performance_and_completeness():
    # 20 modules, each with 40 unique attributes (total 800)
    class PerfDummyModule:
        def __init__(self, idx):
            self.__all__ = [f'perf_{idx}_{i}' for i in range(40)]
            for i in range(40):
                setattr(self, f'perf_{idx}_{i}', True)
    perf_mods = [PerfDummyModule(i) for i in range(20)]
    old_mods = list(_mods)
    old_all = list(__all__)
    globals()['_mods'] = perf_mods
    globals()['__all__'] = sorted(item for mod in _mods for item in mod.__all__)
    # __dir__ should return all 800 names, sorted and deduplicated
    expected = sorted(set(name for mod in perf_mods for name in mod.__all__))
    dir_list = __dir__()
    # Restore
    globals()['_mods'] = old_mods
    globals()['__all__'] = old_all
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.
#------------------------------------------------
from spdl.io.utils.__init__ import __getattr__
from spdl.io.utils._tracing import trace_counter
import pytest

def test___getattr__():
    with pytest.raises(AttributeError, match="module\\ 'spdl\\.io\\.utils\\.__init__'\\ has\\ no\\ attribute\\ ''"):
        __getattr__('')

def test___getattr___2():
    __getattr__('trace_counter')
🔎 Concolic Coverage Tests and Runtime
Test File::Test Function Original ⏱️ Optimized ⏱️ Speedup
codeflash_concolic_xpyvdxks/tmpvkfyfbz2/test_concolic_coverage.py::test___getattr__ 2.01μs 1.55μs 29.9%✅
codeflash_concolic_xpyvdxks/tmpvkfyfbz2/test_concolic_coverage.py::test___getattr___2 1.09μs 740ns 46.9%✅

To edit these changes git checkout codeflash/optimize-__getattr__-mgqmvh7h and push.

Codeflash

The optimized code introduces a **lazy-initialized cache** that dramatically reduces redundant work in module attribute lookups.

**Key Optimization:** Instead of iterating through `_mods` and checking each module's `__all__` list on every `__getattr__` call, the optimization builds a one-time mapping (`__mod_attr_map`) that directly maps attribute names to their containing modules.

**Specific Changes:**
- **Cache initialization**: On first access, builds a dictionary mapping all attributes to their respective modules
- **Cache storage**: Stores the mapping as a function attribute using `setattr(__getattr__, "__mod_attr_map", mapping)`
- **Direct lookup**: Subsequent calls use `O(1)` dictionary lookup instead of `O(n*m)` nested iteration

**Performance Analysis:**
- **Original**: 7,471 loop iterations + 5,604 `__all__` membership checks per call
- **Optimized**: Just 1,868 dictionary lookups after initial cache build
- **Cache build cost**: Only 4 module iterations + 14 attribute iterations (one-time overhead)

**Test Case Performance:**
The optimization is particularly effective for:
- **Repeated lookups** (21-31% faster across all test cases)
- **Non-existent attributes** (22-31% speedup) - avoids full module traversal
- **Large-scale scenarios** (30.7% improvement with 1000+ attributes)

The cache pays for itself immediately since most real-world usage involves multiple attribute lookups, converting expensive linear searches into constant-time operations.
@codeflash-ai
codeflash-ai Bot requested a review from mashraf-222 October 14, 2025 14:04
@codeflash-ai codeflash-ai Bot added the ⚡️ codeflash Optimization PR opened by Codeflash AI label Oct 14, 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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants