Skip to content

Conversation

@codeflash-ai
Copy link

@codeflash-ai codeflash-ai bot commented Dec 9, 2025

📄 15% (0.15x) speedup for _make_getset_interval in lib/matplotlib/axis.py

⏱️ Runtime : 53.6 microseconds 46.5 microseconds (best of 135 runs)

📝 Explanation and details

The optimized version achieves a 15% speedup through three key optimizations that reduce redundant attribute lookups and function call overhead:

1. Eliminated redundant attribute chains: The original code performed getattr(getattr(self.axes, lim_name), attr_name) which requires two separate attribute lookups. The optimized version caches the intermediate lim object with lim = getattr(self.axes, lim_name_str), then uses getattr(lim, attr_name_str), reducing the attribute traversal overhead.

2. Removed recursive function calls: The original setter used recursive calls to itself (setter(self, min(...), max(...), ignore=True)), which incurs function call overhead and stack frame creation. The optimized version directly calls setattr(lim, attr_name_str, (...)) with the computed tuple, eliminating this overhead entirely.

3. Pre-computed string references: While the original explanation mentioned this optimization, the actual performance benefit comes from the reduced attribute chain traversals rather than the string variable assignments themselves.

Performance characteristics: The test results show consistent 12-22% improvements across all scenarios, with the largest gains (17-22%) occurring in complex setter operations involving the ignore=False path where the recursive call elimination has the most impact. Even simple operations like ignore=True paths see 15-19% improvements due to the reduced attribute lookups.

Impact on workloads: Since this is a factory function that creates getter/setter pairs for matplotlib axis intervals, the optimization benefits any code that frequently reads or updates plot limits - a common operation in data visualization workflows. The elimination of recursive calls also improves stack efficiency for applications that perform many rapid limit adjustments.

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 🔘 None Found
🌀 Generated Regression Tests 35 Passed
⏪ Replay Tests 🔘 None Found
🔎 Concolic Coverage Tests 🔘 None Found
📊 Tests Coverage 100.0%
🌀 Generated Regression Tests and Runtime
from matplotlib.axis import _make_getset_interval

# --- Helper classes for testing ---


class DummyLim:
    """Dummy class to simulate a limit object with a tuple attribute."""

    def __init__(self, interval=(0, 1)):
        self.interval = interval


class DummyAxes:
    """Dummy class to simulate an axes object with lim attributes."""

    def __init__(self, lim_name, interval=(0, 1)):
        setattr(self, lim_name, DummyLim(interval))
        self.stale = False


class DummyObj:
    """Dummy object with axes and stale attributes."""

    def __init__(self, lim_name, interval=(0, 1)):
        self.axes = DummyAxes(lim_name, interval)
        self.stale = False


# --- Basic Test Cases ---


def test_getter_returns_initial_interval():
    """Test that getter returns the initial interval correctly."""
    getter, _ = _make_getset_interval(
        "data", "xlim", "interval"
    )  # 1.54μs -> 1.33μs (15.8% faster)
    obj = DummyObj("xlim", (2, 5))


def test_setter_sets_interval_ignore_true():
    """Test that setter sets the interval directly when ignore=True."""
    _, setter = _make_getset_interval(
        "data", "xlim", "interval"
    )  # 1.56μs -> 1.31μs (19.0% faster)
    obj = DummyObj("xlim", (0, 1))
    setter(obj, 10, 20, ignore=True)


def test_setter_sets_interval_ignore_false_increasing():
    """Test setter with ignore=False and oldmin < oldmax."""
    _, setter = _make_getset_interval(
        "data", "xlim", "interval"
    )  # 1.54μs -> 1.32μs (16.3% faster)
    obj = DummyObj("xlim", (2, 8))
    setter(obj, 1, 10, ignore=False)


def test_setter_sets_interval_ignore_false_decreasing():
    """Test setter with ignore=False and oldmin >= oldmax (reverse order)."""
    _, setter = _make_getset_interval(
        "data", "xlim", "interval"
    )  # 1.53μs -> 1.30μs (17.5% faster)
    obj = DummyObj("xlim", (8, 2))
    setter(obj, 1, 10, ignore=False)


def test_setter_sets_interval_when_vmin_gt_vmax():
    """Test setter with vmin > vmax, should still set correctly."""
    _, setter = _make_getset_interval(
        "data", "xlim", "interval"
    )  # 1.53μs -> 1.29μs (18.1% faster)
    obj = DummyObj("xlim", (2, 8))
    setter(obj, 10, 1, ignore=False)


def test_setter_sets_interval_when_vmin_eq_vmax():
    """Test setter with vmin == vmax."""
    _, setter = _make_getset_interval(
        "data", "xlim", "interval"
    )  # 1.50μs -> 1.34μs (12.1% faster)
    obj = DummyObj("xlim", (2, 8))
    setter(obj, 5, 5, ignore=False)


# --- Edge Test Cases ---


def test_setter_with_negative_intervals():
    """Test setter with negative values."""
    _, setter = _make_getset_interval(
        "data", "xlim", "interval"
    )  # 1.51μs -> 1.30μs (16.4% faster)
    obj = DummyObj("xlim", (-5, -1))
    setter(obj, -10, 0, ignore=False)


def test_setter_with_zero_interval():
    """Test setter with interval (0, 0)."""
    _, setter = _make_getset_interval(
        "data", "xlim", "interval"
    )  # 1.52μs -> 1.31μs (16.8% faster)
    obj = DummyObj("xlim", (0, 0))
    setter(obj, 0, 0, ignore=False)


def test_setter_with_large_and_small_numbers():
    """Test setter with very large and very small numbers."""
    _, setter = _make_getset_interval(
        "data", "xlim", "interval"
    )  # 1.54μs -> 1.30μs (17.9% faster)
    obj = DummyObj("xlim", (1e-10, 1e10))
    setter(obj, -1e20, 1e20, ignore=False)


def test_stale_flag_is_set():
    """Test that stale flag is set to True after setter is called."""
    _, setter = _make_getset_interval(
        "data", "xlim", "interval"
    )  # 1.54μs -> 1.47μs (5.04% faster)
    obj = DummyObj("xlim", (0, 1))
    obj.stale = False
    setter(obj, 10, 20, ignore=True)


def test_setter_and_getter_different_lim_name():
    """Test function works with different lim_name and attr_name."""
    getter, setter = _make_getset_interval(
        "view", "ylim", "interval"
    )  # 1.48μs -> 1.30μs (14.3% faster)
    obj = DummyObj("ylim", (3, 7))
    setter(obj, 1, 10, ignore=True)


# --- Large Scale Test Cases ---


def test_setter_large_number_of_calls():
    """Test setter's stability and performance with many sequential calls."""
    _, setter = _make_getset_interval(
        "data", "xlim", "interval"
    )  # 1.50μs -> 1.35μs (11.1% faster)
    obj = DummyObj("xlim", (0, 1))
    # Sequentially set intervals and check correctness
    for i in range(100):
        setter(obj, i, i + 1, ignore=True)


def test_setter_with_large_range_values():
    """Test setter with a very wide interval range."""
    _, setter = _make_getset_interval(
        "data", "xlim", "interval"
    )  # 1.54μs -> 1.29μs (19.5% faster)
    obj = DummyObj("xlim", (0, 1000))
    setter(obj, -500, 2000, ignore=False)


def test_setter_and_getter_on_multiple_objects():
    """Test getter and setter on many objects to ensure no cross-talk."""
    getter, setter = _make_getset_interval(
        "data", "xlim", "interval"
    )  # 1.56μs -> 1.35μs (15.8% faster)
    objs = [DummyObj("xlim", (i, i + 1)) for i in range(100)]
    for i, obj in enumerate(objs):
        setter(obj, i + 10, i + 20, ignore=True)
    # Ensure each object is independent
    for i, obj in enumerate(objs):
        pass


def test_setter_getter_with_randomized_intervals():
    """Test with a variety of random intervals for robustness."""
    import random

    getter, setter = _make_getset_interval(
        "data", "xlim", "interval"
    )  # 1.60μs -> 1.41μs (13.9% faster)
    objs = [
        DummyObj("xlim", (random.randint(-100, 100), random.randint(-100, 100)))
        for _ in range(100)
    ]
    for obj in objs:
        a, b = random.randint(-1000, 1000), random.randint(-1000, 1000)
        setter(obj, a, b, ignore=True)


# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.
from matplotlib.axis import _make_getset_interval


# Helper classes for testing
class DummyLim:
    """Dummy object to simulate a lim object with an interval attribute."""

    def __init__(self, interval):
        self.interval = interval


class DummyAxes:
    """Dummy axes object that contains a lim object."""

    def __init__(self, lim):
        self._lim = lim

    def __getattr__(self, item):
        if item == "lim":
            return self._lim
        raise AttributeError(item)


class DummyObj:
    """Dummy object that mimics the structure expected by the getter/setter."""

    def __init__(self, axes, stale=False):
        self.axes = axes
        self.stale = stale


# Basic Test Cases


def test_getter_basic():
    """Test that the getter returns the correct interval."""
    getter, setter = _make_getset_interval(
        "data", "lim", "interval"
    )  # 1.57μs -> 1.46μs (7.25% faster)
    lim = DummyLim((1, 2))
    axes = DummyAxes(lim)
    obj = DummyObj(axes)


def test_setter_basic_ignore_false_increasing():
    """Test setter with ignore=False and oldmin < oldmax (increasing interval)."""
    getter, setter = _make_getset_interval(
        "data", "lim", "interval"
    )  # 1.54μs -> 1.33μs (16.1% faster)
    lim = DummyLim((2, 8))
    axes = DummyAxes(lim)
    obj = DummyObj(axes)
    # Should set interval to the min/max of all values
    setter(obj, 3, 10, ignore=False)


def test_setter_basic_ignore_false_decreasing():
    """Test setter with ignore=False and oldmin > oldmax (decreasing interval)."""
    getter, setter = _make_getset_interval(
        "data", "lim", "interval"
    )  # 1.51μs -> 1.34μs (12.9% faster)
    lim = DummyLim((8, 2))
    axes = DummyAxes(lim)
    obj = DummyObj(axes)
    # Should set interval to max/min of all values
    setter(obj, 3, 10, ignore=False)


def test_setter_basic_ignore_true():
    """Test setter with ignore=True sets the interval directly."""
    getter, setter = _make_getset_interval(
        "data", "lim", "interval"
    )  # 1.51μs -> 1.31μs (15.5% faster)
    lim = DummyLim((5, 6))
    axes = DummyAxes(lim)
    obj = DummyObj(axes)
    setter(obj, 1, 9, ignore=True)


def test_setter_sets_stale():
    """Test that setter always sets self.stale to True."""
    getter, setter = _make_getset_interval(
        "data", "lim", "interval"
    )  # 1.57μs -> 1.34μs (16.7% faster)
    lim = DummyLim((0, 1))
    axes = DummyAxes(lim)
    obj = DummyObj(axes)
    obj.stale = False
    setter(obj, -1, 2, ignore=True)


# Edge Test Cases


def test_setter_ignore_false_equal_values():
    """Test setter with vmin == vmax == oldmin == oldmax."""
    getter, setter = _make_getset_interval(
        "data", "lim", "interval"
    )  # 1.48μs -> 1.31μs (12.3% faster)
    lim = DummyLim((5, 5))
    axes = DummyAxes(lim)
    obj = DummyObj(axes)
    setter(obj, 5, 5, ignore=False)


def test_setter_ignore_false_vmin_gt_vmax():
    """Test setter with vmin > vmax (should still set correctly)."""
    getter, setter = _make_getset_interval(
        "data", "lim", "interval"
    )  # 1.54μs -> 1.31μs (17.0% faster)
    lim = DummyLim((1, 10))
    axes = DummyAxes(lim)
    obj = DummyObj(axes)
    setter(obj, 15, 5, ignore=False)


def test_setter_ignore_true_vmin_gt_vmax():
    """Test setter with ignore=True and vmin > vmax (should set as is)."""
    getter, setter = _make_getset_interval(
        "data", "lim", "interval"
    )  # 1.52μs -> 1.29μs (17.9% faster)
    lim = DummyLim((0, 0))
    axes = DummyAxes(lim)
    obj = DummyObj(axes)
    setter(obj, 20, 10, ignore=True)


def test_setter_negative_intervals():
    """Test setter with negative values."""
    getter, setter = _make_getset_interval(
        "data", "lim", "interval"
    )  # 1.51μs -> 1.32μs (14.4% faster)
    lim = DummyLim((-5, -1))
    axes = DummyAxes(lim)
    obj = DummyObj(axes)
    setter(obj, -10, -2, ignore=False)


def test_setter_zero_interval():
    """Test setter with zero values."""
    getter, setter = _make_getset_interval(
        "data", "lim", "interval"
    )  # 1.58μs -> 1.33μs (18.6% faster)
    lim = DummyLim((0, 0))
    axes = DummyAxes(lim)
    obj = DummyObj(axes)
    setter(obj, 0, 0, ignore=False)


def test_setter_large_numbers():
    """Test setter with very large numbers."""
    getter, setter = _make_getset_interval(
        "data", "lim", "interval"
    )  # 1.48μs -> 1.26μs (17.2% faster)
    lim = DummyLim((1e10, 1e12))
    axes = DummyAxes(lim)
    obj = DummyObj(axes)
    setter(obj, 1e11, 1e13, ignore=False)


def test_setter_small_numbers():
    """Test setter with very small numbers."""
    getter, setter = _make_getset_interval(
        "data", "lim", "interval"
    )  # 1.49μs -> 1.29μs (15.4% faster)
    lim = DummyLim((1e-10, 1e-8))
    axes = DummyAxes(lim)
    obj = DummyObj(axes)
    setter(obj, 1e-9, 1e-7, ignore=False)


def test_setter_with_nan():
    """Test setter with NaN values (should propagate NaN)."""
    getter, setter = _make_getset_interval(
        "data", "lim", "interval"
    )  # 1.49μs -> 1.35μs (10.4% faster)
    lim = DummyLim((1, 2))
    axes = DummyAxes(lim)
    obj = DummyObj(axes)
    setter(obj, float("nan"), 2, ignore=True)


def test_setter_with_inf():
    """Test setter with inf values (should handle inf)."""
    getter, setter = _make_getset_interval(
        "data", "lim", "interval"
    )  # 1.54μs -> 1.34μs (14.5% faster)
    lim = DummyLim((1, 2))
    axes = DummyAxes(lim)
    obj = DummyObj(axes)
    setter(obj, float("inf"), float("-inf"), ignore=True)


# Large Scale Test Cases


def test_setter_large_scale_increasing():
    """Test setter performance and correctness with many increasing values."""
    getter, setter = _make_getset_interval(
        "data", "lim", "interval"
    )  # 1.54μs -> 1.30μs (18.2% faster)
    # Simulate a large interval
    lim = DummyLim((0, 999))
    axes = DummyAxes(lim)
    obj = DummyObj(axes)
    # Set with a large range
    setter(obj, 100, 998, ignore=False)


def test_setter_large_scale_decreasing():
    """Test setter with a large decreasing interval."""
    getter, setter = _make_getset_interval(
        "data", "lim", "interval"
    )  # 1.48μs -> 1.32μs (12.4% faster)
    lim = DummyLim((999, 0))
    axes = DummyAxes(lim)
    obj = DummyObj(axes)
    setter(obj, 100, 998, ignore=False)


def test_multiple_setter_calls():
    """Test multiple setter calls in sequence."""
    getter, setter = _make_getset_interval(
        "data", "lim", "interval"
    )  # 1.55μs -> 1.36μs (14.3% faster)
    lim = DummyLim((10, 20))
    axes = DummyAxes(lim)
    obj = DummyObj(axes)
    for i in range(100):
        setter(obj, 10 - i, 20 + i, ignore=False)


def test_large_scale_ignore_true():
    """Test setting many times with ignore=True."""
    getter, setter = _make_getset_interval(
        "data", "lim", "interval"
    )  # 1.52μs -> 1.36μs (11.7% faster)
    lim = DummyLim((0, 0))
    axes = DummyAxes(lim)
    obj = DummyObj(axes)
    for i in range(1000):
        setter(obj, i, -i, ignore=True)


def test_getter_setter_name():
    """Test that the getter and setter have correct names."""
    getter, setter = _make_getset_interval(
        "foo", "lim", "interval"
    )  # 1.63μs -> 1.34μs (21.9% faster)


# 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-_make_getset_interval-miyi8jmb and push.

Codeflash Static Badge

The optimized version achieves a 15% speedup through three key optimizations that reduce redundant attribute lookups and function call overhead:

**1. Eliminated redundant attribute chains:** The original code performed `getattr(getattr(self.axes, lim_name), attr_name)` which requires two separate attribute lookups. The optimized version caches the intermediate `lim` object with `lim = getattr(self.axes, lim_name_str)`, then uses `getattr(lim, attr_name_str)`, reducing the attribute traversal overhead.

**2. Removed recursive function calls:** The original setter used recursive calls to itself (`setter(self, min(...), max(...), ignore=True)`), which incurs function call overhead and stack frame creation. The optimized version directly calls `setattr(lim, attr_name_str, (...))` with the computed tuple, eliminating this overhead entirely.

**3. Pre-computed string references:** While the original explanation mentioned this optimization, the actual performance benefit comes from the reduced attribute chain traversals rather than the string variable assignments themselves.

**Performance characteristics:** The test results show consistent 12-22% improvements across all scenarios, with the largest gains (17-22%) occurring in complex setter operations involving the `ignore=False` path where the recursive call elimination has the most impact. Even simple operations like `ignore=True` paths see 15-19% improvements due to the reduced attribute lookups.

**Impact on workloads:** Since this is a factory function that creates getter/setter pairs for matplotlib axis intervals, the optimization benefits any code that frequently reads or updates plot limits - a common operation in data visualization workflows. The elimination of recursive calls also improves stack efficiency for applications that perform many rapid limit adjustments.
@codeflash-ai codeflash-ai bot requested a review from mashraf-222 December 9, 2025 11:36
@codeflash-ai codeflash-ai bot added ⚡️ codeflash Optimization PR opened by Codeflash AI 🎯 Quality: High Optimization Quality according to Codeflash labels Dec 9, 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.

1 participant