Performance

為什麼非互動、佔用大量記憶體的程序的速度取決於正在執行的程序(以及如何修復)?

  • February 6, 2017

這似乎是一個基本問題,但我無法在任何地方找到它。我想通過在多核機器上執行其中的許多來在記憶體密集型程序上獲得更多吞吐量。這些程序不相互通信。

希望每個程序的完成時間大致獨立於正在執行的程序數,直到程序數接近物理核心數(在我的情況下為 16 個)。

觀察到完成時間逐漸彎曲,直到當 16 個程序同時執行時,每個程序的執行速度比只有一個程序執行時慢 3 倍。

是什麼讓他們慢下來?(請比“上下文切換”這兩個詞更詳細。)對此我能做些什麼嗎?

編輯: 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 數組。使用本地記憶體,記憶體匯流排上沒有爭用,並且程序繼續擴展,遠高於我在普通硬體上觀察到的限制。

事件率與獨立程序數

這是在 MCDRAM 而不是普通 RAM 上分配 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)

引用自:https://unix.stackexchange.com/questions/341865