Как найти идеальное количество параллельных процессов для многопроцессорной обработки python? - PullRequest
0 голосов
/ 04 марта 2020

Попытка определить правильное количество параллельных процессов для запуска с python многопроцессорным .

Сценарии ниже выполняются на 8-ядерном компьютере с 32 ГБ (Ubuntu 18.04) , (Были только системные процессы и основные c пользовательские процессы, запущенные во время тестирования ниже.)

Протестировано multiprocessing.Pool и apply_async со следующим:

from multiprocessing import current_process, Pool, cpu_count
from datetime import datetime
import time

num_processes = 1 # vary this

print(f"Starting at {datetime.now()}")
start = time.perf_counter()

print(f"# CPUs = {cpu_count()}") # 8
num_procs = 5 * cpu_count() # 40


def cpu_heavy_fn():
    s = time.perf_counter()
    print(f"{datetime.now()}: {current_process().name}")
    x = 1
    for i in range(1, int(1e7)):
        x = x * i
        x = x / i
    t_taken = round(time.perf_counter() - s, 2)
    return t_taken, current_process().name


pool = Pool(processes=num_processes)

multiple_results = [pool.apply_async(cpu_heavy_fn, ()) for i in range(num_procs)]
results = [res.get() for res in multiple_results]
for r in results:
    print(r[0], r[1])

print(f"Done at {datetime.now()}")
print(f"Time taken = {time.perf_counter() - start}s")

Вот результаты:

num_processes total_time_taken
1 28.25
2 14.28
3 10.2
4 7.35
5 7.89
6 8.03
7 8.41
8 8.72
9 8.75
16 8.7
40 9.53

Для меня имеет смысл следующее:

  • Запуск одного процесса за раз занимает около 0,7 секунды для каждого процесса, поэтому запуск 40 должен занять около 28 с, что согласуется с тем, что мы наблюдаем выше.
  • Запуск 2 процессов за один раз должен вдвое сократить время, а это наблюдается выше (~ 14 с).
  • Запуск 4 процессов за один раз должен еще вдвое сократить время, и это наблюдается выше (~ 7 с).
  • Увеличение параллелизма больше, чем количество ядер (8) должно снизить производительность (из-за конфликта ЦП), и это наблюдается (вроде).

Что не имеет смысла:

  • Почему параллельная работа 8 не в два раза быстрее, чем параллельная 4, т.е. почему это не ~ 3,5 с?
  • Почему параллельно работает 5–8 одновременно? или чем работает 4 за раз? Есть 8 ядер, но все же почему общее время работы хуже? (При параллельном запуске 8 htop показал, что все процессоры загружены почти на 100%. При параллельном запуске 4 только 4 из них имели 100%, что имеет смысл.)

Ответы [ 2 ]

2 голосов
/ 05 марта 2020

Q : " Почему работает 5–8 параллельно одновременно хуже, чем 4 одновременно?"

Ну,
есть несколько причин, и мы начнем со стати c, самой простой наблюдаемой:

Поскольку кремниевый дизайн (для чего они использовали несколько аппаратных приемов)
не масштабируется за пределы 4.

Итак последний Закон Амдала объяснено и повышено ускорение от просто +1 увеличенного числа процессоров равно 4, и любое следующее +1 не будет повышать производительность так же, как в случае {2, 3, 4}:

Эта карта топологии процессора lstopo помогает начать декодирование WHY (здесь для 4-ядерных, но логика c такая же, как для вашего 8-ядерного кремния - запустите lstopo на вашем устройстве, чтобы увидеть более подробную информацию в естественных условиях):

┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Machine (31876MB)                                                                                                 │
│                                                                                                                   │
│ ┌────────────────────────────────────────────────────────────┐                      ┌───────────────────────────┐ │
│ │ Package P#0                                                │  ├┤╶─┬─────┼┤╶───────┤ PCI 10ae:1F44             │ │
│ │                                                            │      │               │                           │ │
│ │ ┌────────────────────────────────────────────────────────┐ │      │               │ ┌────────────┐  ┌───────┐ │ │
│ │ │ L3 (8192KB)                                            │ │      │               │ │ renderD128 │  │ card0 │ │ │
│ │ └────────────────────────────────────────────────────────┘ │      │               │ └────────────┘  └───────┘ │ │
│ │                                                            │      │               │                           │ │
│ │ ┌──────────────────────────┐  ┌──────────────────────────┐ │      │               │ ┌────────────┐            │ │
│ │ │ L2 (2048KB)              │  │ L2 (2048KB)              │ │      │               │ │ controlD64 │            │ │
│ │ └──────────────────────────┘  └──────────────────────────┘ │      │               │ └────────────┘            │ │
│ │                                                            │      │               └───────────────────────────┘ │
│ │ ┌──────────────────────────┐  ┌──────────────────────────┐ │      │                                             │
│ │ │ L1i (64KB)               │  │ L1i (64KB)               │ │      │               ┌───────────────┐             │
│ │ └──────────────────────────┘  └──────────────────────────┘ │      ├─────┼┤╶───────┤ PCI 10bc:8268 │             │
│ │                                                            │      │               │               │             │
│ │ ┌────────────┐┌────────────┐  ┌────────────┐┌────────────┐ │      │               │ ┌────────┐    │             │
│ │ │ L1d (16KB) ││ L1d (16KB) │  │ L1d (16KB) ││ L1d (16KB) │ │      │               │ │ enp2s0 │    │             │
│ │ └────────────┘└────────────┘  └────────────┘└────────────┘ │      │               │ └────────┘    │             │
│ │                                                            │      │               └───────────────┘             │
│ │ ┌────────────┐┌────────────┐  ┌────────────┐┌────────────┐ │      │                                             │
│ │ │ Core P#0   ││ Core P#1   │  │ Core P#2   ││ Core P#3   │ │      │     ┌──────────────────┐                    │
│ │ │            ││            │  │            ││            │ │      ├─────┤ PCI 1002:4790    │                    │
│ │ │ ┌────────┐ ││ ┌────────┐ │  │ ┌────────┐ ││ ┌────────┐ │ │      │     │                  │                    │
│ │ │ │ PU P#0 │ ││ │ PU P#1 │ │  │ │ PU P#2 │ ││ │ PU P#3 │ │ │      │     │ ┌─────┐  ┌─────┐ │                    │
│ │ │ └────────┘ ││ └────────┘ │  │ └────────┘ ││ └────────┘ │ │      │     │ │ sr0 │  │ sda │ │                    │
│ │ └────────────┘└────────────┘  └────────────┘└────────────┘ │      │     │ └─────┘  └─────┘ │                    │
│ └────────────────────────────────────────────────────────────┘      │     └──────────────────┘                    │
│                                                                     │                                             │
│                                                                     │     ┌───────────────┐                       │
│                                                                     └─────┤ PCI 1002:479c │                       │
│                                                                           └───────────────┘                       │
└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

Более пристальный взгляд, как тот, что из звонка на hwloc -инструмент: lstopo-no-graphics -.ascii, показывает где взаимный пр независимость обработки заканчивается - здесь на уровне shared L1 -inchin-cache (единица L3 также используется совместно, но на вершине иерархии и на таком уровне размер, который беспокоит только для решения больших проблем, а не в нашем случае)


Далее следует худшая наблюдаемая причина ПОЧЕМУ еще хуже на 8 процессах:

Q : "Почему параллельная работа 8 не в два раза быстрее, чем параллельная 4, т.е. почему это не ~3.5s?"

Из-за теплового управления . enter image description here

Чем больше нагрузок загружено на ядра процессора, тем больше тепла вырабатывается движущимися электронами на ~3.5+ GHz через кремниевый лабиринт. Тепловые ограничения - это те, которые препятствуют дальнейшему повышению производительности вычислительных мощностей ЦП, просто потому, что физика L aws, как мы их знаем, не позволяет расти за пределами определенных материальными пределами.

Так что же будет дальше?
Дизайн ЦП обошел не физику (это невозможно), а нас, пользователей - обещая нам чип ЦП с ~3.5+ GHz (но на самом деле ЦП может использовать эту тактовую частоту только в течение небольших промежутков времени - пока рассеянное тепло не приблизит кремний к температурным пределам), а затем ЦП решит либо уменьшить свою собственную тактовую частоту как защитный шаг при перегреве (это снижает производительность, не так ли?) или некоторые микроархитектуры ЦП могут перепрыгивать (перемещать поток обработки) на другой, свободное, а значит, круче, процессорное ядро ​​(которое обещает более высокую тактовую частоту там (по крайней мере, в течение небольшого промежутка времени) , но также снижает производительность ce, поскольку скачок не происходит в нулевое время и не происходит при нулевых затратах (потери в кеше, повторные выборки и т. д. c)

На этом снимке показан снимок ядра прыжок - ядра 0-19 слишком горячие и находятся под крышкой термодросселирования, тогда как ядра 20-39 могут (по крайней мере, на данный момент) работать на полной скорости:

enter image description here


Результат?

Оба температурных ограничения (погружение ЦП в пул жидкого азота было продемонстрировано для «популярного» журнального шоу, но пока не разумный вариант для любых устойчивых вычислений, так как механическое напряжение от перехода из глубокой заморозки в парообразующий суперобогреватель 6+ GHz с тактовой частотой трескает корпус ЦП и приводит к ЦП смерть от трещин и механической усталости, за исключением нескольких эпизодов рабочей нагрузки - так что зона без go, потому что отрицательный ROI для любого (не YouTube-мания) серьезно означал Project).

Хорошее охлаждение и правильный размер бассейна -производство, основанное на предварительном тестировании in vivo, является единственной верной ставкой здесь.

Другая архитектура:

enter image description here

1 голос
/ 05 марта 2020

Наиболее вероятной причиной является то, что вы запускаете программу на ЦП, который использует одновременную многопоточность (SMT) , более известную как гиперпоточность на устройствах Intel. Чтобы процитировать после вики для каждого физического ядра процессора, операционная система обращается к двум виртуальным (логическим) ядрам и делит рабочую нагрузку между ними, когда это возможно. Вот что здесь происходит.

Ваша ОС говорит о 8 ядрах, но на самом деле это 4 ядра с SMT. Задача явно связана с процессором, поэтому любое увеличение числа ядер, превышающее физическое , не приносит никакой выгоды, только накладные расходы на многопроцессорность. Вот почему вы видите почти линейное увеличение производительности, пока не достигнете (физического!) Макс. количество ядер (4), а затем уменьшается, когда необходимо совместно использовать ядра для этой задачи, очень интенсивно использующей процессор.

...