Skip to content

⚡️ Speed up method PropertyValueList.__iadd__ by 16%#163

Open
codeflash-ai[bot] wants to merge 1 commit into
branch-3.9from
codeflash/optimize-PropertyValueList.__iadd__-mhwy5bj3
Open

⚡️ Speed up method PropertyValueList.__iadd__ by 16%#163
codeflash-ai[bot] wants to merge 1 commit into
branch-3.9from
codeflash/optimize-PropertyValueList.__iadd__-mhwy5bj3

Conversation

@codeflash-ai
Copy link
Copy Markdown

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

📄 16% (0.16x) speedup for PropertyValueList.__iadd__ in src/bokeh/core/property/wrappers.py

⏱️ Runtime : 1.98 microsecondss 1.72 microsecondss (best of 250 runs)

📝 Explanation and details

The optimization achieves a 15% speedup by eliminating Python's super() method resolution overhead through two key changes:

1. Direct parent class calls instead of super():

  • In __init__(): Replaced super().__init__(*args, **kwargs) with explicit calls to list.__init__(self, *args, **kwargs) and PropertyValueContainer.__init__(self)
  • In __iadd__(): Replaced super().__iadd__(y) with list.__iadd__(self, y)

2. Added __slots__ = () to prevent __dict__ creation:

  • Saves memory overhead for instances that don't need additional attributes
  • Provides slight performance improvement for attribute access

Why this is faster:
The super() function in Python performs Method Resolution Order (MRO) lookup at runtime, which involves traversing the inheritance hierarchy to find the correct method. For simple multiple inheritance like PropertyValueList(PropertyValueContainer, list[T]), this adds unnecessary overhead when we know exactly which parent method to call. Direct method calls bypass this resolution entirely.

Performance context:
Based on the test results, the optimization is most effective for basic list operations like test_iadd_inplace_identity() which showed the 15.6% improvement. The speedup is particularly valuable since __iadd__ is a fundamental list operation that could be called frequently in data manipulation workloads.

Memory benefit:
The __slots__ = () declaration prevents Python from creating a __dict__ for each instance, reducing memory footprint when many PropertyValueList instances are created, which is common in property-heavy frameworks like Bokeh.

Correctness verification report:

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

# imports
import pytest
from bokeh.core.property.wrappers import PropertyValueList

T = TypeVar("T")

# Dummy decorator for notify_owner, since actual implementation is not provided
def notify_owner(func):
    return func

class PropertyValueContainer:
    _owners: set

    def __init__(self, *args, **kwargs) -> None:
        self._owners = set()
        super().__init__(*args, **kwargs)
from bokeh.core.property.wrappers import PropertyValueList

# unit tests

# ------------------------------
# 1. Basic Test Cases
# ------------------------------

def test_iadd_basic_list():
    # Adding a list to a PropertyValueList should append elements
    pv = PropertyValueList([1, 2, 3])
    pv += [4, 5]

def test_iadd_basic_tuple():
    # Adding a tuple should append elements
    pv = PropertyValueList([1, 2])
    pv += (3, 4)

def test_iadd_basic_empty():
    # Adding an empty list should not change the list
    pv = PropertyValueList([1, 2])
    pv += []

def test_iadd_basic_empty_self():
    # Adding to an empty PropertyValueList should work
    pv = PropertyValueList([])
    pv += [1, 2]

def test_iadd_basic_multiple_types():
    # Adding mixed types should work like list
    pv = PropertyValueList([1, "a"])
    pv += ["b", 3.14]

# ------------------------------
# 2. Edge Test Cases
# ------------------------------

def test_iadd_self():
    # Adding the list to itself should duplicate its contents
    pv = PropertyValueList([1, 2])
    pv += pv

def test_iadd_iterable():
    # Adding a generator should append its values
    pv = PropertyValueList([1])
    pv += (x for x in [2, 3])

def test_iadd_string():
    # Adding a string should append its characters (like list)
    pv = PropertyValueList(['a'])
    pv += "bc"

def test_iadd_non_iterable_raises():
    # Adding a non-iterable should raise TypeError
    pv = PropertyValueList([1])
    with pytest.raises(TypeError):
        pv += 5  # int is not iterable

def test_iadd_none_raises():
    # Adding None should raise TypeError
    pv = PropertyValueList([1])
    with pytest.raises(TypeError):
        pv += None

def test_iadd_nested_lists():
    # Adding a list of lists should append each inner list as element
    pv = PropertyValueList([[1]])
    pv += [[2], [3]]

def test_iadd_bool():
    # Adding a bool should raise TypeError (not iterable)
    pv = PropertyValueList([1])
    with pytest.raises(TypeError):
        pv += True

def test_iadd_dict():
    # Adding a dict should append its keys
    pv = PropertyValueList([0])
    pv += {'a': 1, 'b': 2}

def test_iadd_bytes():
    # Adding bytes should append its ints
    pv = PropertyValueList([])
    pv += b'AB'

def test_iadd_memoryview():
    # Adding a memoryview should append its ints
    pv = PropertyValueList([])
    pv += memoryview(b'AB')

def test_iadd_custom_iterable():
    # Adding a custom iterable should append its yielded elements
    class MyIter:
        def __iter__(self):
            yield 1
            yield 2
    pv = PropertyValueList([0])
    pv += MyIter()

def test_iadd_object_with_len_but_not_iterable():
    # Adding an object with __len__ but not __iter__ should raise TypeError
    class Weird:
        def __len__(self): return 1
    pv = PropertyValueList([0])
    with pytest.raises(TypeError):
        pv += Weird()

def test_iadd_object_with_iter_but_not_iterable():
    # Adding an object with __iter__ returning non-iterator should raise TypeError
    class BadIter:
        def __iter__(self): return 42
    pv = PropertyValueList([0])
    with pytest.raises(TypeError):
        pv += BadIter()

def test_iadd_object_with_getitem():
    # Adding an object with __getitem__ should work if it's iterable
    class Seq:
        def __getitem__(self, idx):
            if idx < 2: return idx
            else: raise IndexError
    pv = PropertyValueList([10])
    pv += Seq()

def test_iadd_inplace_identity():
    # __iadd__ should return self (in-place)
    pv = PropertyValueList([1, 2])
    codeflash_output = pv.__iadd__([3]); result = codeflash_output # 1.98μs -> 1.72μs (15.6% faster)

def test_iadd_mutation_side_effect():
    # __iadd__ should mutate the original list, not return a new one
    pv = PropertyValueList([1])
    pv2 = pv
    pv += [2]

def test_iadd_empty_iterable():
    # Adding an empty tuple should not change the list
    pv = PropertyValueList([1])
    pv += ()

def test_iadd_large_single_element():
    # Adding a very large integer as an iterable (should raise TypeError)
    pv = PropertyValueList([1])
    with pytest.raises(TypeError):
        pv += 10**10

# ------------------------------
# 3. Large Scale Test Cases
# ------------------------------

def test_iadd_large_list():
    # Adding a large list should work and preserve order
    pv = PropertyValueList(list(range(500)))
    pv += list(range(500, 1000))

def test_iadd_large_tuple():
    # Adding a large tuple should work
    pv = PropertyValueList(list(range(100)))
    pv += tuple(range(100, 500))

def test_iadd_large_generator():
    # Adding a large generator should work
    pv = PropertyValueList(list(range(100)))
    pv += (x for x in range(100, 1000))

def test_iadd_large_string():
    # Adding a large string should append its characters
    s = "a" * 1000
    pv = PropertyValueList([])
    pv += s

def test_iadd_large_bytes():
    # Adding large bytes should append their ints
    b = bytes(range(256))
    pv = PropertyValueList([])
    pv += b

def test_iadd_large_custom_iterable():
    # Adding a large custom iterable should work
    class LargeIter:
        def __iter__(self):
            for i in range(1000):
                yield i
    pv = PropertyValueList([])
    pv += LargeIter()

def test_iadd_large_nested_lists():
    # Adding a list of 1000 lists should append each list
    pv = PropertyValueList([])
    pv += [[i] for i in range(1000)]

def test_iadd_large_dict():
    # Adding a large dict should append its keys
    d = {str(i): i for i in range(1000)}
    pv = PropertyValueList([])
    pv += d

def test_iadd_performance():
    # Performance: ensure that adding 1000 elements does not take excessive time
    import time
    pv = PropertyValueList([])
    data = list(range(1000))
    start = time.time()
    pv += data
    end = time.time()
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.
from typing import Any, TypeVar

# imports
import pytest
from bokeh.core.property.wrappers import PropertyValueList


# function to test (as provided above, with dummy notify_owner decorator)
def notify_owner(func):
    # Dummy decorator for testing purposes (does nothing)
    def wrapper(self, *args, **kwargs):
        return func(self, *args, **kwargs)
    return wrapper

class PropertyValueContainer:
    _owners: set

    def __init__(self, *args, **kwargs) -> None:
        self._owners = set()
        super().__init__(*args, **kwargs)
from bokeh.core.property.wrappers import PropertyValueList

# unit tests

# --- Basic Test Cases ---

def test_iadd_basic_lists():
    # Test adding two lists of integers
    a = PropertyValueList([1, 2, 3])
    b = [4, 5]
    a += b

def test_iadd_basic_empty_right():
    # Test adding an empty list
    a = PropertyValueList([1, 2, 3])
    a += []

def test_iadd_basic_empty_left():
    # Test adding to an empty PropertyValueList
    a = PropertyValueList([])
    a += [1, 2, 3]

def test_iadd_basic_strings():
    # Test adding a list of strings
    a = PropertyValueList(["a", "b"])
    a += ["c", "d"]

def test_iadd_basic_tuple():
    # Test adding a tuple
    a = PropertyValueList([1, 2])
    a += (3, 4)

def test_iadd_basic_self_return():
    # __iadd__ should return self (not a new object)
    a = PropertyValueList([1, 2])
    b = a
    a += [3]

# --- Edge Test Cases ---

def test_iadd_non_iterable_raises():
    # Adding a non-iterable should raise TypeError
    a = PropertyValueList([1, 2])
    with pytest.raises(TypeError):
        a += 3  # int is not iterable

def test_iadd_string_iterable():
    # Adding a string should add each character
    a = PropertyValueList(['x'])
    a += "yz"

def test_iadd_bytes_iterable():
    # Adding bytes should add each byte as int
    a = PropertyValueList([1])
    a += b"ab"

def test_iadd_nested_lists():
    # Adding a list of lists should add the lists as elements
    a = PropertyValueList([[1], [2]])
    a += [[3], [4]]

def test_iadd_right_is_propertyvaluelist():
    # Adding another PropertyValueList
    a = PropertyValueList([1])
    b = PropertyValueList([2, 3])
    a += b

def test_iadd_right_is_generator():
    # Adding a generator
    a = PropertyValueList([1])
    a += (x for x in [2, 3])

def test_iadd_right_is_set():
    # Adding a set (order not guaranteed)
    a = PropertyValueList([1])
    a += set([2, 3])

def test_iadd_right_is_range():
    # Adding a range
    a = PropertyValueList([0])
    a += range(1, 4)

def test_iadd_right_is_dict():
    # Adding a dict adds keys
    a = PropertyValueList([0])
    a += {1: "a", 2: "b"}

def test_iadd_mutation_does_not_affect_right():
    # Mutating left should not affect right
    right = [2, 3]
    a = PropertyValueList([1])
    a += right
    right.append(4)

def test_iadd_left_and_right_are_same_object():
    # Adding self should double the list
    a = PropertyValueList([1, 2])
    a += a

def test_iadd_right_is_iterator_consumed():
    # Adding a consumed iterator should add nothing
    gen = iter([2, 3])
    list(gen)  # Exhaust generator
    a = PropertyValueList([1])
    a += gen

def test_iadd_right_is_none_raises():
    # Adding None should raise TypeError
    a = PropertyValueList([1])
    with pytest.raises(TypeError):
        a += None

def test_iadd_right_is_bool_raises():
    # Adding True/False should raise TypeError
    a = PropertyValueList([1])
    with pytest.raises(TypeError):
        a += True

def test_iadd_right_is_object_raises():
    # Adding an object should raise TypeError
    class Dummy:
        pass
    a = PropertyValueList([1])
    with pytest.raises(TypeError):
        a += Dummy()

# --- Large Scale Test Cases ---

def test_iadd_large_lists():
    # Adding two large lists
    left = list(range(500))
    right = list(range(500, 1000))
    a = PropertyValueList(left)
    a += right

def test_iadd_large_right_empty():
    # Adding large empty right to large left
    a = PropertyValueList(list(range(1000)))
    a += []

def test_iadd_large_right_generator():
    # Adding large generator
    a = PropertyValueList(list(range(500)))
    a += (x for x in range(500, 1000))

def test_iadd_large_right_tuple():
    # Adding large tuple
    a = PropertyValueList(list(range(500)))
    a += tuple(range(500, 1000))

def test_iadd_large_right_set():
    # Adding large set
    a = PropertyValueList(list(range(500)))
    a += set(range(500, 1000))

def test_iadd_large_left_empty():
    # Adding large right to empty left
    a = PropertyValueList([])
    a += list(range(1000))

def test_iadd_large_right_is_range():
    # Adding large range
    a = PropertyValueList(list(range(500)))
    a += range(500, 1000)

def test_iadd_large_right_is_dict():
    # Adding large dict should add keys
    d = {i: str(i) for i in range(500, 1000)}
    a = PropertyValueList(list(range(500)))
    a += d

def test_iadd_large_right_is_propertyvaluelist():
    # Adding large PropertyValueList
    a = PropertyValueList(list(range(500)))
    b = PropertyValueList(list(range(500, 1000)))
    a += b
# 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-PropertyValueList.__iadd__-mhwy5bj3 and push.

Codeflash Static Badge

The optimization achieves a 15% speedup by eliminating Python's `super()` method resolution overhead through two key changes:

**1. Direct parent class calls instead of `super()`:**
- In `__init__()`: Replaced `super().__init__(*args, **kwargs)` with explicit calls to `list.__init__(self, *args, **kwargs)` and `PropertyValueContainer.__init__(self)`
- In `__iadd__()`: Replaced `super().__iadd__(y)` with `list.__iadd__(self, y)`

**2. Added `__slots__ = ()` to prevent `__dict__` creation:**
- Saves memory overhead for instances that don't need additional attributes
- Provides slight performance improvement for attribute access

**Why this is faster:**
The `super()` function in Python performs Method Resolution Order (MRO) lookup at runtime, which involves traversing the inheritance hierarchy to find the correct method. For simple multiple inheritance like `PropertyValueList(PropertyValueContainer, list[T])`, this adds unnecessary overhead when we know exactly which parent method to call. Direct method calls bypass this resolution entirely.

**Performance context:**
Based on the test results, the optimization is most effective for basic list operations like `test_iadd_inplace_identity()` which showed the 15.6% improvement. The speedup is particularly valuable since `__iadd__` is a fundamental list operation that could be called frequently in data manipulation workloads.

**Memory benefit:**
The `__slots__ = ()` declaration prevents Python from creating a `__dict__` for each instance, reducing memory footprint when many `PropertyValueList` instances are created, which is common in property-heavy frameworks like Bokeh.
@codeflash-ai codeflash-ai Bot requested a review from mashraf-222 November 13, 2025 04:46
@codeflash-ai codeflash-ai Bot added ⚡️ codeflash Optimization PR opened by Codeflash AI 🎯 Quality: Medium Optimization Quality according to Codeflash labels Nov 13, 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: Medium Optimization Quality according to Codeflash

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants