0

私はTomorrowというライブラリを使用していますが、Async関数の呼び出しを可能にするために標準ライブラリのThreadPoolExecutorを使用しています。なぜ、1人のワーカーを持つThreadPoolExecutorは通常の実行よりも高速ですか?

デコレータを呼び出す@tomorrow.threads(1)は、1人の作業者でThreadPoolExecutorをスピンアップします。

質問

  • なぜそれが(例えば通常)であるとして、それを呼び出す上1 thread workerを使用して機能を実行する方が速いですか?
  • 10 thread workersと同じコードを1の代わりに実行するのが遅いのはなぜですか?

デモコード

輸入は

def openSync(path: str): 
    for row in open(path): 
     for _ in row: 
      pass 

@tomorrow.threads(1) 
def openAsync1(path: str): 
    openSync(path) 

@tomorrow.threads(10) 
def openAsync10(path: str): 
    openSync(path) 

def openAll(paths: list): 
    def do(func: callable)->float: 
     t = time.time() 
     [func(p) for p in paths] 
     t = time.time() - t 
     return t 
    print(do(openSync)) 
    print(do(openAsync1)) 
    print(do(openAsync10)) 

openAll(glob.glob("data/*")) 

注意を除外:dataフォルダには18個のファイル、ランダムなテキストの各700行が含まれています。

出力

0労働者: 0.0120
1労働者: 0.0009
10労働者: 0.0535 私がきたもの

テストしました

  • 私は複数のdusin回以上、別のプログラムをバックグラウンドで実行していました(昨日、そして今日カップルを走らせました)。数字はofcに変わりますが、順序は常に同じです。 (すなわち、1が最も速く、次いで0が10)。
  • 私はまた、要因としてキャッシングを排除するために、実行順序を変更してみました。
    • は、他のすべての順列と比較(1 0、次いで、その後、10最速で)異なる順序に、1、順10None結果を実行することが判明しました。結果は、doの呼び出しが最後に実行されたときは、最初に実行されたか、その代わりに途中で実行されたよりもかなり遅いことを示します。 (@Dunesから溶液を受信した後)

結果

0労働者: 0.0122
1ワーカー: 0。0214
10労働者: 0.0296

+0

正確なパフォーマンス統計に近いものは、サンプルサイズが小さすぎます。また、ファイル操作などのテストでは、(オペレーティングシステム内の)キャッシングの影響を考慮する必要があります。 –

+0

@DarkFalcon私は、複数のdusin回以上のコードを実行しました。さまざまなプログラムがバックグラウンドで実行されています(昨日、そして今日カップルを走らせました)。数字はofcに変わりますが、順序は常に同じです。 私はまた、実行の順番を入れ替えようとしました。 'do'コールを動かす。まだ同じ。 – Olian04

+1

テストが正しく表示されません。つまり、非同期バリアントは、テストを終了する前にすべての作業が終了するまで待つ必要はありません。つまり、すべてのワーカースレッドをセットアップするのにかかる時間と、スレッドが作業を完了するまでに要する時間ではありません。 – Dunes

答えて

1

あなたは非同期関数の1つを呼び出すと、それは「先物」のオブジェクト(この場合はtomorrow.Tomorrowのインスタンス)を返します。これにより、完了するまで待つことなく、すべてのジョブを提出することができます。しかし、実際には仕事が終わるのを待つことは決してありません。したがって、すべてdo(openAsync1)は、すべてのジョブをセットアップするのにかかる時間です(非常に速くなければなりません)。一般的に物事を遅くするPythonで追加のスレッドを使用して

def openAll(paths: list): 
    def do(func: callable)->float: 
     t = time.time() 
     # do all jobs if openSync, else start all jobs if openAsync 
     results = [func(p) for p in paths] 
     # if openAsync, the following waits until all jobs are finished 
     if func is not openSync: 
      for r in results: 
       r._wait() 
     t = time.time() - t 
     return t 
    print(do(openSync)) 
    print(do(openAsync1)) 
    print(do(openAsync10)) 

openAll(glob.glob("data/*")) 

:より正確なテストのためにあなたのような何かをする必要があります。これは、グローバルインタプリタロックのため、CPUが持つコアの数にかかわらず、ただ1つのスレッドしかアクティブにならないことがあるためです。

しかし、あなたのジョブがIOバウンドであるという事実は複雑です。より多くのワーカースレッドかもしれないスピードアップ。これは、単一のスレッドが、マルチスレッドのバリアント内のさまざまなスレッド間のコンテキスト切り替えの間に失われるよりも、ハードドライブが応答するのを待つのに多くの時間を費やす可能性があるからです。

サイドノートでは、さえopenAsync1およびジョブが完了するまで待機openAsync10でもないのに、do(openAsync10)は、新しいジョブを送信する際には、スレッド間のより多くの同期を必要とするため、おそらく遅くなります。

+0

ありがとう!意図したとおりに作業する(そして、Pythonでasyncについて新しいことを学んだ):) – Olian04

関連する問題