Я наблюдаю странное поведение в параллельном коде, который я разрабатываю с Python 3.6 / Numpy под Linux: запуск его на 2 ядрах примерно в 3 раза быстрее, чем на одном ядре. Я не уверен, что понимаю точную проблему, но мне удалось найти некоторые подсказки:
- Используя perf, я обнаружил, что с одним ядром получаю гораздо больше сбоев страниц, а не 2 (частичные прогоны - это долго выполняющиеся задания):
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
.
Мне кажется, что по крайней мере часть проблемы возникает при копировании массива Numpy размером ~ 15 × 3500, но не каждый раз. Код по существу вычисляет модели на декартовой сетке физических параметров и делает частые копии объекта, который содержит несколько массивов Numpy. Теперь, для некоторых копий (но не всех) этого объекта копия этого массива ~ 15 × 3500 Numpy занимает ~ 15 раз больше, я полагаю из-за ошибки страницы. Код часто создает / удаляет эти объекты, но общая память, связанная с каждым процессом, изменяется медленно (результаты записываются в общий RawArray), поэтому я считаю, что память обычно должна быть перераспределена (создание новых объектов происходит вскоре после уничтожения предыдущего). Это происходит только тогда, когда используется одно ядро, а не при использовании большего количества ядер.
Я попытался запустить два независимых запуска, используя по 1 ядру одновременно, и у них обоих большое количество ошибок страниц. Так что это не похоже на одновременное выполнение нескольких процессов.
При работе на одном ядре я пробовал как с многопроцессорной обработкой (spawn и fork), так и без, результат один и тот же, с большим количеством сбоев страниц. Так что это, похоже, не связано
Это происходит как с пакетами Python Archlinux, так и с пакетами Anaconda.
Это происходит как на AMD EPYC 7351 с 512 ГБ или ОЗУ, так и на Intel Core i7 6700K с 64 ГБ ОЗУ (оба работают под управлением Archlinux). Код использует ~ 10 ГБ ОЗУ, в основном в виде RawArrays для хранения результата. Объем оперативной памяти, используемой в отдельных объектах, должен составлять ~ 500 кБ на объект, и в любой момент времени в оперативной памяти их должно быть не более 7-8.
Я немного растерялся из-за того, почему я получаю так много сбоев страниц при работе на одном ядре, а не при работе на ядрах 2+. Есть идеи, что может происходить и возможные способы это исправить? Я пытаюсь измерить, как масштабируется код с количеством ядер, поэтому мне нужна надежная точка данных при работе на одном ядре. As использует 2 ядра в 3 раза быстрее, чем работает на одном ядре.