これは基本的な質問のようですが、どこにも見つかりませんでした。それらの多くをメニーコアマシンで実行することにより、メモリを大量に消費するプロセスでスループットを向上させたいと考えています。これらのプロセスは相互に通信しません。
expect各プロセスの完了までの時間は、プロセスの数が物理コアの数(私の場合は16)に近づくまで、実行中のプロセスの数にほぼ依存しません。
I observe完了までの時間徐々に 16が同時に実行されている場合、1つだけの場合と比べて、各プロセスの実行が約3倍遅くなるまでカーブします。が走っています。
何が彼らを遅くしているのですか? (「コンテキスト切り替え」という2つの単語よりも詳細にお願いします。)これについて何かできますか?
編集: Michael Homerは、CPUを多用するプロセスではなく、メモリを多用するプロセスに関心があると指摘しています。これらのCPUはすべてメモリバスを共有していると思いますが、それがボトルネックになる可能性があります。理想的には、ある種のNUMAアーキテクチャで、プロセスメモリをCPUに「近づけて」配置したいと思います。これは、この問題を解決するために別のハードウェアを探す必要があることを意味しますか?
詳細はこちら:
quickie2.py
という単純なスクリプトがあり、CPUを集中的に使用するランダムな作業を実行します。 14個のプロセスに対して次のようなbashコマンドラインで一度にN個を起動します。
for x in 1 2 3 4 5 6 7 8 9 10 11 12 13 14; do (python quickie2.py &); done
各Nの完了までの時間は次のとおりです。
N_proc Time to completion (sec)
1 7.29
2 7.28 7.30
3 7.27 7.28 7.38
4 7.01 7.19 7.34 7.43
5 8.41 8.94 9.51 10.27 11.73
6 7.49 7.79 7.97 10.01 10.58 10.85
7 7.71 8.72 10.22 10.43 10.81 10.81 11.42
8 10.1 10.16 10.27 10.29 10.48 10.60 10.66 10.73
9 9.94 11.20 11.27 11.35 11.61 12.43 12.46 12.99 13.53
10 9.26 12.54 12.66 12.84 12.95 13.03 13.06 13.52 13.93 13.95
11 12.46 12.48 12.65 12.74 13.69 13.92 14.14 14.39 14.40 14.69 17.13
12 13.48 13.49 13.51 13.58 13.65 13.67 14.72 14.87 14.89 14.94 15.01 15.06
13 15.47 15.51 16.72 16.79 16.79 16.91 17.00 17.45 17.75 17.78 17.86 18.14 18.48
14 15.14 15.22 16.47 16.53 16.84 17.78 18.07 19.00 19.12 19.32 19.63 19.71 19.80 19.94
15 18.05 18.18 18.58 18.69 19.84 20.70 21.82 21.93 22.13 22.44 22.63 22.81 22.92 23.23 23.23
16 20.96 21.00 21.10 21.21 22.68 22.70 22.76 22.82 24.65 24.66 25.32 25.59 26.16 26.22 26.31 26.38
編集:ちなみに、プロセスをコアに固定すると、フォールオフが悪化します。以下のコードリストのコメントアウトされた行を参照してください。
N_proc Time to completion (sec) with CPU-pinning
1 6.95
2 10.11 10.18
4 19.11 19.11 19.12 19.12
8 20.09 20.12 20.36 20.46 23.86 23.88 23.98 24.16
16 20.24 22.10 22.22 22.24 26.54 26.61 26.64 26.73 26.75 26.78 26.78 26.79 29.41 29.73 29.78 29.90
これはhtopのスクリーンショットで、実際に正確にN個(ここでは14個)のコアがビジーであることを示しています。
1 [|||||||||||||||98.0%] 5 [|| 5.8%] 9 [||||||||||||||100.0%] 13 [ 0.0%]
2 [||||||||||||||100.0%] 6 [||||||||||||||100.0%] 10 [||||||||||||||100.0%] 14 [||||||||||||||100.0%]
3 [||||||||||||||100.0%] 7 [||||||||||||||100.0%] 11 [||||||||||||||100.0%] 15 [||||||||||||||100.0%]
4 [||||||||||||||100.0%] 8 [||||||||||||||100.0%] 12 [||||||||||||||100.0%] 16 [||||||||||||||100.0%]
Mem[|||||||||||||||||||||||||||||||||||||3952/64420MB] Tasks: 96, 7 thr; 15 running
Swp[ 0/16383MB] Load average: 5.34 3.66 2.29
Uptime: 76 days, 06:59:39
完全を期すために、ここにいくつかの作業を行うPythonプログラムがあります。CPUをビジー状態に保つことだけが重要です。
# Code of quickie2.py (for completeness).
import numpy
import time
# import psutil
# psutil.Process().cpu_affinity([int(sys.argv[1])])
arena = numpy.empty(240*1024**2, dtype=numpy.uint8)
startTime = time.time()
# just do some work that takes a lot of CPU
for i in range(100):
one = arena[:80*1024**2].view(numpy.float64)
two = arena[80*1024**2:160*1024**2].view(numpy.float64)
three = arena[160*1024**2:].view(numpy.float64)
three = one + two
print(" {:.2f} ".format(time.time() - startTime))
何が悪かったのかがわかったので、UNIXの制限ではなく、ハードウェアの制限であることがわかりました。したがって、これは投稿するのに適切な場所ではありません。しかし、私はいくつかのクロージャを追加する必要があると思いました。
私のメモリが制限された独立したプロセスは、実際にメモリ帯域幅の問題に直面していました。 Knights Landingプロセッサでそれを繰り返し、ローカルMCDRAMにNumpy配列を割り当てる方法を学びました。ローカルメモリを使用すると、メモリバスで競合は発生せず、プロセスは通常のハードウェアで観察された制限をはるかに超えてスケーリングし続けます。
これは、通常のRAMではなくMCDRAMにNumpyアレイを割り当てるためのレシピです。
import ctypes
import numpy
def malloc_mcdram(size):
libnuma = ctypes.cdll.LoadLibrary("libnuma.so")
assert libnuma.numa_available() == 0 # NUMA not available is -1
libnuma.numa_alloc_onnode.restype = ctypes.POINTER(ctypes.c_uint8)
return libnuma.numa_alloc_onnode(ctypes.c_size_t(size), ctypes.c_int(1))
def custom_allocator_array(allocator, size, dtype):
ptr = allocator(size)
ptr.__array_interface__ = {"version": 3,
"typestr": numpy.ctypeslib._dtype(type(ptr.contents)).str,
"data": (ctypes.addressof(ptr.contents), False),
"shape": (size,)}
return numpy.array(ptr, copy=False).view(dtype)
myarray = custom_allocator_array(malloc_mcdram, sizeInBytes, numpy.float64)
あなたのプロセスはメモリが多く、CPUが重いわけではありません。代わりにこれを試してください:
#!/usr/bin/env python
import datetime
import hashlib
data = "\0" * 64
ts_start = datetime.datetime.now()
for i in range(10000000):
data = hashlib.sha512(data).digest()
ts_end = datetime.datetime.now()
print("Elapsed: %s" % (ts_end - ts_start))
最大8回の実行を並行して実行すると、2ソケット/ 8コア/ 16スレッドのマシンで一貫した結果(完了までに約20秒)が得られます。その上、プロセスがCPUリソースをめぐって争い始めると、パフォーマンスが低下します。
シングルラン:
~$ python cpuheavy.py
Elapsed: 0:00:20.461652
並列に8つ(=コアごとに1つ)、それでも同時に:
~$ for i in $(seq 8); do python cpuheavy.py & done
Elapsed: 0:00:18.979012
Elapsed: 0:00:19.092770
Elapsed: 0:00:19.873763
Elapsed: 0:00:20.139105
Elapsed: 0:00:20.147066
Elapsed: 0:00:20.181319
Elapsed: 0:00:21.328754
Elapsed: 0:00:21.495310
並行して16回実行すると(ハイパースレッドごとに1回)、プロセスがCPU時間にわたって戦い始めたため、時間は約31秒に増加しました。時間の約50%の増加。
32回の並行実行では、プロセスがCPUスレッドを共有する必要があったため、丘を下っていきました。完了するまでの時間は、プロセスごとに2分以上に増加しました(時間の4倍の増加)。