1

私は dask 遅延関数を使用しており@dask.delayed、関数でデコレータを使用する際のすべきこととすべきでないことに慣れてきました。compute()ベスト プラクティスに従っていると思っていたにもかかわらず、結果を得るために 2 回電話する必要がある場合があることに気付きました。つまり、別の dask 遅延関数内で dask 遅延関数を呼び出さないでください。

この問題は 2 つのシナリオで発生しました。ネストされた関数がある場合と、遅延オブジェクトであるクラス メンバーを使用するクラスでメンバー関数を呼び出す場合です。

@dask.delayed
def add(a, b):
    return  a + b

def inc(a):
    return add(a, 1)

@dask.delayed
def foo(x):
    return inc(x)

x = foo(3)
x.compute()
class Add():
    def __init__(self, a, b):
        self.a = a
        self.b = b

    @dask.delayed
    def calc(self):
        return self.a+self.b

a = dask.delayed(1)
b = dask.delayed(2)
add = Add(a, b)
add.calc().compute()

最初の例でx.compute()は、結果は返されませんが、別の遅延オブジェクトが返さx.compute().compute()れるため、実際の結果を取得するには呼び出す必要があります。しかし、 inc は遅延関数ではないため、別の遅延関数内で遅延関数を呼び出さないという規則に違反していないと思いますか?

add.calc().compute().compute()2 番目の例でも、実際の結果を取得するために呼び出す必要があります。この場合self.a、 とself.bは単なる遅延属性であり、ネストされた遅延関数はどこにもありません。

compute()これら2つのケースで2回電話する必要がある理由を誰かが理解するのを手伝ってくれますか? またはさらに良いことに、dask遅延機能を使用するときの一般的な「ルール」を誰かが簡単に説明できますか? 私はドキュメントを読みましたが、それほど多くはありません。

compute()更新: @malbertは、遅延関数に関連する遅延結果があり、「別の遅延関数内での遅延関数の呼び出し」としてカウントされるため、例では 2 回呼び出す必要があると指摘しました。しかし、なぜ次のようなものは一度だけ呼び出す必要があるのcompute()でしょうか?

@dask.delayed
def add(a,b):
    return a+b

a = dask.delayed(1)
b = dask.delayed(2)
c = add(a,b)
c.compute()

この例では、abも両方とも遅延結果であり、遅延関数で使用されています。私のランダムな推測は、実際に重要なのは、遅延された結果が遅延関数のどこにあるかということですか? それらが引数として渡された場合にのみ、おそらく問題ありませんか?

4

1 に答える 1

1

鍵は、何が何をするのかをより正確に理解することにあると思いますdask.delayed

検討

my_delayed_function = dask.delayed(my_function)

のデコレータとして使用すると、 の実行を遅らせる関数をmy_function返しdask.delayedます。が引数付きで呼び出された場合my_delayed_functionmy_functionmy_delayed_function

delayed_result = my_delayed_function(arg)

my_functionこれは、引数 を使用したの実行に関するすべての必要な情報を含むオブジェクトを返しますarg

通話中

result = delayed_result.compute()

関数の実行をトリガーします。

ここで、2 つの遅延結果に対してなどの演算子を使用する効果は+、入力に含まれる 2 つの実行をバンドルする新しい遅延結果が返されることです。このオブジェクトを呼び出すcomputeと、この一連の実行がトリガーされます。


ここまでは順調ですね。さて、あなたの最初の例では、遅延した結果を返す遅延関数を呼び出しますfooincしたがって、コンピューティングfooはまさにこれを行い、この遅延した結果を返します。この遅延した結果 (「2 番目の」計算) を呼び出すcomputeと、その計算がトリガーされます。

2番目の例では、a結果bが遅れています。を使用して 2 つの遅延結果を加算すると+、 の実行を束ねた遅延結果と、それらの加算が返されaますb。現在、calcは遅延関数であるため、遅延結果を取得すると遅延結果を返します。したがって、その計算は遅延オブジェクトを返します。

どちらの場合も、ベスト プラクティスに完全に従っていませんでした。具体的にポイント

遅延関数内での遅延呼び出しを避ける

最初の例では、遅延addが内incで呼び出され、内で呼び出されるためfooです。したがって、遅延内で遅延を呼び出していますfoo。2 番目の例では、delayedcalcがdelayed aandbで動作しているため、delayed 関数内でdelayed を呼び出しています。

あなたの質問では、あなたは言います

しかし、 inc は遅延関数ではないため、別の遅延関数内で遅延関数を呼び出さないという規則に違反していないと思いますか?

「遅延関数内での遅延呼び出し」を間違って理解しているのではないかと思います。これは、関数内で発生するすべてのことを指し、したがってその一部です。delayedincの呼び出しが含まれているaddため、delayed が で呼び出されていfooます。

質問更新後の追加: 遅延引数を遅延関数に渡すと、遅延実行が新しい遅延結果にバンドルされます。これは「遅延関数内での遅延呼び出し」とは異なり、意図したユース ケースの一部です。実際、ドキュメントでもこれに関する明確な説明は見つかりませんでしたが、1つのエントリポイントはこれかもしれません:遅延unpack_collections引数を処理するために使用されます。これが多少不明確なままであっても、(このように解釈された) ベスト プラクティスに固執することで、 の出力に関して再現可能な動作が生成されるはずですcompute()

次のコードは、「遅延関数内での遅延呼び出しを回避する」に固執した場合の結果であり、 の 1 回の呼び出しの後に結果を返しますcompute

最初の例:

#@dask.delayed
def add(a, b):
    return  a + b

def inc(a):
    return add(a, 1)

@dask.delayed
def foo(x):
    return inc(x)

x = foo(3)
x.compute()

2 番目の例:

class Add():
    def __init__(self, a, b):
        self.a = a
        self.b = b

    #@dask.delayed
    def calc(self):
        return self.a+self.b

a = dask.delayed(1)
b = dask.delayed(2)
add = Add(a, b)
add.calc().compute()
于 2019-07-10T17:29:31.670 に答える