Skip to content

Conversation

@codeflash-ai
Copy link

@codeflash-ai codeflash-ai bot commented Oct 28, 2025

📄 46% (0.46x) speedup for _handle_sublists in src/bokeh/plotting/graph.py

⏱️ Runtime : 987 microseconds 674 microseconds (best of 261 runs)

📝 Explanation and details

The optimization achieves a 46% speedup by eliminating redundant iterations over the values list and reducing the overhead of Python's built-in any() and all() functions.

Key changes:

  1. Single-pass detection: Replaced any(isinstance(x, (list, tuple)) for x in values) with an explicit loop that breaks early when finding the first non-scalar element
  2. Combined validation: The second validation loop (all(isinstance(...))) now runs only when non-scalars were detected, avoiding unnecessary work for all-scalar cases
  3. Reduced generator overhead: Eliminated multiple generator expressions that were creating temporary objects

Why this is faster:

  • All-scalar cases (like test_all_scalars_ints, test_all_none) show 45-124% speedups because they avoid the expensive any() call and skip the validation loop entirely
  • Non-scalar cases (like test_all_lists, test_all_tuples) show 47-67% speedups by replacing two separate traversals with a more efficient early-exit detection followed by targeted validation
  • Large datasets benefit significantly - test_large_all_none_with_list shows 111% speedup because the optimization scales linearly with input size

The optimization is particularly effective for common use cases where inputs are either all-scalar or consistently non-scalar, as it can short-circuit expensive validation work.

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 🔘 None Found
🌀 Generated Regression Tests 70 Passed
⏪ Replay Tests 🔘 None Found
🔎 Concolic Coverage Tests 🔘 None Found
📊 Tests Coverage 100.0%
🌀 Generated Regression Tests and Runtime
import pytest  # used for our unit tests
from bokeh.plotting.graph import _handle_sublists

# unit tests

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

def test_all_scalars_ints():
    # All elements are scalars (ints)
    vals = [1, 2, 3]
    codeflash_output = _handle_sublists(vals); result = codeflash_output # 1.36μs -> 871ns (55.9% faster)

def test_all_scalars_floats():
    # All elements are scalars (floats)
    vals = [1.0, 2.5, 3.7]
    codeflash_output = _handle_sublists(vals); result = codeflash_output # 1.26μs -> 847ns (48.6% faster)

def test_all_lists():
    # All elements are lists
    vals = [[1, 2], [3, 4], [5, 6]]
    codeflash_output = _handle_sublists(vals); result = codeflash_output # 2.67μs -> 1.62μs (64.7% faster)

def test_all_tuples():
    # All elements are tuples
    vals = [(1, 2), (3, 4), (5, 6)]
    codeflash_output = _handle_sublists(vals); result = codeflash_output # 2.58μs -> 1.75μs (47.5% faster)

def test_all_none():
    # All elements are None
    vals = [None, None, None]
    codeflash_output = _handle_sublists(vals); result = codeflash_output # 1.31μs -> 584ns (124% faster)

def test_lists_with_none():
    # Some elements are lists, some are None
    vals = [[1, 2], None, [3, 4]]
    codeflash_output = _handle_sublists(vals); result = codeflash_output # 2.49μs -> 1.57μs (59.1% faster)

def test_tuples_with_none():
    # Some elements are tuples, some are None
    vals = [(1, 2), None, (3, 4)]
    codeflash_output = _handle_sublists(vals); result = codeflash_output # 2.58μs -> 1.70μs (51.7% faster)

def test_lists_and_tuples():
    # Mix of lists and tuples
    vals = [[1, 2], (3, 4), [5, 6]]
    codeflash_output = _handle_sublists(vals); result = codeflash_output # 2.50μs -> 1.65μs (51.8% faster)

def test_lists_tuples_none():
    # Mix of lists, tuples, and None
    vals = [[1], (2,), None]
    codeflash_output = _handle_sublists(vals); result = codeflash_output # 2.41μs -> 1.63μs (48.2% faster)

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

def test_empty_list():
    # Empty input list
    vals = []
    codeflash_output = _handle_sublists(vals); result = codeflash_output # 768ns -> 449ns (71.0% faster)

def test_single_scalar():
    # Single scalar value
    vals = [42]
    codeflash_output = _handle_sublists(vals); result = codeflash_output # 1.10μs -> 697ns (57.7% faster)

def test_single_list():
    # Single list value
    vals = [[42]]
    codeflash_output = _handle_sublists(vals); result = codeflash_output # 2.18μs -> 1.31μs (66.1% faster)

def test_single_tuple():
    # Single tuple value
    vals = [(42,)]
    codeflash_output = _handle_sublists(vals); result = codeflash_output # 2.26μs -> 1.48μs (52.8% faster)

def test_single_none():
    # Single None value
    vals = [None]
    codeflash_output = _handle_sublists(vals); result = codeflash_output # 1.07μs -> 497ns (115% faster)

def test_mix_scalar_and_list_raises():
    # Mix of scalar and list should raise ValueError
    vals = [1, [2, 3], 4]
    with pytest.raises(ValueError):
        _handle_sublists(vals) # 2.26μs -> 1.41μs (59.8% faster)

def test_mix_scalar_and_tuple_raises():
    # Mix of scalar and tuple should raise ValueError
    vals = [1, (2, 3), 4]
    with pytest.raises(ValueError):
        _handle_sublists(vals) # 2.33μs -> 1.52μs (53.5% faster)

def test_mix_scalar_list_none_raises():
    # Mix of scalar, list, and None should raise ValueError
    vals = [1, None, [2, 3]]
    with pytest.raises(ValueError):
        _handle_sublists(vals) # 2.23μs -> 1.35μs (65.5% faster)

def test_mix_scalar_tuple_none_raises():
    # Mix of scalar, tuple, and None should raise ValueError
    vals = [1, None, (2, 3)]
    with pytest.raises(ValueError):
        _handle_sublists(vals) # 2.25μs -> 1.46μs (54.1% faster)

def test_all_empty_lists():
    # All elements are empty lists
    vals = [[], [], []]
    codeflash_output = _handle_sublists(vals); result = codeflash_output # 2.45μs -> 1.61μs (52.0% faster)

def test_all_empty_tuples():
    # All elements are empty tuples
    vals = [(), (), ()]
    codeflash_output = _handle_sublists(vals); result = codeflash_output # 2.71μs -> 1.76μs (53.4% faster)

def test_lists_with_empty_and_none():
    # Mix of empty lists and None
    vals = [[], None, []]
    codeflash_output = _handle_sublists(vals); result = codeflash_output # 2.31μs -> 1.50μs (54.0% faster)

def test_tuples_with_empty_and_none():
    # Mix of empty tuples and None
    vals = [(), None, ()]
    codeflash_output = _handle_sublists(vals); result = codeflash_output # 2.56μs -> 1.69μs (52.0% faster)

def test_lists_and_tuples_with_empty_and_none():
    # Mix of lists, tuples, empty, and None
    vals = [[1], (), None, [], (2,)]
    codeflash_output = _handle_sublists(vals); result = codeflash_output # 2.89μs -> 1.97μs (46.4% faster)

def test_none_and_scalar_only():
    # Mix of None and scalar only (should not raise)
    vals = [None, 1, None]
    codeflash_output = _handle_sublists(vals); result = codeflash_output # 1.28μs -> 783ns (63.9% faster)

def test_none_and_list_only():
    # Mix of None and list only (should not raise)
    vals = [None, [1, 2], None]
    codeflash_output = _handle_sublists(vals); result = codeflash_output # 2.51μs -> 1.49μs (69.0% faster)

def test_none_and_tuple_only():
    # Mix of None and tuple only (should not raise)
    vals = [None, (1, 2), None]
    codeflash_output = _handle_sublists(vals); result = codeflash_output # 2.59μs -> 1.64μs (58.1% faster)

def test_nested_lists_are_handled_flat():
    # Nested lists should be treated as non-scalar, but not flattened
    vals = [[[1, 2]], [[3, 4]], None]
    codeflash_output = _handle_sublists(vals); result = codeflash_output # 2.40μs -> 1.49μs (60.7% faster)

def test_nested_tuples_are_handled_flat():
    # Nested tuples should be treated as non-scalar, but not flattened
    vals = [((1, 2),), ((3, 4),), None]
    codeflash_output = _handle_sublists(vals); result = codeflash_output # 2.34μs -> 1.65μs (41.6% faster)

def test_mix_of_different_non_scalars():
    # Mix of lists and tuples (no scalars), should work
    vals = [[1], (2, 3), [], (), None]
    codeflash_output = _handle_sublists(vals); result = codeflash_output # 2.77μs -> 1.87μs (47.9% faster)

def test_large_list_of_scalars_and_none():
    # Large list of scalars and None (should not raise)
    vals = [None if i % 10 == 0 else i for i in range(1000)]
    codeflash_output = _handle_sublists(vals); result = codeflash_output # 56.5μs -> 39.8μs (41.8% faster)

def test_large_list_of_lists_and_none():
    # Large list of lists and None (should not raise)
    vals = [None if i % 10 == 0 else [i, i+1] for i in range(1000)]
    codeflash_output = _handle_sublists(vals); result = codeflash_output # 99.2μs -> 69.0μs (43.8% faster)
    for i, v in enumerate(result):
        if i % 10 == 0:
            pass
        else:
            pass

def test_large_list_of_tuples_and_none():
    # Large list of tuples and None (should not raise)
    vals = [None if i % 10 == 0 else (i, i+1) for i in range(1000)]
    codeflash_output = _handle_sublists(vals); result = codeflash_output # 89.2μs -> 70.6μs (26.3% faster)
    for i, v in enumerate(result):
        if i % 10 == 0:
            pass
        else:
            pass

def test_large_list_mix_scalars_and_lists_raises():
    # Large list mixing scalars and lists should raise
    vals = [i if i % 2 == 0 else [i, i+1] for i in range(1000)]
    with pytest.raises(ValueError):
        _handle_sublists(vals) # 2.35μs -> 1.40μs (68.4% faster)

def test_large_list_mix_scalars_and_tuples_raises():
    # Large list mixing scalars and tuples should raise
    vals = [i if i % 2 == 0 else (i, i+1) for i in range(1000)]
    with pytest.raises(ValueError):
        _handle_sublists(vals) # 2.35μs -> 1.49μs (57.0% faster)

def test_large_list_mix_scalars_lists_none_raises():
    # Large list mixing scalars, lists, and None should raise
    vals = [None if i % 10 == 0 else (i if i % 2 == 0 else [i, i+1]) for i in range(1000)]
    with pytest.raises(ValueError):
        _handle_sublists(vals) # 2.43μs -> 1.34μs (80.9% faster)

def test_large_list_mix_scalars_tuples_none_raises():
    # Large list mixing scalars, tuples, and None should raise
    vals = [None if i % 10 == 0 else (i if i % 2 == 0 else (i, i+1)) for i in range(1000)]
    with pytest.raises(ValueError):
        _handle_sublists(vals) # 2.69μs -> 1.54μs (74.2% faster)
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.
#------------------------------------------------
import pytest  # used for our unit tests
from bokeh.plotting.graph import _handle_sublists

# unit tests

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

def test_all_scalars_ints():
    # All scalars: should return as is
    data = [1, 2, 3]
    codeflash_output = _handle_sublists(data) # 1.29μs -> 886ns (45.9% faster)

def test_all_scalars_floats():
    # All floats: should return as is
    data = [1.1, 2.2, 3.3]
    codeflash_output = _handle_sublists(data) # 1.18μs -> 800ns (47.5% faster)

def test_all_scalars_strings():
    # All strings: should return as is
    data = ["a", "b", "c"]
    codeflash_output = _handle_sublists(data) # 1.18μs -> 796ns (48.4% faster)

def test_all_lists():
    # All lists: should convert all to lists (idempotent)
    data = [[1, 2], [3, 4], [5]]
    codeflash_output = _handle_sublists(data) # 2.54μs -> 1.62μs (56.9% faster)

def test_all_tuples():
    # All tuples: should convert all to lists
    data = [(1, 2), (3, 4), (5,)]
    codeflash_output = _handle_sublists(data) # 2.68μs -> 1.75μs (53.5% faster)

def test_all_lists_and_none():
    # Lists and None: None should become []
    data = [[1, 2], None, [3]]
    codeflash_output = _handle_sublists(data) # 2.37μs -> 1.50μs (57.8% faster)

def test_all_tuples_and_none():
    # Tuples and None: None should become []
    data = [(1, 2), None, (3,)]
    codeflash_output = _handle_sublists(data) # 2.48μs -> 1.67μs (48.7% faster)

def test_all_empty_lists():
    # All empty lists: should remain empty lists
    data = [[], [], []]
    codeflash_output = _handle_sublists(data) # 2.50μs -> 1.55μs (61.8% faster)

def test_all_empty_tuples():
    # All empty tuples: should become empty lists
    data = [(), (), ()]
    codeflash_output = _handle_sublists(data) # 2.61μs -> 1.72μs (51.9% faster)

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

def test_mix_scalars_and_list_raises():
    # Mixing scalars and lists should raise ValueError
    data = [1, [2, 3], 4]
    with pytest.raises(ValueError):
        _handle_sublists(data) # 2.37μs -> 1.40μs (69.2% faster)

def test_mix_scalars_and_tuple_raises():
    # Mixing scalars and tuples should raise ValueError
    data = [1, (2, 3), 4]
    with pytest.raises(ValueError):
        _handle_sublists(data) # 2.25μs -> 1.44μs (56.8% faster)

def test_mix_list_and_tuple_allowed():
    # Mixing lists and tuples is allowed, all become lists
    data = [[1, 2], (3, 4), [5]]
    codeflash_output = _handle_sublists(data) # 2.66μs -> 1.76μs (51.4% faster)

def test_lists_and_none_and_tuples():
    # Lists, tuples, and None: all nones become [], tuples become lists
    data = [[1], None, (2, 3), [], (4,)]
    codeflash_output = _handle_sublists(data) # 2.81μs -> 1.96μs (43.2% faster)

def test_all_none_when_non_scalar():
    # All None: when any item is a list/tuple, all None become []
    data = [None, None, (1,)]
    codeflash_output = _handle_sublists(data) # 2.56μs -> 1.60μs (60.0% faster)

def test_empty_input():
    # Empty input: should return empty list
    data = []
    codeflash_output = _handle_sublists(data) # 757ns -> 467ns (62.1% faster)

def test_single_scalar():
    # Single scalar: should return as is
    data = [42]
    codeflash_output = _handle_sublists(data) # 1.08μs -> 714ns (51.3% faster)

def test_single_list():
    # Single list: should return as list of list
    data = [[1, 2, 3]]
    codeflash_output = _handle_sublists(data) # 2.15μs -> 1.33μs (61.8% faster)

def test_single_tuple():
    # Single tuple: should return as list of list
    data = [(1, 2, 3)]
    codeflash_output = _handle_sublists(data) # 2.14μs -> 1.47μs (46.2% faster)

def test_single_none_with_list():
    # Single None with list: None becomes []
    data = [None, [1, 2]]
    codeflash_output = _handle_sublists(data) # 2.40μs -> 1.39μs (72.4% faster)

def test_nested_lists_are_not_flattened():
    # Nested lists: inner lists are not flattened
    data = [[[1, 2], [3]], [[4]], None]
    codeflash_output = _handle_sublists(data) # 2.32μs -> 1.52μs (52.5% faster)

def test_mixed_types_within_lists():
    # Lists containing mixed types are allowed as long as top-level is list/tuple
    data = [[1, "a"], (2.2, None), []]
    codeflash_output = _handle_sublists(data) # 2.44μs -> 1.64μs (49.3% faster)

def test_none_with_scalars():
    # None with only scalars: should return as is
    data = [None, 1, 2]
    codeflash_output = _handle_sublists(data) # 1.26μs -> 836ns (50.4% faster)

def test_list_of_lists_and_scalars_raises():
    # Lists and scalars mixed: should raise
    data = [[1, 2], 3, [4, 5]]
    with pytest.raises(ValueError):
        _handle_sublists(data) # 2.17μs -> 1.32μs (64.6% faster)

def test_tuple_and_scalar_raises():
    # Tuples and scalars mixed: should raise
    data = [(1, 2), 3, (4, 5)]
    with pytest.raises(ValueError):
        _handle_sublists(data) # 2.23μs -> 1.46μs (53.3% faster)

def test_list_and_none_and_scalar_raises():
    # List, None, and scalar: should raise
    data = [[1, 2], None, 5]
    with pytest.raises(ValueError):
        _handle_sublists(data) # 2.12μs -> 1.28μs (66.2% faster)

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

def test_large_all_scalars():
    # Large list of scalars: should return as is
    data = list(range(1000))
    codeflash_output = _handle_sublists(data) # 56.7μs -> 41.6μs (36.2% faster)

def test_large_all_lists():
    # Large list of lists: should return as lists
    data = [[i, i+1] for i in range(1000)]
    codeflash_output = _handle_sublists(data) # 101μs -> 80.5μs (26.3% faster)

def test_large_all_tuples():
    # Large list of tuples: should convert all to lists
    data = [(i, i+1) for i in range(1000)]
    codeflash_output = _handle_sublists(data) # 91.0μs -> 70.6μs (28.8% faster)

def test_large_lists_and_nones():
    # Large list of lists and None: None becomes []
    data = [[i, i+1] if i % 2 == 0 else None for i in range(1000)]
    expected = [[i, i+1] if i % 2 == 0 else [] for i in range(1000)]
    codeflash_output = _handle_sublists(data) # 61.6μs -> 52.6μs (17.1% faster)

def test_large_lists_and_tuples_and_none():
    # Large mix of lists, tuples, and None: all become lists or []
    data = []
    expected = []
    for i in range(1000):
        if i % 3 == 0:
            data.append([i, i+1])
            expected.append([i, i+1])
        elif i % 3 == 1:
            data.append((i, i+1))
            expected.append([i, i+1])
        else:
            data.append(None)
            expected.append([])
    codeflash_output = _handle_sublists(data) # 71.0μs -> 57.6μs (23.1% faster)

def test_large_mixed_scalars_and_lists_raises():
    # Large list mixing scalars and lists: should raise
    data = [i if i % 2 == 0 else [i, i+1] for i in range(1000)]
    with pytest.raises(ValueError):
        _handle_sublists(data) # 2.45μs -> 1.43μs (70.9% faster)

def test_large_all_none_with_list():
    # Large list of None and at least one list: all None become []
    data = [None] * 999 + [[1, 2]]
    expected = [[]] * 999 + [[1, 2]]
    codeflash_output = _handle_sublists(data) # 88.4μs -> 41.8μs (111% faster)

def test_large_all_none_with_tuple():
    # Large list of None and at least one tuple: all None become []
    data = [None] * 999 + [(1, 2)]
    expected = [[]] * 999 + [[1, 2]]
    codeflash_output = _handle_sublists(data) # 88.3μs -> 41.8μs (111% faster)

def test_large_all_none_scalars():
    # Large list of None and scalars: should return as is
    data = [None if i % 2 == 0 else i for i in range(1000)]
    codeflash_output = _handle_sublists(data) # 56.4μs -> 27.2μs (108% 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-_handle_sublists-mhaznyuq and push.

Codeflash

The optimization achieves a **46% speedup** by eliminating redundant iterations over the `values` list and reducing the overhead of Python's built-in `any()` and `all()` functions.

**Key changes:**
1. **Single-pass detection**: Replaced `any(isinstance(x, (list, tuple)) for x in values)` with an explicit loop that breaks early when finding the first non-scalar element
2. **Combined validation**: The second validation loop (`all(isinstance(...))`) now runs only when non-scalars were detected, avoiding unnecessary work for all-scalar cases
3. **Reduced generator overhead**: Eliminated multiple generator expressions that were creating temporary objects

**Why this is faster:**
- **All-scalar cases** (like `test_all_scalars_ints`, `test_all_none`) show 45-124% speedups because they avoid the expensive `any()` call and skip the validation loop entirely
- **Non-scalar cases** (like `test_all_lists`, `test_all_tuples`) show 47-67% speedups by replacing two separate traversals with a more efficient early-exit detection followed by targeted validation
- **Large datasets** benefit significantly - `test_large_all_none_with_list` shows 111% speedup because the optimization scales linearly with input size

The optimization is particularly effective for common use cases where inputs are either all-scalar or consistently non-scalar, as it can short-circuit expensive validation work.
@codeflash-ai codeflash-ai bot requested a review from mashraf-222 October 28, 2025 19:58
@codeflash-ai codeflash-ai bot added ⚡️ codeflash Optimization PR opened by Codeflash AI 🎯 Quality: High Optimization Quality according to Codeflash labels Oct 28, 2025
Copy link

@misrasaurabh1 misrasaurabh1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems to be used in an important part of the codebase

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.

2 participants