Skip to content

Conversation

@codeflash-ai
Copy link

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

📄 8% (0.08x) speedup for PropertyValueDict.clear in src/bokeh/core/property/wrappers.py

⏱️ Runtime : 145 microseconds 134 microseconds (best of 250 runs)

📝 Explanation and details

The optimized code achieves an 8% speedup by eliminating Python's method resolution overhead in two critical areas of the PropertyValueDict class:

Key Optimizations:

  1. Direct Constructor Calls: Instead of using super().__init__(*args, **kwargs) which triggers Python's Method Resolution Order (MRO) lookup, the optimized version directly calls each parent class constructor:

    • PropertyValueContainer.__init__(self)
    • dict.__init__(self, *args, **kwargs)

    This avoids the MRO dispatch mechanism that must determine which parent's __init__ to call in the multiple inheritance hierarchy.

  2. Direct Method Invocation: The clear() method replaces super().clear() with dict.clear(self), bypassing the super() proxy object and directly invoking the C-implemented dict method.

Why This Speeds Things Up:

  • super() creates a proxy object and performs dynamic method lookup through the MRO chain, adding Python-level overhead
  • Direct method calls on built-in types like dict execute at C speed without Python dispatch overhead
  • The optimization is most effective for frequently called methods on built-in container types

Performance Impact Based on Tests:
The annotated tests show consistent 8-28% improvements across all scenarios, with the largest gains (20-28%) occurring in cases with:

  • Non-empty dictionaries being cleared
  • Complex value types (nested dicts, mixed types)
  • Bulk operations followed by clear

The optimization is particularly valuable since PropertyValueDict is a wrapper around Python's built-in dict and these methods may be called frequently in property update scenarios within the Bokeh framework.

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 🔘 None Found
🌀 Generated Regression Tests 120 Passed
⏪ Replay Tests 🔘 None Found
🔎 Concolic Coverage Tests 🔘 None Found
📊 Tests Coverage 100.0%
🌀 Generated Regression Tests and Runtime
from __future__ import annotations

from typing import Any, TypeVar

# imports
import pytest  # used for our unit tests
from bokeh.core.property.wrappers import PropertyValueDict


# Minimal stubs for dependencies
class HasProps: pass
class PropertyDescriptor: pass

def notify_owner(fn):
    # Dummy decorator for testing purposes
    return fn

class PropertyValueContainer:
    _owners: set[tuple[HasProps, PropertyDescriptor[Any]]]
    def __init__(self, *args, **kwargs) -> None:
        self._owners = set()
        super().__init__(*args, **kwargs)

T_Val = TypeVar("T_Val")
from bokeh.core.property.wrappers import PropertyValueDict

# unit tests

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

def test_clear_empty_dict():
    # Test clearing an empty PropertyValueDict
    d = PropertyValueDict()
    d.clear() # 1.64μs -> 1.51μs (8.74% faster)

def test_clear_single_item():
    # Test clearing a dict with one item
    d = PropertyValueDict(a=1)
    d.clear() # 1.67μs -> 1.51μs (10.8% faster)

def test_clear_multiple_items():
    # Test clearing a dict with multiple items
    d = PropertyValueDict(a=1, b=2, c=3)
    d.clear() # 1.72μs -> 1.58μs (8.52% faster)

def test_clear_then_add():
    # Test that after clearing, new items can be added
    d = PropertyValueDict(a=1, b=2)
    d.clear() # 1.68μs -> 1.50μs (11.5% faster)
    d['x'] = 42

def test_clear_then_clear_again():
    # Test that clear can be called multiple times without error
    d = PropertyValueDict(a=1)
    d.clear() # 1.60μs -> 1.48μs (7.97% faster)
    d.clear() # 747ns -> 720ns (3.75% faster)

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

def test_clear_with_non_str_keys():
    # PropertyValueDict should accept only str keys; test with str-like keys
    d = PropertyValueDict({'1': 10, '2': 20})
    d.clear() # 1.59μs -> 1.37μs (16.1% faster)

def test_clear_with_mutable_values():
    # Test clearing when values are mutable objects
    d = PropertyValueDict(a=[], b={}, c=set())
    d.clear() # 1.62μs -> 1.63μs (0.246% slower)

def test_clear_with_nested_dicts():
    # Test clearing when values are nested PropertyValueDicts
    nested = PropertyValueDict(x=1)
    d = PropertyValueDict(a=nested, b=PropertyValueDict(y=2))
    d.clear() # 1.79μs -> 1.61μs (11.3% faster)

def test_clear_with_unicode_keys():
    # Test clearing with unicode string keys
    d = PropertyValueDict({'ключ': 1, '值': 2})
    d.clear() # 1.67μs -> 1.45μs (15.4% faster)

def test_clear_with_empty_string_key():
    # Test clearing with an empty string key
    d = PropertyValueDict({'': 123})
    d.clear() # 1.60μs -> 1.52μs (5.26% faster)

def test_clear_does_not_affect_other_dicts():
    # Test that clearing one PropertyValueDict does not affect another
    d1 = PropertyValueDict(a=1)
    d2 = PropertyValueDict(b=2)
    d1.clear() # 1.65μs -> 1.48μs (12.1% faster)

def test_clear_preserves_instance_type():
    # After clearing, the object is still a PropertyValueDict
    d = PropertyValueDict(a=1)
    d.clear() # 1.65μs -> 1.41μs (16.4% faster)

def test_clear_does_not_raise_on_already_empty():
    # Clearing an already empty dict should not raise
    d = PropertyValueDict()
    d.clear() # 1.63μs -> 1.45μs (12.3% faster)

def test_clear_with_large_values():
    # Test clearing when values are large objects
    big_list = [i for i in range(100)]
    d = PropertyValueDict(a=big_list)
    d.clear() # 1.72μs -> 1.50μs (14.0% faster)

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

def test_clear_large_dict():
    # Test clearing a large dict (up to 1000 items)
    d = PropertyValueDict({str(i): i for i in range(1000)})
    d.clear() # 10.7μs -> 10.4μs (2.21% faster)

def test_clear_large_dict_multiple_times():
    # Test clearing a large dict multiple times
    d = PropertyValueDict({str(i): i for i in range(500)})
    d.clear() # 6.07μs -> 5.87μs (3.44% faster)
    d.clear() # 805ns -> 769ns (4.68% faster)

def test_clear_after_bulk_update():
    # Test clearing after bulk update
    d = PropertyValueDict()
    d.update({str(i): i for i in range(300)})
    d.clear() # 3.39μs -> 3.35μs (1.10% faster)

def test_clear_performance():
    # Test that clear executes in reasonable time for 1000 items
    import time
    d = PropertyValueDict({str(i): i for i in range(1000)})
    start = time.time()
    d.clear() # 10.5μs -> 10.3μs (1.53% faster)
    duration = time.time() - start

def test_clear_with_varied_types_of_values():
    # Test clearing with values of various types
    d = PropertyValueDict({
        'int': 1,
        'float': 2.0,
        'str': 'abc',
        'list': [1,2,3],
        'dict': {'x': 1},
        'set': {1,2},
        'tuple': (1,2),
        'none': None,
        'bool': True,
    })
    d.clear() # 1.88μs -> 1.80μs (4.51% faster)

# ------------------------
# Mutation Safety Test
# ------------------------

def test_clear_removes_all_items():
    # If clear does not remove all items, this test will fail
    d = PropertyValueDict({'a': 1, 'b': 2, 'c': 3})
    d.clear() # 1.69μs -> 1.56μs (8.46% faster)

def test_clear_is_not_replaced_by_del():
    # If clear is replaced by del, this test will fail
    d = PropertyValueDict({'a': 1, 'b': 2})
    d.clear() # 1.66μs -> 1.50μs (10.8% faster)

def test_clear_does_not_return_anything():
    # clear should return None
    d = PropertyValueDict({'a': 1})
    codeflash_output = d.clear(); result = codeflash_output # 1.65μs -> 1.49μs (10.4% faster)

def test_clear_does_not_modify_other_state():
    # Clearing should not affect _owners or other attributes
    d = PropertyValueDict(a=1)
    owners_before = d._owners.copy()
    d.clear() # 1.62μs -> 1.44μs (13.2% faster)
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.
from __future__ import annotations

from typing import Any, TypeVar

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


# Dummy notify_owner decorator for testing, as the real one is not provided.
def notify_owner(func):
    def wrapper(self, *args, **kwargs):
        # In real Bokeh, this would notify property owners of the mutation.
        return func(self, *args, **kwargs)
    return wrapper

class PropertyValueContainer:
    _owners: set

    def __init__(self, *args, **kwargs) -> None:
        self._owners = set()
        super().__init__(*args, **kwargs)

T_Val = TypeVar("T_Val")
from bokeh.core.property.wrappers import PropertyValueDict

# unit tests

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

def test_clear_on_empty_dict():
    # Test clearing an empty dict leaves it empty
    d = PropertyValueDict()
    d.clear() # 1.68μs -> 1.65μs (1.82% faster)

def test_clear_on_nonempty_dict():
    # Test clearing a dict with items removes all items
    d = PropertyValueDict(a=1, b=2)
    d.clear() # 1.87μs -> 1.51μs (24.2% faster)

def test_clear_returns_none():
    # clear() should return None
    d = PropertyValueDict(a=1)
    codeflash_output = d.clear(); result = codeflash_output # 1.76μs -> 1.52μs (15.7% faster)

def test_clear_multiple_times():
    # Test calling clear multiple times is safe and idempotent
    d = PropertyValueDict(a=1, b=2)
    d.clear() # 1.75μs -> 1.51μs (15.7% faster)
    d.clear() # 741ns -> 681ns (8.81% faster)
    d.clear() # 505ns -> 453ns (11.5% faster)

def test_clear_after_mutation():
    # Test clear after mutation
    d = PropertyValueDict(a=1)
    d['b'] = 2
    d.clear() # 1.21μs -> 1.00μs (20.7% faster)

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

def test_clear_large_keys_and_values():
    # Test clear with large string keys and values
    key = "x" * 100
    value = "y" * 100
    d = PropertyValueDict({key: value})
    d.clear() # 1.62μs -> 1.34μs (21.5% faster)

def test_clear_with_non_str_keys():
    # PropertyValueDict should only accept str keys, test with str-like keys
    d = PropertyValueDict({'1': 2, 'True': False})
    d.clear() # 1.64μs -> 1.27μs (28.6% faster)

def test_clear_with_mutable_values():
    # Test clear on dict with mutable values
    d = PropertyValueDict(a=[1,2,3], b={'x': 1})
    d.clear() # 1.70μs -> 1.50μs (14.0% faster)

def test_clear_with_nested_dicts():
    # Test clear on dict with nested dict values
    d = PropertyValueDict(a=PropertyValueDict(b=2), c=PropertyValueDict(d=3))
    d.clear() # 1.91μs -> 1.66μs (15.3% faster)

def test_clear_does_not_affect_other_instances():
    # Clearing one instance should not affect another
    d1 = PropertyValueDict(a=1)
    d2 = PropertyValueDict(b=2)
    d1.clear() # 1.66μs -> 1.42μs (17.0% faster)

def test_clear_does_not_raise():
    # Clear should never raise, even if already empty
    d = PropertyValueDict()
    try:
        d.clear()
    except Exception as e:
        pytest.fail(f"clear() raised {e}")

def test_clear_preserves_type():
    # After clear, type should still be PropertyValueDict
    d = PropertyValueDict(a=1)
    d.clear() # 1.69μs -> 1.42μs (19.0% faster)

def test_clear_with_kwargs_init():
    # Test clear on dict initialized with kwargs
    d = PropertyValueDict(a=1, b=2)
    d.clear() # 1.56μs -> 1.39μs (12.9% faster)

def test_clear_with_iterable_init():
    # Test clear on dict initialized with iterable
    d = PropertyValueDict([('a', 1), ('b', 2)])
    d.clear() # 1.68μs -> 1.52μs (10.7% faster)

def test_clear_with_update_and_setdefault():
    # Test clear after update and setdefault
    d = PropertyValueDict()
    d.update({'x': 1, 'y': 2})
    d.setdefault('z', 3)
    d.clear() # 1.16μs -> 1.01μs (14.7% faster)

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

def test_clear_large_dict():
    # Test clear on a dict with 1000 items
    d = PropertyValueDict({str(i): i for i in range(1000)})
    d.clear() # 10.8μs -> 10.6μs (2.35% faster)

def test_clear_performance_large_dict():
    # Test clear does not hang or error with 999 items
    d = PropertyValueDict({str(i): i for i in range(999)})
    d.clear() # 10.6μs -> 10.4μs (1.84% faster)

def test_clear_after_bulk_mutations():
    # Test clear after many mutations
    d = PropertyValueDict()
    for i in range(500):
        d[str(i)] = i
    for i in range(250):
        del d[str(i)]
    d.clear() # 5.53μs -> 5.43μs (1.92% faster)

def test_clear_on_dict_with_large_values():
    # Test clear on dict with large values
    d = PropertyValueDict({str(i): [j for j in range(100)] for i in range(50)})
    d.clear() # 6.20μs -> 5.92μs (4.56% faster)

def test_clear_on_dict_with_varied_types_of_values():
    # Test clear on dict with values of different types
    d = PropertyValueDict({
        'a': [1,2,3],
        'b': {'x': 1},
        'c': "string",
        'd': 123,
        'e': None,
        'f': (1,2),
        'g': set([1,2])
    })
    d.clear() # 1.93μs -> 1.70μs (13.7% faster)

# --------------------------
# Additional Edge Cases
# --------------------------

def test_clear_with_unicode_keys():
    # Test clear with unicode string keys
    d = PropertyValueDict({'ключ': 'значение', '测试': '值'})
    d.clear() # 1.70μs -> 1.50μs (13.5% faster)

def test_clear_with_empty_string_keys():
    # Test clear with empty string keys
    d = PropertyValueDict({'': 1, 'a': 2})
    d.clear() # 1.66μs -> 1.45μs (14.5% faster)

def test_clear_with_boolean_string_keys():
    # Test clear with boolean string keys
    d = PropertyValueDict({'True': 1, 'False': 0})
    d.clear() # 1.68μs -> 1.41μs (19.5% faster)

def test_clear_does_not_return_dict():
    # clear should not return the dict itself
    d = PropertyValueDict(a=1)
    codeflash_output = d.clear(); result = codeflash_output # 1.66μs -> 1.45μs (14.2% faster)

def test_clear_on_dict_with_duplicate_values():
    # Test clear on dict with duplicate values
    d = PropertyValueDict({'a': 1, 'b': 1, 'c': 1})
    d.clear() # 1.70μs -> 1.48μs (14.8% faster)

def test_clear_on_dict_with_none_values():
    # Test clear on dict with None values
    d = PropertyValueDict({'a': None, 'b': None})
    d.clear() # 1.72μs -> 1.46μs (18.2% faster)

def test_clear_on_dict_with_zero_values():
    # Test clear on dict with zero values
    d = PropertyValueDict({'a': 0, 'b': 0})
    d.clear() # 1.60μs -> 1.42μs (12.9% faster)

def test_clear_on_dict_with_negative_values():
    # Test clear on dict with negative values
    d = PropertyValueDict({'a': -1, 'b': -2})
    d.clear() # 1.70μs -> 1.43μs (18.7% faster)

def test_clear_on_dict_with_float_values():
    # Test clear on dict with float values
    d = PropertyValueDict({'a': 1.5, 'b': 2.5})
    d.clear() # 1.62μs -> 1.47μs (10.7% faster)

def test_clear_on_dict_with_tuple_values():
    # Test clear on dict with tuple values
    d = PropertyValueDict({'a': (1,2), 'b': (3,4)})
    d.clear() # 1.67μs -> 1.41μs (18.6% faster)

def test_clear_on_dict_with_set_values():
    # Test clear on dict with set values
    d = PropertyValueDict({'a': {1,2}, 'b': {3,4}})
    d.clear() # 1.69μs -> 1.53μs (10.8% faster)

def test_clear_on_dict_with_mixed_types():
    # Test clear on dict with mixed value types
    d = PropertyValueDict({'a': 1, 'b': 'str', 'c': [1,2], 'd': None})
    d.clear() # 1.76μs -> 1.46μs (20.5% 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-PropertyValueDict.clear-mhwzg7gs and push.

Codeflash Static Badge

The optimized code achieves an 8% speedup by eliminating Python's method resolution overhead in two critical areas of the `PropertyValueDict` class:

**Key Optimizations:**

1. **Direct Constructor Calls**: Instead of using `super().__init__(*args, **kwargs)` which triggers Python's Method Resolution Order (MRO) lookup, the optimized version directly calls each parent class constructor:
   - `PropertyValueContainer.__init__(self)` 
   - `dict.__init__(self, *args, **kwargs)`
   
   This avoids the MRO dispatch mechanism that must determine which parent's `__init__` to call in the multiple inheritance hierarchy.

2. **Direct Method Invocation**: The `clear()` method replaces `super().clear()` with `dict.clear(self)`, bypassing the `super()` proxy object and directly invoking the C-implemented dict method.

**Why This Speeds Things Up:**
- `super()` creates a proxy object and performs dynamic method lookup through the MRO chain, adding Python-level overhead
- Direct method calls on built-in types like `dict` execute at C speed without Python dispatch overhead
- The optimization is most effective for frequently called methods on built-in container types

**Performance Impact Based on Tests:**
The annotated tests show consistent 8-28% improvements across all scenarios, with the largest gains (20-28%) occurring in cases with:
- Non-empty dictionaries being cleared
- Complex value types (nested dicts, mixed types)
- Bulk operations followed by clear

The optimization is particularly valuable since `PropertyValueDict` is a wrapper around Python's built-in `dict` and these methods may be called frequently in property update scenarios within the Bokeh framework.
@codeflash-ai codeflash-ai bot requested a review from mashraf-222 November 13, 2025 05:23
@codeflash-ai codeflash-ai bot added ⚡️ codeflash Optimization PR opened by Codeflash AI 🎯 Quality: High 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: High Optimization Quality according to Codeflash

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant