1

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

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

与 形状相同的输出数组assetscompute应将其所需的返回值写入out.

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

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

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

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

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

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

1 回答 1

2

(我在这里设计并实现了有问题的 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。特别是,让引擎控制输出分配使我们更容易做一些事情,比如为多输出因子传递重新数组。

于 2016-05-24T15:50:20.340 回答