【问题标题】:Quantopian / Zipline: weird pattern in Pipeline packageQuantopian / Zipline:管道包中的奇怪模式
【发布时间】:2016-09-21 05:16:38
【问题描述】:

我最近在"Pipeline" API from Quantopian/Zipline 中发现了一个非常奇怪的模式:它们有一个CustomFactor 类,在实现您自己的Factor 模型时,您会在其中找到一个要被覆盖的compute() 方法。

compute()的签名是:def compute(self, today, assets, out, *inputs),参数“out”的注释如下:

assets 形状相同的输出数组。 compute 应将其所需的返回值写入out

当我问为什么函数不能简单地返回一个输出数组而不是写入一个输入参数时,我得到了以下答案:

“如果 API 要求输出数组由 compute() 返回,我们最终会将数组复制到实际的输出缓冲区中,这意味着会产生不必要的额外副本。”

我不明白为什么他们最终会这样做......显然在 Python 中没有关于按值传递的问题,也没有不必要地复制数据的风险。这真的很痛苦,因为这是他们推荐人们编写代码的那种实现:

    def compute(self, today, assets, out, data):
       out[:] = data[-1]

所以我的问题是,为什么不能简单地:

    def compute(self, today, assets, data):
       return data[-1]

【问题讨论】:

    标签: python arrays parameter-passing pass-by-reference zipline


    【解决方案1】:

    (我在这里设计并实现了相关 API。)

    你说得对,Python 对象在传入和传出函数时不会被复制。从您的 CustomFactor 返回一行和将值写入提供的数组之间存在差异的原因与将在调用您的 CustomFactor 计算方法的代码中生成的副本有关。

    最初设计 CustomFactor API 时,调用计算方法的代码大致如下所示:

    def _compute(self, windows, dates, assets):
        # `windows` here is list of iterators yielding 2D slices of 
        # the user's requested inputs
    
        # `dates` and `assets` are row/column labels for the final output.
    
        # Allocate a (dates x assets) output array.
        # Each invocation of the user's `compute` function
        # corresponds to one row of output.
        output = allocate_output()
    
        for i in range(len(dates)):
    
            # Grab the next set of input arrays.
            inputs = [next(w) for w in windows]
    
            # Call the user's compute, which is responsible for writing
            # values into `out`.
            self.compute(
                dates[i], 
                assets,
                # This index is a non-copying operation.
                # It creates a view into row `i` of `output`.
                output[i],
                *inputs  # Unpack all the inputs.
            )
    
        return output
    

    这里的基本思想是,我们已经预先获取了大量数据,现在我们将在窗口中循环访问该数据,在数据上调用用户的计算函数,并将结果写入一个预分配的输出数组,然后将其传递给进一步的转换。

    无论我们做什么,我们都必须付出至少一份副本的成本,才能将用户的compute函数的结果放入输出数组。

    正如您所指出的,最明显的 API 是让用户简单地返回输出行,在这种情况下,调用代码如下所示:

    # Get the result row from the user.
    result_row = self.compute(dates[i], assets, *inputs)
    # Copy the user's result into our output buffer.
    output[i] = result_row
    

    如果那是 API,那么我们必须为每次调用用户的 compute 支付至少以下费用

    1. 分配用户将返回的 ~64000 字节数组。
    2. 将用户的计算数据复制到用户的输出数组中。
    3. 将用户输出数组的副本复制到我们自己的更大数组中。

    使用现有 API,我们避免了成本 (1) 和 (3)。

    话虽如此,我们已经对 CustomFactors 的工作方式进行了更改,从而降低了上述一些优化的用处。特别是,我们现在只将当天未被屏蔽的资产的数据传递给 compute,这需要在调用 compute 之前和之后的输出数组的部分副本。

    尽管如此,仍然有一些设计原因更喜欢现有的 API。特别是,让引擎控制输出分配让我们更容易执行诸如通过recarrays 获取多输出因素的操作。

    【讨论】:

      猜你喜欢
      • 2019-02-03
      • 2019-08-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-01
      • 2019-09-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多