目標:トレーニングであろうと推論であろうと、pytorch。CPUの使用率は基本的に約10%です。CPUをより有効に活用して、トレーニングと推論を高速化してください。
1.情報を検索するときに、多くの著者がPython GILの問題について言及しています。ここでは、最初にこのメカニズムを理解し、例から直接始めましょう。
このマシンのCPUはi5-44604コア4スレッドです
まず、Di Shengの手書きのメモの明確な説明を参照してください。この実験では、python3.5を使用しているため、コードを変更します。(Python 3.2は新しいGILの使用を開始しました。他のスレッドが新しいGIL実装でこのロックを要求すると、現在のスレッドは5ミリ秒後にロックを解放するように強制されます。)
GILロック解除メカニズム:
Pythonインタープリタープロセスのマルチスレッドは、協調マルチタスク実行です。スレッドがI / Oタスクに遭遇すると、GILを解放します。CPUにバインドされたスレッドは、インタプリタの約100ティックを実行すると、GILを解放します。ステップカウント(ティック)は、Python仮想マシンの命令と大まかに見なすことができます。ステップカウントは、実際にはタイムスライスの長さとは関係ありません。ステップ長はsys.setcheckinterval()で設定できます。
A1。単一のスレッドが同じプログラム呼び出しを実行します。これには71.47秒かかります。
import time
def counter1():
for i in range(300000000):
i = i + 1
print("this is i:", i + 5)
def counter2():
for j in range(300000000):
j = j + 1
print("this is j:", j + 10)
def main():
start_time = time.time()
for x in range(2):
counter2()
counter1()
end_time = time.time()
print("Total time: {}".format(end_time - start_time))
if __name__ == '__main__':
main()
this is j: 300000010
this is i: 300000005
this is j: 300000010
this is i: 300000005
Total time: 71.47001194953918
A2。同じプログラムを複数のスレッドで実行するには72.08秒かかります。
from threading import Thread
import time
def counter1():
for i in range(300000000):
i = i + 1
print("this is i:", i + 5)
def counter2():
for j in range(300000000):
j = j + 1
print("this is j:", j + 10)
def main():
start_time = time.time()
for x in range(2):
t1 = Thread(target=counter2)
t2 = Thread(target=counter1)
t1.start()
t2.start()
t2.join()
end_time = time.time()
print("Total time: {}".format(end_time - start_time))
if __name__ == '__main__':
main()
this is i: 300000005
this is j: 300000010
this is i: 300000005
Total time: 72.07586812973022
this is j: 300000010
明らかに、上記の2つのケースは、同じプログラムがPython(Cpthon)で実行され、マルチスレッドよりもシングルスレッドの方が高速であることを示しています。GILロックのため、マルチスレッドは、特にマルチコアCPUの場合、並行操作のために実際に頻繁に切り替える必要があります。ひどいスレッドスラッシングです。
B1。シングルスレッドで同じプログラムを実行します。上記と同じプログラムであることに注意してください。ここでは、スリープ(0.01)の時間のかかる操作がコードに追加されています。その結果、現時点では、プログラムをシングルスレッドで実行するのに42.10秒かかりました。
import time
def counter1():
for i in range(1000):
i = i + 1
time.sleep(0.01)
print("this is i:", i + 5)
def counter2():
for j in range(1000):
j = j + 1
time.sleep(0.01)
print("this is j:", j + 10)
def main():
start_time = time.time()
for x in range(2):
counter2()
counter1()
end_time = time.time()
print("Total time: {}".format(end_time - start_time))
if __name__ == '__main__':
main()
this is j: 1010
this is i: 1005
this is j: 1010
this is i: 1005
Total time: 42.09901976585388
B2。また、複数のスレッドを使用して同じプログラムを実行します。同じプログラムは同じであることに注意してください。このタイプのコードは、スリープ(0.01)の時間のかかる操作を追加します。その結果、現時点ではマルチスレッドでプログラムを実行するのに22.00秒かかりました。
from threading import Thread
import time
def counter1():
for i in range(1000):
i = i + 1
time.sleep(0.01)
print("this is i:", i + 5)
def counter2():
for j in range(1000):
j = j + 1
time.sleep(0.01)
print("this is j:", j + 10)
def main():
start_time = time.time()
for x in range(2):
t1 = Thread(target=counter1)
t2 = Thread(target=counter2)
t1.start()
t2.start()
t2.join()
end_time = time.time()
print("Total time: {}".format(end_time - start_time))
if __name__ == '__main__':
main()
this is j: 1010
this is i: 1005
this is i: 1005
this is j: 1010
Total time: 22.006017684936523
同じプログラムで、スリープ時間のかかる操作を追加した後、Pythonでのマルチスレッド操作がシングルスレッド実行よりも速いのはなぜですか?これは上記の結果と矛盾しませんか?これは実際にはGILロックの解放メカニズムです。上記のように:スレッドがI / Oタスクに遭遇すると、GILを解放します。CPUにバインドされたスレッドは、インタプリタの約100ティックを実行すると、GILを解放します。そのため、計算プログラムを時間のかかる待機I / Oプログラムに変換するのと同等の、スリープ時間のかかる操作を追加しました。このとき、GILロックがI / Oタスクに遭遇すると、待機を継続しません。すぐにロックを解除して他のスレッドに渡して実行するため、シングルスレッドよりも効率が大幅に向上します(シングルスレッドは時間のかかる終了を待って実行を継続する必要があるため)
2.Pythonでのマルチスレッドは偽のマルチスレッドであると言った人も見つかりました。これは、DarrenChan Chen Chiの「Pythonのマルチスレッドは無味だと言う人がいるのはなぜですか?」への参照です。'回答。
Pythonでスレッドを導入する前に、問題を明確にしましょう。Pythonでのマルチスレッドは、偽のマルチスレッドです。なぜそう言うのか、最初に概念であるグローバルインタプリタロック(GIL)を明確にしましょう。
Pythonコードの実行は、Python仮想マシン(インタープリター)によって制御されます。Pythonが設計されたとき、Pythonはメインループにあり、同時に1つのスレッドのみが実行されていると見なされていました。単一のCPUシステムで複数のプロセスを実行するのと同じように、複数のプログラムをメモリに格納できますが、いつでも1つのプログラムがCPU上にあります。で実行します。同様に、Pythonインタープリターは複数のスレッドを実行できますが、インタープリターでは1つのスレッドのみが実行されます。
Python仮想マシンへのアクセスは、グローバルインタープリターロック(GIL)によって制御されます。これにより、同時に1つのスレッドのみが実行されます。マルチスレッド環境では、Python仮想マシンは次のように実行されます。
1.GILを設定します。
2.実行するスレッドに切り替えます。
3.実行します。
4.スレッドをスリープ状態に設定します。
5.GILのロックを解除します。
6.上記の手順をもう一度繰り返します。
すべてのI / O指向プログラム(組み込みオペレーティングシステムのCコードを呼び出す)の場合、GILはI / O呼び出しの前に解放され、スレッドがI / Oを待機している間に他のスレッドを実行できるようにします。スレッドが多くのI / O操作を使用しない場合、スレッドは常に独自のタイムスライスでプロセッサとGILを占有します。言い換えると、I / Oを多用するPythonプログラムは、計算を多用するPythonプログラムよりもマルチスレッドの利点を最大限に活用できます。
たとえば、私は4コアのCPUを使用しているので、このように、各コアは単位時間あたり1つのスレッドしか実行できず、タイムスライスがローテーションおよび切り替えられます。ただし、Pythonは異なります。コアの数は関係ありません。複数のコアは、単位時間あたり1つのスレッドしか実行できず、タイムスライスが回転します。信じられないように見えますか?しかし、これはGILの幽霊です。Pythonスレッドを実行する前に、まずGILロックを取得する必要があります。その後、100バイトのコードが実行されるたびに、インタープリターは自動的にGILロックを解放し、他のスレッドが実行できるようにします。このGILグローバルロックは、実際にはすべてのスレッドの実行コードをロックするため、Pythonでは複数のスレッドを交互に実行することしかできません。100コアのCPUで100スレッドを実行しても、使用できるコアは1つだけです。通常、使用するインタープリターはCPythonの公式実装であり、GILなしでインタープリターを書き直さない限り、実際にはマルチコアを使用する必要があります。
最初にPythonマルチスレッドを使用します。
#coding=utf-8
from multiprocessing import Pool
from threading import Thread
from multiprocessing import Process
def loop():
while True:
pass
if __name__ == '__main__':
for i in range(3):
t = Thread(target=loop)
t.start()
while True:
pass
CPUはわずか30%を占めました。
Pythonマルチプロセス:
#coding=utf-8
from multiprocessing import Pool
from threading import Thread
from multiprocessing import Process
def loop():
while True:
pass
if __name__ == '__main__':
for i in range(3):
t = Process(target=loop)
t.start()
while True:
pass
CPUの割合は直接100%です。比較すると、複数のプロセスが複数のコアを使用していることがわかります。
上記のロジックに従って、最初の大きなポイントでマルチプロセスB3アナロジーを記述します。その結果、現時点で複数のスレッドでプログラムを実行するのに21.89秒かかりました。以前の22.00と比較。効果はありますが、効果は小さいです。スリープ時間が長いためです(シングルコアの切り替えでデュアルコアの処理を行うためのフルタイムがあります)。
import time
from multiprocessing import Process
def counter1():
for i in range(1000):
i = i + 1
time.sleep(0.01)
print("this is i:", i + 5)
def counter2():
for j in range(1000):
j = j + 1
time.sleep(0.01)
print("this is j:", j + 10)
def main():
start_time = time.time()
for x in range(2):
t1 = Process(target=counter1)
t2 = Process(target=counter2)
t1.start()
t2.start()
t2.join()
end_time = time.time()
print("Total time: {}".format(end_time - start_time))
if __name__ == '__main__':
main()
this is i: 1005
this is j: 1010
this is j: 1010
this is i: 1005
Total time: 21.886003255844116
上記の私の論理に従ってください。スリープを0.0001に設定したとき。マルチスレッド:4.01秒VSマルチプロセス:3.71秒違いは大きくありませんが、効果は以前よりも明白です。(何度も試行した結果、0.001〜0.000000001までの時間は約4秒対3.7秒でした。ボトルネックはありますか?)
次に、サイクル数を変更します。B2とB3のサイクルを100に変更しても、スリープは0.0001のままです。マルチスレッド:0.40秒VSマルチプロセス:1.08秒
B2とB3のループを10000に変更しても、スリープは0.0001のままです。マルチスレッド:40.01秒VSマルチプロセス:29.94秒
最後に、極値で、B2とB3のループを5に変更しますが、スリープはまだ0.1時間です()。マルチスレッド:1.01秒VSマルチプロセス:1.82秒
最初の大きなポイントは、時間のかかるスリープの操作をすでに説明しています。これは、計算プログラムを時間のかかる待機I / Oプログラムに変えることに相当します。サイクル数は、計算量が多いのと同じです。これらの比較は、 B2とB3にI / Oプログラムと計算集約型プログラムがあり、2つの間の相互作用を単純に比較できないことを示しています。
上記の例はマルチスレッドの利点には適していないためです。マルチプロセスA3に変更しました。その結果、現時点で複数のスレッドでプログラムを実行するのに36.63秒かかりました。以前の72.08のほぼ半分と比較して。
from threading import Thread
import time
from multiprocessing import Process
def counter1():
for i in range(300000000):
i = i + 1
print("this is i:", i + 5)
def counter2():
for j in range(300000000):
j = j + 1
print("this is j:", j + 10)
def main():
start_time = time.time()
for x in range(2):
t1 = Process(target=counter2)
t2 = Process(target=counter1)
t1.start()
t2.start()
t2.join()
end_time = time.time()
print("Total time: {}".format(end_time - start_time))
if __name__ == '__main__':
main()
this is j: 300000010
this is i: 300000005
this is i: 300000005
this is j: 300000010
Total time: 36.62899208068848
A1、A2、およびA3は、計算量の多いプログラムです。A2とA3は、計算量の多いプログラムの計算時間を比較します。マルチコアとマルチプロセス>シングルコアとマルチスレッドです。
B2とB3を単純なマルチスレッドおよびマルチプロセスI / Oプログラムとして比較し、Cを比較します。
from threading import Thread
import time
from multiprocessing import Process
def counter1():
time.sleep(0.1)
def counter2():
time.sleep(0.1)
def main_Thread():
start_time = time.time()
for x in range(100):
t1 = Thread(target=counter1)
t2 = Thread(target=counter2)
t1.start()
t2.start()
t2.join()
end_time = time.time()
print("Thread Total time: {}".format(end_time - start_time))
def main_Process():
start_time = time.time()
for x in range(100):
t1 = Process(target=counter1)
t2 = Process(target=counter2)
t1.start()
t2.start()
t2.join()
end_time = time.time()
print("Process Total time: {}".format(end_time - start_time))
if __name__ == '__main__':
main_Thread()
main_Process()
Thread Total time: 10.126013040542603
Process Total time: 49.22399544715881
Cから、I / Oプログラムの計算時間はシングルコアとマルチスレッド>マルチコアとマルチプロセスであることがわかります。