1

私は最近、Quantopian/Zipline の「Pipeline」APIで非常に奇妙なパターンを発見しました。それらにはCustomFactorクラスがありcompute()、独自の Factor モデルを実装するときにオーバーライドされるメソッドが見つかります。

の署名compute()は:def compute(self, today, assets, out, *inputs)で、パラメーター「out」について次のコメントがあります。

と同じ形状の出力配列assetscomputeは、目的の戻り値を に書き込む必要がありますout

関数が入力パラメーターに書き込むのではなく、単純に出力配列を返すことができない理由を尋ねたところ、次の答えが返ってきました。

「出力配列がcompute()によって返されることを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関数の結果を出力配列に取得するには、少なくとも 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

  1. ユーザーが返す ~64000 バイトの配列を割り当てます。
  2. ユーザーの計算されたデータのユーザーの出力配列へのコピー。
  3. ユーザーの出力配列から独自のより大きな配列へのコピー。

既存の API では、(1) と (3) のコストを回避できます。

そうは言っても、CustomFactors の動作方法に変更を加えたため、上記の最適化の一部が役に立たなくなりました。特に、computeその日にマスクされていないアセットのデータのみを に渡すようになりました。これには、 への呼び出しの前後に出力配列の部分的なコピーが必要computeです。

ただし、既存の API を優先する設計上の理由がいくつかあります。特に、出力の割り当てをエンジンに制御させることで、複数の出力要素の再配列のパスなどを簡単に実行できるようになります。

于 2016-05-24T15:50:20.340 に答える