(ここで問題の 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
関数の結果を出力配列に取得するには、少なくとも 1 つのコピーのコストを支払う必要があります。
ご指摘のとおり、最も明白な 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 を優先する設計上の理由がいくつかあります。特に、出力の割り当てをエンジンに制御させることで、複数の出力要素の再配列のパスなどを簡単に実行できるようになります。