Thanks to visit codestin.com
Credit goes to github.com

Skip to content

⚡️ Speed up function make_decreasing_ohlc by 7% #107

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

codeflash-ai[bot]
Copy link

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

📄 7% (0.07x) speedup for make_decreasing_ohlc in plotly/figure_factory/_ohlc.py

⏱️ Runtime : 1.74 milliseconds 1.63 milliseconds (best of 224 runs)

📝 Explanation and details

Here’s an optimized version of your code. The main bottleneck is utils.flatten(self.decrease_x) and utils.flatten(self.decrease_y), which uses Python list comprehension for flattening and is repeatedly called for every render. This can be replaced with a local, more efficient flatten method using itertools.chain.from_iterable, which is both faster and has lower memory overhead for a sequence of lists. Additionally, the string multiplication for hover text can be replaced with a more efficient allocation.

We also avoid re-initialization of temporary lists inside the class __init__, reduce attribute lookups, and use __slots__ for memory improvement.

Key optimized changes:

  • Use itertools.chain.from_iterable for flattening lists.
  • Avoid unnecessary variable allocations.
  • Use __slots__ in the class to improve attribute access speed.
  • Preallocate "text_decrease" as a list and only tuple at the end (less string overhead).
  • Avoid repeated lookups and minimize append calls inside loops.

Summary of optimizations:

  • Bulk flatten with itertools.chain.from_iterable instead of nested list comprehensions.
  • Only use min with a generator.
  • Use local lookups for hot loops.
  • Use __slots__ to speed up instance attribute management.
  • Reuse intermediate variables.
  • Branch prediction hints (short-circuit None first).

Let me know if you need Cython-level or further memory tuning!

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 🔘 None Found
🌀 Generated Regression Tests 28 Passed
⏪ Replay Tests 🔘 None Found
🔎 Concolic Coverage Tests 🔘 None Found
📊 Tests Coverage 100.0%
🌀 Generated Regression Tests Details
from datetime import datetime, timedelta

# imports
import pytest
from plotly.figure_factory._ohlc import make_decreasing_ohlc

# function to test (as provided)
# [Code omitted for brevity; assume make_decreasing_ohlc and dependencies are defined above]

# -------------------- UNIT TESTS BEGIN HERE --------------------

# 1. BASIC TEST CASES

def test_single_decreasing_ohlc():
    # Single bar, close < open (decreasing)
    open_ = [10]
    high = [12]
    low = [9]
    close = [8]
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output

def test_single_increasing_ohlc():
    # Single bar, close > open (should NOT be in decreasing trace)
    open_ = [10]
    high = [12]
    low = [9]
    close = [13]
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output

def test_mixed_bars_decreasing_and_increasing():
    # Multiple bars, some increasing, some decreasing
    open_ = [10, 10, 10]
    high = [12, 13, 11]
    low = [9, 8, 9]
    close = [8, 11, 10]  # 8<10 (decr), 11>10 (incr), 10==10 (decr)
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output

def test_dates_input_decreasing():
    # Test with datetime x-axis
    dt0 = datetime(2020, 1, 1)
    dates = [dt0, dt0 + timedelta(days=1), dt0 + timedelta(days=2)]
    open_ = [10, 10, 10]
    high = [12, 11, 13]
    low = [8, 9, 9]
    close = [8, 11, 9]  # decr, incr, decr
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output

def test_kwargs_override_defaults():
    # Test that kwargs override defaults
    open_ = [10]
    high = [12]
    low = [9]
    close = [8]
    dates = None
    custom_line = {"color": "blue", "width": 3}
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates, line=custom_line, name="Down", showlegend=True); result = codeflash_output

# 2. EDGE TEST CASES

def test_all_increasing_bars():
    # All bars increasing: output should have empty x/y
    open_ = [1, 2, 3]
    high = [2, 3, 4]
    low = [0, 1, 2]
    close = [3, 4, 5]
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output

def test_all_decreasing_bars():
    # All bars decreasing: output should have all bars
    open_ = [5, 5, 5]
    high = [6, 7, 8]
    low = [4, 3, 2]
    close = [4, 3, 2]
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output

def test_all_equal_open_close():
    # All bars with open == close (should be decreasing)
    open_ = [5, 7, 9]
    high = [6, 8, 10]
    low = [4, 6, 8]
    close = [5, 7, 9]
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output

def test_empty_input_lists():
    # All lists empty: should return empty x/y/text
    open_ = []
    high = []
    low = []
    close = []
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output

def test_none_in_close_list():
    # Some close values are None: should skip those bars
    open_ = [10, 11, 12]
    high = [12, 13, 14]
    low = [9, 10, 11]
    close = [8, None, 10]  # Only bars 0 and 2 are valid
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output


def test_dates_with_irregular_intervals():
    # Dates with irregular intervals should still work
    dt0 = datetime(2021, 1, 1)
    dates = [dt0, dt0 + timedelta(days=2), dt0 + timedelta(days=5)]
    open_ = [10, 9, 12]
    high = [11, 10, 13]
    low = [9, 8, 11]
    close = [8, 7, 12]  # All decreasing
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output


def test_large_number_of_bars_mixed():
    # 1000 bars, half decreasing, half increasing
    n = 1000
    open_ = [i for i in range(n)]
    high = [i + 2 for i in range(n)]
    low = [i - 2 for i in range(n)]
    close = [i - 1 if i % 2 == 0 else i + 1 for i in range(n)]  # even: decr, odd: incr
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output

def test_large_number_of_bars_all_decreasing_with_dates():
    # 1000 bars, all decreasing, with datetime x-axis
    n = 1000
    dt0 = datetime(2022, 1, 1)
    dates = [dt0 + timedelta(days=i) for i in range(n)]
    open_ = [1000 - i for i in range(n)]
    high = [1001 - i for i in range(n)]
    low = [999 - i for i in range(n)]
    close = [999 - i for i in range(n)]
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output

def test_large_number_of_bars_all_increasing():
    # 1000 bars, all increasing
    n = 1000
    open_ = [i for i in range(n)]
    high = [i + 3 for i in range(n)]
    low = [i - 3 for i in range(n)]
    close = [i + 1 for i in range(n)]
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.

import datetime

# imports
import pytest
from plotly import exceptions
# function to test (as provided)
from plotly.figure_factory import utils
from plotly.figure_factory._ohlc import make_decreasing_ohlc

_DEFAULT_DECREASING_COLOR = "#FF4136"
from plotly.figure_factory._ohlc import make_decreasing_ohlc

# ---------------------
# Unit tests start here
# ---------------------

# 1. Basic Test Cases

def test_decreasing_basic_no_dates():
    # One decreasing bar, no dates
    open_ = [10]
    high = [12]
    low = [9]
    close = [8]
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output


def test_decreasing_and_increasing_mixed():
    # Two bars: one decreasing, one increasing
    open_ = [10, 5]
    high = [12, 8]
    low = [9, 4]
    close = [8, 7]  # first is decreasing, second is increasing
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output

def test_decreasing_equal_open_close():
    # Bar where open == close (should be decreasing)
    open_ = [10]
    high = [12]
    low = [9]
    close = [10]
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output

def test_decreasing_multiple_bars():
    # Three bars: two decreasing, one increasing
    open_ = [10, 5, 8]
    high = [12, 8, 10]
    low = [9, 4, 7]
    close = [8, 7, 10]  # first and third: decreasing, increasing, increasing
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output

# 2. Edge Test Cases

def test_empty_lists():
    # All lists empty
    open_ = []
    high = []
    low = []
    close = []
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output

def test_none_in_close():
    # close contains None, should be skipped
    open_ = [10, 5]
    high = [12, 8]
    low = [9, 4]
    close = [None, 4]
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output

def test_all_increasing():
    # All bars are increasing, so decreasing trace should be empty
    open_ = [1, 2, 3]
    high = [2, 3, 4]
    low = [0, 1, 2]
    close = [2, 3, 4]
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output

def test_all_decreasing():
    # All bars are decreasing
    open_ = [5, 6, 7]
    high = [6, 7, 8]
    low = [4, 5, 6]
    close = [4, 5, 6]
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output

def test_dates_with_irregular_intervals():
    # Dates with irregular intervals
    dt1 = datetime.datetime(2024, 1, 1)
    dt2 = datetime.datetime(2024, 1, 3)
    dt3 = datetime.datetime(2024, 1, 10)
    open_ = [10, 8, 7]
    high = [12, 10, 9]
    low = [9, 7, 6]
    close = [8, 7, 6]  # all decreasing
    dates = [dt1, dt2, dt3]
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output

def test_invalid_input_length():
    # Lists of different lengths should raise an error
    open_ = [10, 8]
    high = [12]
    low = [9, 7]
    close = [8, 7]
    dates = None
    with pytest.raises(IndexError):
        make_decreasing_ohlc(open_, high, low, close, dates)


def test_kwargs_override():
    # Test that kwargs override defaults
    open_ = [10]
    high = [12]
    low = [9]
    close = [8]
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates, line=dict(color="blue", width=3), name="Down"); result = codeflash_output

# 3. Large Scale Test Cases

def test_large_input_all_decreasing():
    # Large input, all decreasing
    N = 1000
    open_ = list(range(1000, 0, -1))
    high = [o + 2 for o in open_]
    low = [o - 2 for o in open_]
    close = [o - 1 for o in open_]
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output

def test_large_input_mixed():
    # Large input, half increasing, half decreasing
    N = 1000
    open_ = [i for i in range(N)]
    high = [o + 2 for o in open_]
    low = [o - 2 for o in open_]
    # First half decreasing, second half increasing
    close = [o - 1 if i < N//2 else o + 1 for i, o in enumerate(open_)]
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output
    # Check a sample bar
    idx = 10
    y_start = 7 * idx

def test_large_input_with_dates():
    # Large input with dates
    N = 500
    base = datetime.datetime(2024, 1, 1)
    dates = [base + datetime.timedelta(days=i) for i in range(N)]
    open_ = [i for i in range(N)]
    high = [o + 2 for o in open_]
    low = [o - 2 for o in open_]
    close = [o - 1 for o in open_]
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output
# 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_decreasing_ohlc-mb2brzz7 and push.

Codeflash

Here’s an optimized version of your code. The **main bottleneck** is `utils.flatten(self.decrease_x)` and `utils.flatten(self.decrease_y)`, which uses Python list comprehension for flattening and is repeatedly called for every render. This can be replaced with a local, more efficient flatten method using `itertools.chain.from_iterable`, which is both faster and has lower memory overhead for a sequence of lists. Additionally, the string multiplication for hover text can be replaced with a more efficient allocation.

We also avoid re-initialization of temporary lists inside the class `__init__`, reduce attribute lookups, and use `__slots__` for memory improvement.

**Key optimized changes:**

- Use `itertools.chain.from_iterable` for flattening lists.
- Avoid unnecessary variable allocations.
- Use `__slots__` in the class to improve attribute access speed.
- Preallocate `"text_decrease"` as a list and only tuple at the end (less string overhead).
- Avoid repeated lookups and minimize append calls inside loops.




**Summary of optimizations:**
- Bulk flatten with `itertools.chain.from_iterable` instead of nested list comprehensions.
- Only use `min` with a generator.
- Use local lookups for hot loops.
- Use `__slots__` to speed up instance attribute management.
- Reuse intermediate variables.
- Branch prediction hints (short-circuit None first).

Let me know if you need Cython-level or further memory tuning!
@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 14:28
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