(我在这里设计并实现了相关 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 支付至少以下费用
- 分配用户将返回的 ~64000 字节数组。
- 将用户的计算数据复制到用户的输出数组中。
- 将用户输出数组的副本复制到我们自己的更大数组中。
使用现有 API,我们避免了成本 (1) 和 (3)。
话虽如此,我们已经对 CustomFactors 的工作方式进行了更改,从而降低了上述一些优化的用处。特别是,我们现在只将当天未被屏蔽的资产的数据传递给 compute,这需要在调用 compute 之前和之后的输出数组的部分副本。
尽管如此,仍然有一些设计原因更喜欢现有的 API。特别是,让引擎控制输出分配让我们更容易执行诸如通过recarrays 获取多输出因素的操作。