11

CPU を集中的に使用するタスクを実行する場合、コアごとに 1 つのスレッドを使用するのが最適だと思います。4 コアの CPU を使用している場合、CPU を集中的に使用するサブルーチンの 4 つのインスタンスをペナルティなしで実行できます。たとえば、4 コア CPU で CPU 集中型アルゴリズムの 4 つのインスタンスを実験的に実行したことがあります。プロセスあたりの時間は最大 4 倍減少しませんでした。5 回目のインスタンスでは、すべてのインスタンスに時間がかかりました。

操作をブロックする場合はどうなりますか? 1,000 個の URL のリストがあるとします。私は次のことを行ってきました:

(構文エラーは気にしないでください。私はこれをモックアップしました)

my @threads;
foreach my $url (@urlList) {    
     push @threads, async {
         my $response = $ua->get($url);
         return $response->content;   
     }
}

foreach my $thread (@threads) {
    my $response = $thread->join;
    do_stuff($response); 
}

基本的に、URL リストにある URL と同じ数のスレッドを開始しています。100 万の URL がある場合、100 万のスレッドが開始されます。最適なスレッド数ではない場合、これは最適ですか? スレッドを使用することは、待機できるブロッキング I/O 操作 (ファイルの読み取り、データベースクエリなど) に適していますか?

関連ボーナス質問

好奇心から、Perl スレッドは Python と同じように動作し、それは GIL ですか? Python でマルチスレッドの利点を得て、CPU 集中型のタスクにすべてのコアを利用するには、マルチプロセッシングを使用する必要があります。

4

4 に答える 4

13

好奇心から、Perl スレッドは Python と同じように動作し、それは GIL ですか? Python でマルチスレッドの利点を得て、CPU 集中型のタスクにすべてのコアを利用するには、マルチプロセッシングを使用する必要があります。

いいえ、しかし結論は同じです。Perlには、スレッド間でインタープリターを保護する大きなロックがありません。代わりに、異なるスレッドごとに重複したインタープリターがあります。変数は 1 つのインタープリター (および 1 つのインタープリターのみ) に属しているため、デフォルトではスレッド間でデータは共有されません。変数が明示的に共有されると、他のスレッドに代わって共有変数へのすべてのアクセスをシリアル化する共有インタープリターに配置されます。ここで他の人が言及したメモリの問題に加えて、Perl のスレッドにはいくつかの深刻なパフォーマンスの問題があり、共有できるデータの種類とそのデータでできることの制限もあります (詳細については、perlthrtutを参照してください) 。 .

要するに、多くの IO を並列化する必要があり、それをノンブロッキングにすることができれば、スレッドよりもイベント ループ モデルからはるかに多くのパフォーマンスが得られるということです。ノンブロッキングにできないものを並列化する必要がある場合は、おそらく perl スレッドよりもマルチプロセスの方がうまくいくでしょう (そして、その種のコードに慣れると、デバッグ)。

2 つのモデルを組み合わせることも可能です (たとえば、POE::Wheel::RunまたはAnyEvent::Runを使用して特定の高価な作業を子プロセスに渡す、ほとんどが単一プロセスのイベント アプリ、または複数プロセスのアプリ)。イベント化されていない子を管理するイベント化された親、またはFDを子に渡すだけの親を持つ、事前にフォークされたイベント化されたWebサーバーが多数あるノードクラスタータイプのセットアップがありますaccept)。

ただし、少なくともまだ、特効薬はありません。

于 2013-06-24T15:29:23.607 に答える
3

コードを見てみましょう。私はそれに3つの問題があります:

  1. まず簡単なもの:->contentの代わりに を使用し->decoded_content(charset => 'none')ます。

    ->content生の HTML 応答本文を返します。これは、ヘッダーに情報がなければデコードするのに役に立ちません (たとえば、gzip されている可能性があります)。時々動作します。

    ->decoded_content(charset => 'none')実際の応答を提供します。それは常に動作します。

  2. 要求が行われた順序で応答を処理します。つまり、応答が処理されるのを待っている間、ブロックされる可能性があります。

    最も簡単な解決策は、応答をThread::Queue::Anyオブジェクトに配置することです。

    use Thread::Queue::Any qw( );
    
    my $q = Thread::Queue::Any->new();
    
    my $requests = 0;
    for my $url (@urls) {
       ++$requests;
       async {
          ...
          $q->enqueue($response);
       };
    }
    
    while ($requests && my $response = $q->dequeue()) {
       --$requests;
       $_->join for threads->list(threads::joinable);
       ...
    }
    
    $_->join for threads->list();
    
  3. 一度しか使用されないスレッドを多数作成します。

    このアプローチには、かなりの量のオーバーヘッドがあります。一般的なマルチスレッド プラクティスは、永続的なワーカー スレッドのプールを作成することです。これらのワーカーは、必要な仕事をすべて実行し、終了するのではなく次の仕事に進みます。できるだけ早くジョブを開始できるように、特定のスレッドではなくプールにジョブを送信します。これにより、スレッド作成のオーバーヘッドがなくなるだけでなく、一度に実行されるスレッドの数を制御できます。これは、CPU バウンドのタスクに最適です。

    ただし、スレッドを使用して非同期 IO を実行しているため、ニーズは異なります。スレッド作成の CPU オーバーヘッドはそれほど影響しません (ただし、起動に遅延が発生する場合があります)。メモリはかなり安価ですが、必要以上に使用しています。スレッドは、このタスクには理想的ではありません。

    非同期 IO を行うためのはるかに優れたシステムがありますが、Perl から簡単に利用できるとは限りません。ただし、特定のケースでは、スレッドを避けてNet::Curl::Multiを使用する方がはるかに優れています。概要の例に従うと、オーバーヘッドがほとんどない並列 Web 要求を作成できる非常に高速なエンジンが得られます。

    以前の雇用主は、負荷の高いミッション クリティカルな Web サイトのために問題なく Net::Curl::Multi に切り替えました。

    周囲のコードへの変更を制限したい場合、HTTP::Response オブジェクトを作成するラッパーを作成するのは簡単です。(これは私たちの場合でした。)Perlコードは基礎となるライブラリ上の薄いレイヤーであるため、基礎となるライブラリ(libcurl)への参照を手元に置いておくと役立つことに注意してください。ドキュメントは非常に優れており、すべてを文書化しています。あなたが提供できるオプション。

于 2013-06-24T17:51:44.400 に答える