使用从循环内的代码片段中提取的函数避免代码冗余/计算开销

Posted

技术标签:

【中文标题】使用从循环内的代码片段中提取的函数避免代码冗余/计算开销【英文标题】:Avoid code redundancy/computational overhead with function extracted from a code snippet inside a loop 【发布时间】:2021-05-12 01:27:18 【问题描述】:

我有一个循环,用于循环金融时间序列的天数和离散时间段,我的策略的一些代码位于其中。我将这个内部代码提取到一个独立的函数中,以便能够在特定情况下使用它进行测试,后来才发现这个操作大大增加了这个循环的总计算时间。这是有道理的,长函数签名、调用、(堆栈内存?)所有这些都必须引入一些开销。

# Fast, but cannot use my strategy for specific instances:
for d in data:
    # my strategy code

# Flexible but really slow
def my_strategy(**params):
    # my strategy code
f = partial(my_strategy, **params)
for d in data:
    f(d)

现在,我的问题是:有没有办法在没有显式冗余的情况下保留两个设置、独立函数和带有循环和实际代码的设置?我需要两者兼得。

我一直在尝试使用 inspect.getsource(my_function) 并包装外部函数的副本,然后使用 eval 但 5 分钟后我觉得自己像个白痴,感觉一点也不对。

解决此问题的最佳做法是什么?如果我复制代码,我必须确保如果我修改一个版本,另一个版本总是同步的。我不喜欢这个。我想有效地镜像它。

您可能已经想到,一种选择可能是:将代码保留在循环内部,并用数组包装您的特定情况,并在需要时将其提供给循环。好吧,我不能这样做,主要是因为我需要对内部代码执行进行定时控制(将回测策略移植到实时)。在这里,我只是在寻找一些快速的技巧来优雅地镜像代码,如果有的话。

--- 编辑 ---

好吧,为了进一步测试,我实现了我提到的那个可怕的 hack,并让它与 exec 一起工作。多亏了这一点,我现在可以保证使用的代码完全相同。观察到的时间差异是相同的。包含代码的循环所用时间大约是函数调用循环所用时间的一半。

# horrible hack, please forgive me
def extract_strategy_function():
    # Start
    source_code = inspect.getsource(fastest_compute_trades)
    # Cutting the code snippet
    source_code = source_code.split('[safe_start - 1:]:', 1)[1].rsplit("if s['pos_open']:", 1)[0]
    # Needs re-indentation so the parser doesn't complain
    source_code = reindent(source_code, -2)
    function_name = 'single_apply_strategy'  # internal, no-one will see this anyway
    function_string = f"""
    def function_name(
            # INDICATORS (non mutable)
            i_time, close_price, r2, ma1, ma2, rsi, upper, lower,
            # STATE VARIABLES, TX HISTORY (mutable, 2 dictionaries and 1 list of dicts)
            s, gs, transactions,
            # CONSTANT PARAMETERS (non mutable)
            min_r2, macd_threshold, rsi_bound, waiting_slots, stop_loss,
            take_profit, trail_profit, double_down, dd_qty, commission_fee,
            # Day ID, useful when back-testing/grid-searching, left at the end so it can default to 0
            i_day=0
    ):
    source_code
    """.strip()
    # Evaluate it
    exec(function_string)  # Now in locals
    return locals()[function_name]
    
def slow(...):
    
    ...
    
    apply_strategy = partial(extract_strategy_function(), 
        **params, commission_fee=commission_fee, rsi_bound=rsi_bound)
   
    for i_day, day in enumerate(data):
        for i_time, close_price, r2, ma1, ma2, rsi, upper, lower, in list(enumerate(day))[safe_start - 1:]:
            apply_strategy(i_time, close_price, r2, ma1, ma2, rsi, upper, lower, 
                day_state, global_state, transactions, i_day=i_day)

        if day_state['pos_open']:
            ...
    
    ...
    
def fast(..., 
    #some extra parameters are now unpacked here, 
    #since all code is here they are now needed here
):
    ...
    
    for i_day, day in enumerate(data):
        for i_time, close_price, r2, ma1, ma2, rsi, upper, lower, in list(enumerate(day))[safe_start - 1:]:
            # actual code contained in single_apply_strategy written here, the whole strategy (one timestep)

        if day_state['pos_open']:
            ...
            
    ...

有什么建议吗?

附言不用说,两种设置的输出是完全一样的

【问题讨论】:

请发布您的代码 - 阅读 How to Ask 并提供 minimal reproducible example。 这对任何代码都有效,我说的是一般 【参考方案1】:

函数调用引起的任何开销很可能可以忽略不计。使用您的函数,并在需要时通过“手动内联”您的函数来衡量可能的改进。

如果你需要这样的性能,Python 可能不是最好的选择,在这种情况下我会推荐 C++。您需要一种语言功能,称为内联函数:https://www.geeksforgeeks.org/inline-functions-cpp/

有关 Python 和内联的更多信息:Python equivalence to inline functions or macros

无论如何,我认为你在这个方向上的努力不会有任何改善。

【讨论】:

好吧,为了进一步测试,我实现了我提到的那个可怕的 hack,并使它与 exec 一起工作。多亏了这一点,我现在可以保证使用的代码完全相同。观察到的时间差异是相同的。包含代码的循环所用时间大约是函数调用循环所用时间的一半。这怎么可能?这可能与长签名有关。很多可变和非可变参数。还是谢谢你的回答,我现在去查一下 @quan2m 这个有意思,可以分享一下签名吗? shareablecode.com/snippets/war-against-redundancy-ydRS-9GDp 这行得通吗?或者,请告诉我一个更好的分享方式。如果您需要其他任何东西,请询问 我编辑了这篇文章并在此处添加了它,并进行了一些更正。@Roman @quan2m 也许是import dis,然后dis.dis(slow)dis.dis(fast) 给出了一些见解。

以上是关于使用从循环内的代码片段中提取的函数避免代码冗余/计算开销的主要内容,如果未能解决你的问题,请参考以下文章

12_关于flask中的宏

如何避免循环内的数据丢失

PHP项目开发经验整理

避免 Python 中的冗余循环

如何通过单击片段内的线性布局从片段类开始新活动?下面是我的代码,但这不起作用

Flask之模板之宏继承包含