web-dev-qa-db-ja.com

相互作用しない、メモリを大量に消費するプロセスの速度が、実行中のプロセスの数(および修正方法)に依存するのはなぜですか?

これは基本的な質問のようですが、どこにも見つかりませんでした。それらの多くをメニーコアマシンで実行することにより、メモリを大量に消費するプロセスでスループットを向上させたいと考えています。これらのプロセスは相互に通信しません。

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))
1
Jim Pivarski

何が悪かったのかがわかったので、UNIXの制限ではなく、ハードウェアの制限であることがわかりました。したがって、これは投稿するのに適切な場所ではありません。しかし、私はいくつかのクロージャを追加する必要があると思いました。

私のメモリが制限された独立したプロセスは、実際にメモリ帯域幅の問題に直面していました。 Knights Landingプロセッサでそれを繰り返し、ローカルMCDRAMにNumpy配列を割り当てる方法を学びました。ローカルメモリを使用すると、メモリバスで競合は発生せず、プロセスは通常のハードウェアで観察された制限をはるかに超えてスケ​​ーリングし続けます。

event rate vs number of independent processes

これは、通常の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)
1
Jim Pivarski

あなたのプロセスはメモリが多く、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倍の増加)。

0
MLu