我正在观察我在Linux下使用Python 3.6 / Numpy开发的并行代码中的奇怪行为:在2个内核上运行它比在单个内核上运行它快3倍。我不确定我是否理解确切的问题,但我设法找到了一些线索:
1核心
57414.449252 task-clock:u (msec) # 1.000 CPUs utilized
0 context-switches:u # 0.000 K/sec
0 cpu-migrations:u # 0.000 K/sec
22,583,368 page-faults:u # 0.393 M/sec
115,569,738,160 cycles:u # 2.013 GHz
22,655,695,268 stalled-cycles-frontend:u # 19.60% frontend cycles idle
32,251,136,400 stalled-cycles-backend:u # 27.91% backend cycles idle
189,041,318,733 instructions:u # 1.64 insn per cycle
# 0.17 stalled cycles per insn
38,298,469,811 branches:u # 667.053 M/sec
654,389,291 branch-misses:u # 1.71% of all branches
83,343,166,583 L1-dcache-loads:u # 1451.606 M/sec
318,284,548 L1-dcache-load-misses:u # 0.38% of all L1-dcache hits
25,589,478,853 L1-icache-loads:u # 445.698 M/sec
2,560,674,130 L1-icache-load-misses:u # 10.01% of all L1-icache hits
83,377,552,384 dTLB-loads:u # 1452.205 M/sec
216,145,062 dTLB-load-misses:u # 0.26% of all dTLB cache hits
25,626,979,550 iTLB-loads:u # 446.351 M/sec
55,847,490 iTLB-load-misses:u # 0.22% of all iTLB cache hits
222,176 L1-dcache-prefetches:u # 0.004 M/sec
220,870 L1-dcache-prefetch-misses:u # 0.004 M/sec
2个核心
111569.269846 task-clock:u (msec) # 1.837 CPUs utilized
0 context-switches:u # 0.000 K/sec
0 cpu-migrations:u # 0.000 K/sec
4,415,116 page-faults:u # 0.040 M/sec
330,750,606,867 cycles:u # 2.965 GHz
69,517,418,862 stalled-cycles-frontend:u # 21.02% frontend cycles idle
104,365,888,484 stalled-cycles-backend:u # 31.55% backend cycles idle
609,338,385,634 instructions:u # 1.84 insn per cycle
# 0.17 stalled cycles per ins
125,341,622,204 branches:u # 1123.442 M/sec
1,860,702,685 branch-misses:u # 1.48% of all branches
261,941,396,767 L1-dcache-loads:u # 2347.792 M/sec
935,372,306 L1-dcache-load-misses:u # 0.36% of all L1-dcache hits
79,306,212,694 L1-icache-loads:u # 710.825 M/sec
7,337,551,130 L1-icache-load-misses:u # 9.25% of all L1-icache hits
261,578,594,355 dTLB-loads:u # 2344.540 M/sec
425,510,820 dTLB-load-misses:u # 0.16% of all dTLB cache hits
79,008,997,415 iTLB-loads:u # 708.161 M/sec
102,332,385 iTLB-load-misses:u # 0.13% of all iTLB cache hits
944,223 L1-dcache-prefetches:u # 0.008 M/sec
945,497 L1-dcache-prefetch-misses:u # 0.008 M/sec
运行perf top表示在内核中花费了大约35%的时间,这些函数是最主要的贡献者:
clear_page_rep page_fault check_preemption_disabled get_page_from_freelist __handle_mm_fault free_pcppages_bulk __pagevec_lru_add_fn sync_regs release_pages handle_mm_fault preempt_count_add。
我似乎至少部分问题是在复制大约15×3500浮点数的Numpy数组时创建的,但不是每次都复制。该代码基本上在笛卡尔物理参数网格上计算模型,并对包含少量Numpy数组的对象进行频繁复制。现在,对于该对象的一些副本(但不是全部),这个~15×3500 Numpy数组的副本需要大约15倍的时间,我假设由于页面错误。代码经常创建/删除这些对象,但与每个进程关联的整体内存变化缓慢(结果写入共享的RawArray)所以我认为通常应该重新分配内存(在破坏后很快就会创建新对象)前一个)。只有在使用单个核心时才会发生这种情况,而不是在使用更多核心时。
我尝试同时使用1个核心启动两个独立的运行,并且它们都有大量的页面错误。所以它似乎与同时运行多个进程无关。
当在单核上运行时,我尝试了多处理(spawn和fork)而没有,结果是相同的,有大量的页面错误。所以它似乎没有相关性
Archlinux python软件包和Anaconda软件包都会出现这种情况。
在具有512 GB或RAM的AMD EPYC 7351和具有64 GB RAM的Intel Core i7 6700K(均运行Archlinux)上都会出现这种情况。代码使用~10 GB的RAM,大多数以RawArrays的形式存储结果。单个对象中使用的RAM应该是每个对象大约500 kB,并且在任何给定时刻RAM中的RAM不应超过7-8。
我有点迷失为什么在单核上运行时会出现如此多的页面错误,而在2+核心上运行则不会。知道可能发生的事情和可能的解决方法吗?我正在尝试测量代码如何随核心数量而扩展,因此我希望在单核上运行时获得可靠的数据点。使用2个内核比在单个内核上运行快3倍。