web-dev-qa-db-ja.com

マルチプロセッシング:物理コアのみを使用しますか?

多くのメモリを消費し、複数のインスタンスを並行して実行したい関数fooがあります。

CPUに4つの物理コアがあり、それぞれに2つの論理コアがあるとします。

私のシステムには、fooの4つのインスタンスを8つではなく並列に収容できるだけの十分なメモリがあります。さらに、これらの8つのコアのうち4つは論理コアであるため、8つのコアすべてを使用しても上記のような大きな向上は期待できません。 4つの物理的なもののみを使用する以上。

したがって、4つの物理コアでfooを実行したいonly。言い換えると、multiprocessing.Pool(4)(4は、メモリの制限により、このマシンで対応できる関数の同時実行の最大数)を実行して、4つの物理コアにジョブを確実にディスパッチします(たとえば、2つの物理コアと2つの論理的な子孫の組み合わせではありません)。

Pythonでそれを行う方法?

編集:

以前にmultiprocessingのコード例を使用しましたが、私はライブラリにとらわれないため、混乱を避けるために削除しました。

19
user189035

pythonモジュールのソースコードを変更することを含まないソリューションを見つけました。提案されたアプローチを使用します here 。物理コアのみがそのスクリプトを実行した後にアクティブにする:

lscpu

bashでは次を返します:

CPU(s):                8
On-line CPU(s) list:   0,2,4,6
Off-line CPU(s) list:  1,3,5,7
Thread(s) per core:    1

[上記のリンクされたスクリプトを python 内から実行できます]。いずれの場合も、上記のスクリプトを実行した後、次のコマンドをpythonで入力します。

import multiprocessing
multiprocessing.cpu_count()

4を返します。

2
user189035

私はトピックが今かなり古いことを知っていますが、グーグルで「マルチプロセッシング論理コア」と入力すると最初の答えとして表示されるので... 2018年(またはそれ以降)の人々はここで簡単に混乱します(いくつかの回答は確かに少し混乱しています)

上記の答えのいくつかについて読者に警告するためにここより良い場所はないので、トピックを復活させて申し訳ありません。

-> CPU(論理/物理)をカウントするには、PSUTILモジュールを使用します

4物理コア/ 8スレッドのi7 for exの場合

import psutil 
psutil.cpu_count(logical = False)

4

psutil.cpu_count(logical = True)

8

それと同じくらい簡単です。

そこでは、OS、プラットフォーム、ハードウェア自体などについて心配する必要はありません。 少なくとも自分の経験からは、奇妙な結果をもたらす可能性があるmultiprocessing.cpu_count()よりもはるかに優れていると確信しています。

-> N物理コアを使用するには(お好みで)YUGIによって記述されたマルチプロセスモジュールを使用してください

物理プロセスの数を数えるだけで、マルチプロセッシングを起動します.4人のワーカーのプール。

または、joblib.Parallel()関数を使用することもできます

2018年のjoblibは、Pythonの標準ディストリビューションの一部ではなく、Yugiによって記述されたマルチプロセッシングモジュールのラッパーにすぎません。

->ほとんどの場合、利用可能なコアよりも多くのコアを使用しないでください(非常に具体的なコードをベンチマークして、その価値があることを証明した場合を除きます)

「OSが適切に世話をするのは、利用可能なコアよりも多くのコアを使用することです」と、あちこちで(ここで答える人もいます)聞くことができます。 絶対に100%偽です。利用可能なコアよりも多くのコアを使用すると、パフォーマンスが大幅に低下します。 OSスケジューラーは、同じ注意を払ってすべてのタスクを処理するために最善を尽くし、OSによっては定期的に切り替えます。また、OSによっては、作業時間の最大100%を費やして、プロセスを切り替えるだけで済みます。悲惨なこと。

ただ私を信用しないでください。試してみて、ベンチマークしてみてください。

コードが論理的に実行されるかどうかを決定することは可能ですか?OR物理コア?

この質問をしている場合、これは物理コアと論理コアの設計方法を理解していないことを意味するため、プロセッサのアーキテクチャについてもう少し確認する必要があります。

たとえば、コア1ではなくコア3で実行したい場合、確かにいくつかの解決策があると思いますが、OSのカーネルとスケジューラのコーディング方法を知っている場合にのみ利用できます。この質問をします。

4つの物理的/ 8つの論理プロセッサーで4つのCPU集中型プロセスを起動する場合、スケジューラーは各プロセスを1つの異なる物理コアに帰属させます(そして4つの論理コアは使用されない/使用されないままになります)。しかし、4論理/ 8スレッドプロシージャでは、処理単位が(0,1)(1,2)(2,3)(4,5)(5,6)(6,7)の場合、noになります。プロセスが0または1で実行される場合の違い:同じ処理単位です。

少なくとも私の知識から(ただし、専門家は確認/確認できますが、非常に具体的なハードウェア仕様とも異なる場合があります)0または1でコードを実行することには違いがないか、ほとんどありません。処理ユニット(0,1 )、0が論理的であるのに対し、1が物理的、またはその逆であるかどうかはわかりません。私の理解から(これは間違っている可能性があります)、どちらも同じ処理ユニットのプロセッサであり、キャッシュメモリを共有するだけです/ハードウェア(RAMを含む)へのアクセスを共有し、0は1よりも物理ユニットではありません。

それ以上は、OSに決定させる必要があります。 OSスケジューラーは、一部のプラットフォーム(ex i7、i5、i3 ...など)に存在するハードウェア論理コアターボブーストを利用できるため、電源が入っておらず、本当に役立つ可能性があります。

4つの物理的/ 8つの論理コアで5つのCPU集中型タスクを起動すると、動作は無秩序になり、ほとんど予測できなくなり、ほとんどハードウェアとOSに依存します。スケジューラーは最善を尽くします。ほとんどの場合、あなたは本当に悪いパフォーマンスに直面する必要があります。

4(8)クラシックアーキテクチャについてまだ話していると仮定してみましょう。実行しているプロセスに応じて、スケジューラが最善を尽くすため(したがって、多くの場合、属性を切り替えるため) 、8つの論理コアよりも5つの論理コアで起動する方が悪い場合があります(少なくとも、すべてが100%で使用されることを知っているので、失われたために失われても、それを回避しようとはしません、切り替えません。あまりにも頻繁に、したがって切り替えによってあまり時間を失うことはありません)。

ただし、物理コアを使用可能より多く使用すると、ほとんどすべてのマルチプロセッシングプログラムの実行速度が遅くなることは99%確実です(ただし、ハードウェアでベンチマークしてください)。

多くのことが介入する可能性があります...プログラム、ハードウェア、OSの状態、それが使用するスケジューラー、今朝食べた果物、姉妹の名前...何か疑問がある場合は、ベンチマークしてください。パフォーマンスが低下しているかどうかを確認する簡単な方法は他にありません。時には情報学は本当に奇妙なことができます。

->ほとんどの場合、追加の論理コアは確かに有用ですPYTHON(ただし常にではありません)

Pythonで実際に並列タスクを実行するには、主に2つの方法があります。

  • マルチプロセッシング(論理コアを利用できない)
  • マルチスレッド(論理コアを利用できます)

たとえば、4つのタスクを並行して実行するには

-> multiprocessingは、4つの異なるpythonインタープリターを作成します。それらのそれぞれについて、pythonインタープリターを開始し、読み取り/書き込みの権限を定義し、定義します。環境、大量のメモリの割り当てなどを考えてみましょう。まったく新しいプログラムインスタンスを0から開始します。かなり時間がかかる場合があるため、この新しいプログラムが機能することを確認する必要がありますそれが価値があるように十分に長い。

プログラムに十分な作業がある場合(たとえば、少なくとも数秒の作業としましょう)、OSはCPUを消費するプロセスを異なる物理コアに割り当てるため、動作し、多くのパフォーマンスを得ることができます。これは素晴らしいことです。そして、OSはほとんどの場合、プロセスがそれらの間で通信することを許可しているため(遅いですが)、データを交換することもできます(少しだけ)。

->マルチスレッドは異なります。 pythonインタプリタ内では、多くのCPUが共有できる少量のメモリを作成し、それを同時に処理します。スポーンする方がはるかに高速です(ここで古いコンピュータで新しいプロセスを生成するのに数秒かかることがありますが、スレッドの生成は、途方もなく短い時間で行われます。新しいプロセスを作成するのではなく、はるかに軽い「スレッド」を作成します。

スレッドは、同じメモリ上で文字通り一緒に機能するため、スレッド間でメモリを非常に迅速に共有できます(異なるプロセスで作業する場合は、コピー/交換する必要があります)。

しかし:できない理由WEほとんどの状況でマルチスレッドを使用しますか?非常に便利に見えますか?

Pythonには非常に大きな制限があります。pythonインタープリターでは、一度に1つのpython行しか実行できません。これはGIL(Global Interpreterロック)。したがって、異なるリソースが同じリソースへのアクセスを待機する必要があるため、ほとんどの場合、マルチスレッドを使用するとパフォーマンスが低下します。コードが純粋なpythonの場合、マルチスレッドは常にUSELESSであり、さらにはWORSEです。

->マルチプロセッシングを使用するときに論理コアを使用すべきではないのはなぜですか?

論理コアには、独自のメモリアクセスはありません。これらは、メモリアクセスと、ホストしている物理プロセッサのキャッシュでのみ機能します。たとえば、同じプロセッシングユニットの論理コアと物理コアの両方が、キャッシュメモリの異なる配置で同じC/C++関数を同時に使用する可能性が非常に高くなります(実際に頻繁に使用されます)。治療を本当に速くする。

しかし...これらはC/C++関数です! Pythonは大きなC/C++ラッパーであり、同等のC++コードよりもはるかに多くのメモリとCPUを必要とします。2018年には、何をしたいにせよ、2つの大きなpythonプロセスは、単一の物理+論理ユニットが提供できるものよりもはるかに多くのメモリとキャッシュの読み取り/書き込みを必要とし、同等のC/C++真にマルチスレッド化されたコードが消費するものよりもはるかに多くを必要とします。繰り返しますが、ほとんどの場合、パフォーマンスが低下します。プロセッサのキャッシュで利用できないすべての変数は、メモリの読み取りにx1000時間かかることに注意してください。キャッシュが1つのシングルですでに完全にいっぱいの場合pythonプロセス、2つのプロセスを使用するように強制するとどうなるかを推測します。プロセスは一度に1つずつ使用し、永続的に切り替えます。そのため、データがばかばかしくフラッシュされ、切り替わるたびに再度読み取られます。データが発生しているときメモリから読み書きすると、CPUは「動作している」と思われるかもしれませんが、動作していません。データを待っています!何もしないことによって。

->論理コアをどのように活用できますか?

私が言ったように、グローバルインタープリターロックのため、デフォルトのpythonには真のマルチスレッドはありません(したがって論理コアの真の使用法はありません)。プログラムのいくつかの部分でGILを強制的に削除することができますが、何をしているのか正確にわからない場合は触れないことをお勧めします。

GILを削除することは間違いなく多くの調査の対象となっています(両方を試行する実験的なPyPyまたはCythonプロジェクトを参照してください)。

今のところ、見かけよりもはるかに複雑な問題であるため、実際の解決策はありません。

私が認める、動作する別のソリューションがあります:-Cで関数をコード化-python ctypeでラップする-pythonマルチスレッドモジュールを使用してラップされたC関数を呼び出す

これは100%機能し、すべての論理コアをPythonで、マルチスレッドで、そして実際に使用することができます。 true python関数ではなく、C関数を実行するため、GILは気になりません。

たとえば、Numpyのような一部のライブラリは、Cでコーディングされているため、利用可能なすべてのスレッドで動作します。しかし、この時点で、C/C++で直接プログラムを実行することを考えるのが賢明だと常に思っていました。当初のPythonicの精神とはかけ離れた考察。

**->使用可能なすべての物理コアを常に使用しないでください**

「8つの物理コアがあるので、仕事には8コアを使う」という人がよくいます。それはしばしば機能しますが、特にあなたの仕事が多くのI/Oを必要とする場合、時々悪い考えであることが判明します。

N-1コアで試してみてください(特に、I/O要求の多いタスクの場合は特に)、タスクごと/平均で、100%の時間で、単一のタスクが常にN-1コアでより速く実行されることがわかります。実際、コンピュータはUSB、マウス、キーボード、ネットワーク、ハードドライブなど、さまざまなものを作成します...作業ステーションであっても、定期的なタスクは、あなたが知らないバックグラウンドでいつでも実行されます。 1つの物理コアにこれらのタスクを管理させない場合、計算は定期的に中断され(メモリからフラッシュされる/メモリに戻される)、これもパフォーマンスの問題につながる可能性があります。

「まあ、バックグラウンドタスクはCPU時間の5%しか使わないので、残り95%になる」と思うかもしれません。しかし、そうではありません。

プロセッサは一度に1つのタスクを処理します。そして、それが切り替わるたびに、すべてをメモリキャッシュ/レジストリの元の場所に戻すために、かなりの時間が無駄になります。次に、奇妙な理由でOSスケジューラがこの切り替えを頻繁に行う場合(ユーザーが制御できないもの)、この計算時間のすべてが永久に失われ、それに対してあなたができることは何もありません。

不明な理由でこのスケジューラの問題が1つではなく30のタスクのパフォーマンスに影響を与える場合(およびそれが発生する場合があります)、29/30物理コアでの作業が30/30での作業よりも大幅に高速になるという興味深い状況が発生する可能性があります

より多くのCPU IS常にベストではない

Multiprocessing.Poolを使用する場合、プロセス間で共有されるmultiprocessing.Queueまたはマネージャーキューを使用して、プロセス間の基本的な通信を可能にすることは非常に頻繁です。ときどき(私は100回言ったはずですが、繰り返します)、ハードウェアに依存する方法で、より多くのCPUを使用するとボトルネックが生じる可能性があります(ただし、特定のアプリケーション、コード実装、およびハードウェアに対してベンチマークする必要があります)プロセスを通信/同期させる場合。これらの特定のケースでは、低いCPU数で実行したり、より高速なプロセッサで同期タスクをデポートしたりすることも興味深いかもしれません(ここでは、もちろん、クラスターで実行される科学的集約計算について話しています)。多くの場合、マルチプロセッシングはクラスターで使用することを目的としているため、省エネのためにクラスターの周波数が低くなっていることに注意する必要があります。そのため、シングルコアのパフォーマンスは本当に悪くなる可能性があり(CPUの数が非常に多いことでバランスがとれます)、ローカルコンピューターからコードをスケーリングするときに問題がさらに悪化します(コアが少ない、単一のコアのパフォーマンスが高い)からクラスター(多数のコア、単一のコアのパフォーマンスが低い)へ。これは、single_core_perf/nb_cpuの比率に応じてコードのボトルネックが発生し、ときどき煩わしいからです。

誰もができるだけ多くのCPUを使用したいという誘惑があります。ただし、これらのケースのベンチマークは必須です。

典型的なケース(例:データサイエンス)は、Nプロセスを並行して実行し、結果を1つのファイルにまとめたい場合です。ジョブが完了するのを待つことができないため、特定のライタープロセスを介して実行します。ライターは、彼のmultiprocessing.Queueにプッシュされたすべてのものを出力ファイルに書き込みます(シングルコアおよびハードドライブ限定プロセス)。 Nプロセスがmultiprocessing.Queueを満たします。

31 CPUが非常に遅い1つのCPUに情報を書き込んでいる場合、パフォーマンスが低下する(そして、一時データを処理するシステムの機能を克服すると何かがクラッシュする)と想像するのは簡単です。

->お持ち帰りメッセージ

  • Multiprocessing.cpu_count()などではなく、psutilを使用して論理/物理プロセッサをカウントする
  • マルチプロセッシングは物理コアでのみ機能します(または、少なくともそれをベンチマークして、ケースに当てはまらないことを証明します)
  • マルチスレッドは論理コアで機能しますが、関数をCでコーディングしてラップするか、グローバルロックインタープリターを削除する必要があります(そうするたびに、1匹の子猫が世界のどこかでひどく死ぬ)
  • 純粋なpythonコードでマルチスレッドを実行しようとすると、パフォーマンスが大幅に低下するため、99%の時間でマルチプロセッシングを使用する必要があります
  • プロセス/スレッドに悪用可能な長い休止がない限り、利用可能なコアよりも多くのコアを使用しないでください。
  • タスクがI/Oを集中的に使用する場合は、1つの物理コアでI/Oを処理できるようにする必要があります。十分な物理コアがある場合は、それだけの価値があります。マルチプロセッシング実装では、N-1物理コアを使用する必要があります。従来の2ウェイマルチスレッドでは、N-2論理コアを使用することを意味します。
  • さらにパフォーマンスが必要な場合は、PyPy(製品版ではない)またはCythonを試すか、Cでコーディングしてみてください

最後に重要なことですが、最も重要なのは、本当にパフォーマンスを求めている場合は、絶対に、常に、常にベンチマークを行い、何も推測しないことです。ベンチマークはしばしば、あなたが知らないであろう奇妙なプラットフォーム/ハードウェア/ドライバーの非常に特定の振る舞いを明らかにします。

22

:このアプローチはWindowsでは機能せず、Linuxでのみテストされています。

_multiprocessing.Process_の使用:

Process()を使用すると、各プロセスに物理コアを割り当てるのが非常に簡単になります。 _taskset -p [mask] [pid]_を使用して、各コアを反復して新しいプロセスを新しいコアに割り当てるforループを作成できます。

_import multiprocessing
import os

def foo():
    return

if __name__ == "__main__" :
    for process_idx in range(multiprocessing.cpu_count()):
        p = multiprocessing.Process(target=foo)
        os.system("taskset -p -c %d %d" % (process_idx % multiprocessing.cpu_count(), os.getpid()))
        p.start()
_

私のワークステーションには32コアがあるので、部分的な結果をここに入力します。

_pid 520811's current affinity list: 0-31
pid 520811's new affinity list: 0
pid 520811's current affinity list: 0
pid 520811's new affinity list: 1
pid 520811's current affinity list: 1
pid 520811's new affinity list: 2
pid 520811's current affinity list: 2
pid 520811's new affinity list: 3
pid 520811's current affinity list: 3
pid 520811's new affinity list: 4
pid 520811's current affinity list: 4
pid 520811's new affinity list: 5
...
_

ご覧のとおり、ここには各プロセスの以前と新しいアフィニティがあります。 1つ目はすべてのコア(0-31)用であり、次にコア0に割り当てられ、2つ目のプロセスはデフォルトでcore0に割り当てられ、次にそのアフィニティは次のコア(1)に変更されます。

_multiprocessing.Pool_の使用:

警告:_pool.py_モジュールを微調整する必要があるのは、Pool()。また、この変更は_python 2.7_および_multiprocessing.__version__ = '0.70a1'_でテストされています。

_Pool.py_で、_task_handler_start()メソッドが呼び出されている行を見つけます。次の行では、プール内のプロセスを各「物理」コアに割り当てることができます(読者がそれをインポートするのを忘れないように、ここに_import os_をここに配置します)。

_import os
for worker in range(len(self._pool)):
    p = self._pool[worker]
    os.system("taskset -p -c %d %d" % (worker % cpu_count(), p.pid))
_

これで完了です。テスト:

_import multiprocessing

def foo(i):
    return

if __name__ == "__main__" :
    pool = multiprocessing.Pool(multiprocessing.cpu_count())
    pool.map(foo,'iterable here')
_

結果:

_pid 524730's current affinity list: 0-31
pid 524730's new affinity list: 0
pid 524731's current affinity list: 0-31
pid 524731's new affinity list: 1
pid 524732's current affinity list: 0-31
pid 524732's new affinity list: 2
pid 524733's current affinity list: 0-31
pid 524733's new affinity list: 3
pid 524734's current affinity list: 0-31
pid 524734's new affinity list: 4
pid 524735's current affinity list: 0-31
pid 524735's new affinity list: 5
...
_

この_pool.py_への変更により、ジョブがコアにラウンドロビン方式で割り当てられることに注意してください。したがって、cpu-coresよりも多くのジョブを割り当てると、同じコア上に複数のジョブが存在することになります。

編集:

OPが探しているのは、特定のコアでプールを開始できるpool()を用意することです。このためには、multiprocessingをさらに調整する必要があります(最初に上記の変更を元に戻します)。

警告:

関数定義と関数呼び出しをコピーアンドペーストしないでください。 self._worker_handler.start()の後に追加することになっている部分のみをコピーして貼り付けます(下に表示されます)。私の_multiprocessing.__version___はバージョンが_'0.70a1'_であることを通知しますが、追加する必要があるものを追加するだけであれば問題ありません。

multiprocessingの_pool.py_:

_cores_idx = None_引数を__init__()定義に追加します。私のバージョンでは、追加すると次のようになります:

_def __init__(self, processes=None, initializer=None, initargs=(),
             maxtasksperchild=None,cores_idx=None)
_

また、self._worker_handler.start()の後に次のコードを追加する必要があります。

_if not cores_idx is None:
    import os
    for worker in range(len(self._pool)):
        p = self._pool[worker]
        os.system("taskset -p -c %d %d" % (cores_idx[worker % (len(cores_idx))], p.pid))
_

multiprocessingの___init__.py_

_cores_idx=None_引数をPool() inの定義と、戻り部分の他のPool()関数呼び出しに追加します。私のバージョンでは次のようになります:

_def Pool(processes=None, initializer=None, initargs=(), maxtasksperchild=None,cores_idx=None):
    '''
    Returns a process pool object
    '''
    from multiprocessing.pool import Pool
    return Pool(processes, initializer, initargs, maxtasksperchild,cores_idx)
_

これで完了です。次の例では、コア0および2でのみ5ワーカーのプールを実行します。

_import multiprocessing


def foo(i):
    return

if __name__ == "__main__":
    pool = multiprocessing.Pool(processes=5,cores_idx=[0,2])
    pool.map(foo,'iterable here')
_

結果:

_pid 705235's current affinity list: 0-31
pid 705235's new affinity list: 0
pid 705236's current affinity list: 0-31
pid 705236's new affinity list: 2
pid 705237's current affinity list: 0-31
pid 705237's new affinity list: 0
pid 705238's current affinity list: 0-31
pid 705238's new affinity list: 2
pid 705239's current affinity list: 0-31
pid 705239's new affinity list: 0
_

もちろん、_cores_idx_引数を削除することで、multiprocessing.Poll()の通常の機能を引き続き使用できます。

11
Kennet Celeste