Skip to content

Conversation

@codeflash-ai
Copy link

@codeflash-ai codeflash-ai bot commented May 24, 2025

📄 32% (0.32x) speedup for _build_subplot_title_annotations in plotly/_subplots.py

⏱️ Runtime : 752 microseconds 570 microseconds (best of 630 runs)

📝 Explanation and details

Here's a significantly faster and memory-optimized rewrite of your function. Key improvements come from.

  • Using list comprehensions and local variable caching to minimize Python-level for-loop overhead and sum calls.
  • Single-pass computation: Pre-compute the subtitle positions with optimized expressions by title_edge and avoid repeated function calls.
  • Zip iteration when pairing x/y and titles for less index-tracking and memory churn.
  • Avoid constructing unnecessary lists or intermediate variables.
  • Minimize attribute lookup and dictionary creation for each annotation.
  • Avoid repeated len() calls and index checks inside loops.

Optimization is focused on "hot paths" identified by the profiler (subtitle position computation and annotation dict creation).

Summary of major changes.

  • Move domain/position computation to list comprehensions (much better performance for numeric processing).
  • Minimal annotation-dict construction (everything is looked up or passed once).
  • zip and single loop for linear memory and cpu use.
  • Checks for shift/angle outside loop.
  • Only traverse up to min(len(subplot_titles), len(x_dom), len(y_dom)); avoids out-of-bounds.
  • Reuses font dict for all annotations to reduce per-dict allocation.

All logic and return structure are unchanged; only the implementation and inner loops are high-performance now.

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 🔘 None Found
🌀 Generated Regression Tests 41 Passed
⏪ Replay Tests 🔘 None Found
🔎 Concolic Coverage Tests 🔘 None Found
📊 Tests Coverage 100.0%
🌀 Generated Regression Tests Details
import pytest  # used for our unit tests
from plotly._subplots import _build_subplot_title_annotations

# unit tests

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

def test_single_subplot_top():
    # One subplot, title at top, no offset
    titles = ["A"]
    domains = [(0.1, 0.9), (0.2, 0.8)]
    codeflash_output = _build_subplot_title_annotations(titles, domains); result = codeflash_output
    annot = result[0]

def test_single_subplot_bottom_with_offset():
    # One subplot, title at bottom, offset 5
    titles = ["B"]
    domains = [(0.2, 0.7), (0.3, 0.6)]
    codeflash_output = _build_subplot_title_annotations(titles, domains, title_edge="bottom", offset=5); result = codeflash_output
    annot = result[0]

def test_single_subplot_right_with_offset():
    # One subplot, title at right, offset 10
    titles = ["C"]
    domains = [(0.0, 0.5), (0.0, 1.0)]
    codeflash_output = _build_subplot_title_annotations(titles, domains, title_edge="right", offset=10); result = codeflash_output
    annot = result[0]

def test_single_subplot_left_with_offset():
    # One subplot, title at left, offset 3
    titles = ["D"]
    domains = [(0.2, 0.8), (0.4, 0.9)]
    codeflash_output = _build_subplot_title_annotations(titles, domains, title_edge="left", offset=3); result = codeflash_output
    annot = result[0]

def test_multiple_subplots_top():
    # Two subplots, titles at top
    titles = ["A", "B"]
    domains = [
        (0.0, 0.5), (0.0, 0.5),  # subplot 1
        (0.5, 1.0), (0.0, 0.5)   # subplot 2
    ]
    codeflash_output = _build_subplot_title_annotations(titles, domains); result = codeflash_output

def test_multiple_subplots_mixed_edges():
    # Two subplots, test both left and right edges
    titles = ["Left", "Right"]
    domains = [
        (0.0, 0.5), (0.0, 1.0),
        (0.5, 1.0), (0.0, 1.0)
    ]
    # Left edge
    codeflash_output = _build_subplot_title_annotations(titles, domains, title_edge="left", offset=2); left_result = codeflash_output
    # Right edge
    codeflash_output = _build_subplot_title_annotations(titles, domains, title_edge="right", offset=2); right_result = codeflash_output

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

def test_empty_titles():
    # No titles, should return empty list
    codeflash_output = _build_subplot_title_annotations([], [(0, 1), (0, 1)])

def test_empty_domains():
    # No domains, should return empty list even if titles are present
    codeflash_output = _build_subplot_title_annotations(["A"], [])

def test_titles_longer_than_domains():
    # More titles than domains, extra titles are ignored
    titles = ["A", "B", "C"]
    domains = [(0, 1), (0, 1)]  # Only one subplot
    codeflash_output = _build_subplot_title_annotations(titles, domains); result = codeflash_output

def test_domains_longer_than_titles():
    # More domains than titles, extra domains are ignored
    titles = ["A"]
    domains = [(0, 1), (0, 1), (0.5, 1), (0.5, 1)]
    codeflash_output = _build_subplot_title_annotations(titles, domains); result = codeflash_output

def test_none_and_empty_string_titles():
    # Titles with None or empty string are skipped
    titles = ["A", None, "", "D"]
    domains = [
        (0, 0.5), (0, 0.5),
        (0.5, 1), (0, 0.5),
        (0, 0.5), (0.5, 1),
        (0.5, 1), (0.5, 1)
    ]
    codeflash_output = _build_subplot_title_annotations(titles, domains); result = codeflash_output

def test_invalid_title_edge_raises():
    # Invalid edge should raise ValueError
    with pytest.raises(ValueError):
        _build_subplot_title_annotations(["A"], [(0, 1), (0, 1)], title_edge="invalid")

def test_negative_and_zero_domains():
    # Domains with negative and zero values
    titles = ["Neg", "Zero"]
    domains = [
        (-1, 0), (0, 1),
        (0, 0), (0, 1)
    ]
    codeflash_output = _build_subplot_title_annotations(titles, domains); result = codeflash_output

def test_offset_zero_and_negative():
    # Offsets of zero and negative values
    titles = ["ZeroOff", "NegOff"]
    domains = [
        (0, 1), (0, 1),
        (0, 1), (0, 1)
    ]
    # Zero offset
    codeflash_output = _build_subplot_title_annotations(titles, domains, offset=0); result_zero = codeflash_output
    # Negative offset (top)
    codeflash_output = _build_subplot_title_annotations(titles, domains, offset=-5); result_neg = codeflash_output

def test_non_standard_domain_order():
    # Domains not in [0,1] or reversed
    titles = ["Rev"]
    domains = [(0.8, 0.2), (0.9, 0.4)]
    codeflash_output = _build_subplot_title_annotations(titles, domains); result = codeflash_output

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

def test_large_number_of_subplots_top():
    # 500 subplots, top edge
    n = 500
    titles = [f"Title {i}" for i in range(n)]
    # Each subplot has domain (i/n, (i+1)/n) for x and y
    domains = []
    for i in range(n):
        xdom = (i / n, (i + 1) / n)
        ydom = (i / n, (i + 1) / n)
        domains.extend([xdom, ydom])
    codeflash_output = _build_subplot_title_annotations(titles, domains); result = codeflash_output
    # Check a few random annotations for correct position
    for idx in [0, n//2, n-1]:
        annot = result[idx]
        xdom = (idx / n, (idx + 1) / n)
        ydom = (idx / n, (idx + 1) / n)

def test_large_number_of_subplots_left():
    # 200 subplots, left edge
    n = 200
    titles = [str(i) for i in range(n)]
    domains = []
    for i in range(n):
        xdom = (i / n, (i + 1) / n)
        ydom = (0, 1)
        domains.extend([xdom, ydom])
    codeflash_output = _build_subplot_title_annotations(titles, domains, title_edge="left", offset=1); result = codeflash_output

def test_large_number_of_subplots_with_some_missing_titles():
    # 100 subplots, but every 10th title is None
    n = 100
    titles = [None if i % 10 == 0 else f"T{i}" for i in range(n)]
    domains = []
    for i in range(n):
        xdom = (0, 1)
        ydom = (i/n, (i+1)/n)
        domains.extend([xdom, ydom])
    codeflash_output = _build_subplot_title_annotations(titles, domains); result = codeflash_output
    # None of the annotations should have text ending with "0"
    for a in result:
        pass

def test_large_number_of_domains_but_few_titles():
    # 1000 domains (500 subplots), only 10 titles
    n = 500
    titles = [f"T{i}" for i in range(10)]
    domains = []
    for i in range(n):
        xdom = (0, 1)
        ydom = (i/n, (i+1)/n)
        domains.extend([xdom, ydom])
    codeflash_output = _build_subplot_title_annotations(titles, domains); result = codeflash_output
    for i, a in enumerate(result):
        pass

def test_large_number_of_titles_but_few_domains():
    # 10 domains (5 subplots), 100 titles
    titles = [f"T{i}" for i in range(100)]
    domains = []
    for i in range(5):
        xdom = (0, 1)
        ydom = (i/5, (i+1)/5)
        domains.extend([xdom, ydom])
    codeflash_output = _build_subplot_title_annotations(titles, domains); result = codeflash_output
    for i, a in enumerate(result):
        pass
# 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 plotly._subplots import _build_subplot_title_annotations

# unit tests

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

def test_single_subplot_top():
    # Single subplot, top edge, no offset
    titles = ["A"]
    domains = [[0.0, 0.5], [0.0, 0.5]]
    codeflash_output = _build_subplot_title_annotations(titles, domains, "top", 0); result = codeflash_output
    annot = result[0]

def test_single_subplot_bottom_with_offset():
    # Single subplot, bottom edge, with offset
    titles = ["B"]
    domains = [[0.0, 1.0], [0.2, 0.8]]
    codeflash_output = _build_subplot_title_annotations(titles, domains, "bottom", 10); result = codeflash_output
    annot = result[0]

def test_single_subplot_right_with_offset():
    # Single subplot, right edge, with offset
    titles = ["C"]
    domains = [[0.1, 0.9], [0.3, 0.7]]
    codeflash_output = _build_subplot_title_annotations(titles, domains, "right", 5); result = codeflash_output
    annot = result[0]

def test_single_subplot_left_with_offset():
    # Single subplot, left edge, with offset
    titles = ["D"]
    domains = [[0.2, 0.4], [0.6, 0.9]]
    codeflash_output = _build_subplot_title_annotations(titles, domains, "left", 12); result = codeflash_output
    annot = result[0]

def test_multiple_subplots_top():
    # Two subplots, top edge, no offset
    titles = ["A", "B"]
    domains = [
        [0.0, 0.5], [0.0, 0.5],  # subplot 1
        [0.5, 1.0], [0.0, 0.5],  # subplot 2
    ]
    codeflash_output = _build_subplot_title_annotations(titles, domains, "top", 0); result = codeflash_output

def test_multiple_subplots_bottom():
    # Two subplots, bottom edge, offset
    titles = ["A", "B"]
    domains = [
        [0.0, 0.5], [0.5, 1.0],  # subplot 1
        [0.5, 1.0], [0.5, 1.0],  # subplot 2
    ]
    codeflash_output = _build_subplot_title_annotations(titles, domains, "bottom", 8); result = codeflash_output

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

def test_empty_titles_and_domains():
    # No subplots, should return empty list
    codeflash_output = _build_subplot_title_annotations([], [], "top", 0)

def test_titles_with_none_and_empty_strings():
    # Titles with None or empty string should be skipped
    titles = ["A", None, "", "B"]
    domains = [
        [0.0, 0.5], [0.0, 0.5],  # subplot 1
        [0.5, 1.0], [0.0, 0.5],  # subplot 2
        [0.0, 0.5], [0.5, 1.0],  # subplot 3
        [0.5, 1.0], [0.5, 1.0],  # subplot 4
    ]
    codeflash_output = _build_subplot_title_annotations(titles, domains, "top", 0); result = codeflash_output

def test_titles_longer_than_domains():
    # More titles than domains, extra titles should be ignored
    titles = ["A", "B", "C"]
    domains = [
        [0.0, 0.5], [0.0, 0.5],  # subplot 1
        [0.5, 1.0], [0.0, 0.5],  # subplot 2
    ]
    codeflash_output = _build_subplot_title_annotations(titles, domains, "top", 0); result = codeflash_output

def test_domains_longer_than_titles():
    # More domains than titles, extra domains should be ignored
    titles = ["A"]
    domains = [
        [0.0, 0.5], [0.0, 0.5],  # subplot 1
        [0.5, 1.0], [0.0, 0.5],  # subplot 2 (unused)
    ]
    codeflash_output = _build_subplot_title_annotations(titles, domains, "top", 0); result = codeflash_output

def test_invalid_title_edge_raises():
    # Invalid edge should raise ValueError
    with pytest.raises(ValueError):
        _build_subplot_title_annotations(["A"], [[0, 1], [0, 1]], "diagonal", 0)

def test_negative_offset():
    # Negative offset should be handled correctly
    titles = ["A"]
    domains = [[0, 1], [0, 1]]
    codeflash_output = _build_subplot_title_annotations(titles, domains, "top", -12); result = codeflash_output

def test_zero_length_domains():
    # Domains with zero length should not cause error
    titles = ["A"]
    domains = [[0.5, 0.5], [0.5, 0.5]]
    codeflash_output = _build_subplot_title_annotations(titles, domains, "top", 0); result = codeflash_output

def test_nonstandard_domain_order():
    # Domains where min > max (should still work)
    titles = ["A"]
    domains = [[0.8, 0.2], [0.7, 0.3]]
    codeflash_output = _build_subplot_title_annotations(titles, domains, "top", 0); result = codeflash_output

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

def test_large_number_of_subplots_top():
    # Test with 500 subplots (max allowed)
    n = 500
    titles = [f"Title {i}" for i in range(n)]
    # Each subplot domain is [i/n, (i+1)/n]
    domains = []
    for i in range(n):
        xdom = [i / n, (i + 1) / n]
        ydom = [0.0, 1.0]
        domains.extend([xdom, ydom])
    codeflash_output = _build_subplot_title_annotations(titles, domains, "top", 0); result = codeflash_output
    # Check a few random annotations for correct positioning
    for i in [0, n // 2, n - 1]:
        annot = result[i]
        expected_x = (i + 0.5) / n

def test_large_number_of_subplots_right_offset():
    # Test with 300 subplots, right edge, offset
    n = 300
    titles = [f"R{i}" for i in range(n)]
    domains = []
    for i in range(n):
        xdom = [0.0, 1.0]
        ydom = [i / n, (i + 1) / n]
        domains.extend([xdom, ydom])
    codeflash_output = _build_subplot_title_annotations(titles, domains, "right", 7); result = codeflash_output
    for i in [0, n // 3, n - 1]:
        annot = result[i]
        expected_y = (i + 0.5) / n

def test_large_number_of_subplots_with_some_empty_titles():
    # Test with 100 subplots, some titles empty/None
    n = 100
    titles = [f"T{i}" if i % 2 == 0 else "" for i in range(n)]
    domains = []
    for i in range(n):
        xdom = [0.0, 1.0]
        ydom = [i / n, (i + 1) / n]
        domains.extend([xdom, ydom])
    codeflash_output = _build_subplot_title_annotations(titles, domains, "left", 0); result = codeflash_output
    for i, annot in enumerate(result):
        # Only even indices should be present
        expected_index = i * 2
        expected_y = (expected_index + 0.5) / n

def test_large_offset_large_number_of_subplots():
    # Large offset, 50 subplots, check yshift/xshift
    n = 50
    offset = 100
    titles = [f"Off{i}" for i in range(n)]
    domains = []
    for i in range(n):
        xdom = [i / n, (i + 1) / n]
        ydom = [0.0, 1.0]
        domains.extend([xdom, ydom])
    codeflash_output = _build_subplot_title_annotations(titles, domains, "top", offset); result = codeflash_output
    for annot in result:
        pass

def test_large_scale_titles_shorter_than_domains():
    # 100 domains, only 10 titles
    titles = [f"Short{i}" for i in range(10)]
    n = 100
    domains = []
    for i in range(n):
        xdom = [i / n, (i + 1) / n]
        ydom = [0.0, 1.0]
        domains.extend([xdom, ydom])
    codeflash_output = _build_subplot_title_annotations(titles, domains, "bottom", 0); result = codeflash_output
    for i, annot in enumerate(result):
        pass
# 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-_build_subplot_title_annotations-mb2hnsn9 and push.

Codeflash

Here's a significantly faster and memory-optimized rewrite of your function. Key improvements come from.

- **Using list comprehensions and local variable caching** to minimize Python-level for-loop overhead and `sum` calls.
- **Single-pass computation**: Pre-compute the subtitle positions with optimized expressions by `title_edge` and avoid repeated function calls.
- **Zip iteration** when pairing x/y and titles for less index-tracking and memory churn.
- **Avoid constructing unnecessary lists** or intermediate variables.
- **Minimize attribute lookup and dictionary creation** for each annotation.
- **Avoid repeated len() calls and index checks** inside loops.

Optimization is focused on "hot paths" identified by the profiler (subtitle position computation and annotation dict creation).



**Summary of major changes**.

- **Move domain/position computation to list comprehensions** (much better performance for numeric processing).
- **Minimal annotation-dict construction** (everything is looked up or passed once).
- **`zip` and single loop** for linear memory and cpu use.
- **Checks for shift/angle outside loop**.
- **Only traverse up to min(len(subplot_titles), len(x_dom), len(y_dom))**; avoids out-of-bounds.
- **Reuses font dict** for all annotations to reduce per-dict allocation.

All logic and return structure are unchanged; only the implementation and inner loops are high-performance now.
@codeflash-ai codeflash-ai bot added the ⚡️ codeflash Optimization PR opened by Codeflash AI label May 24, 2025
@codeflash-ai codeflash-ai bot requested a review from misrasaurabh1 May 24, 2025 17:13
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