Использование 100% всех ядер с многопроцессорным модулем

У меня есть две части кода, которые я использую, чтобы узнать о многопроцессорности в Python 3.1. Моя цель - использовать 100% всех доступных процессоров. Однако фрагменты кода здесь достигают только 30% - 50% на всех процессорах.

Есть ли какой-либо способ заставить "питона" использовать все 100%? ОС (windows 7, 64bit) ограничивает доступ Python к процессорам? Пока выполняются приведенные ниже фрагменты кода, я открываю диспетчер задач и наблюдаю всплеск процессора, но никогда не достигаю и не поддерживаю 100%. В дополнение к этому, я вижу несколько процессов python.exe, созданных и уничтоженных по пути. Как эти процессы связаны с процессорами? Например, если я порождаю 4 процесса, каждый процесс не использует свое собственное ядро. Вместо этого, какие процессы используют? Они разделяют все ядра? И если так, то ОС заставляет процессы делить ядра?

фрагмент кода 1

import multiprocessing

def worker():
    #worker function
    print ('Worker')
    x = 0
    while x < 1000:
        print(x)
        x += 1
    return

if __name__ == '__main__':
    jobs = []
    for i in range(50):
        p = multiprocessing.Process(target=worker)
        jobs.append(p)
        p.start()

фрагмент кода 2

from multiprocessing import Process, Lock

def f(l, i):
    l.acquire()
    print('worker ', i)
    x = 0
    while x < 1000:
        print(x)
        x += 1
    l.release()

if __name__ == '__main__': 
    lock = Lock()
    for num in range(50):
        Process(target=f, args=(lock, num)).start()

5 ответов

Решение

Чтобы использовать 100% всех ядер, не создавайте и не разрушайте новые процессы.

Создайте несколько процессов на ядро ​​и свяжите их с конвейером.

На уровне ОС все конвейерные процессы выполняются одновременно.

Чем меньше вы пишете (и тем больше вы делегируете ОС), тем больше у вас шансов использовать как можно больше ресурсов.

python p1.py | python p2.py | python p3.py | python p4.py ...

Будет максимально использовать ваш процессор.

Ты можешь использовать psutil закрепить каждый процесс, порожденный multiprocessing на конкретный процессор:

import multiprocessing as mp
import psutil


def spawn():
    procs = list()
    n_cpus = psutil.cpu_count()
    for cpu in range(n_cpus):
        affinity = [cpu]
        d = dict(affinity=affinity)
        p = mp.Process(target=run_child, kwargs=d)
        p.start()
        procs.append(p)
    for p in procs:
        p.join()
        print('joined')

def run_child(affinity):
    proc = psutil.Process()  # get self pid
    print('PID: {pid}'.format(pid=proc.pid))
    aff = proc.cpu_affinity()
    print('Affinity before: {aff}'.format(aff=aff))
    proc.cpu_affinity(affinity)
    aff = proc.cpu_affinity()
    print('Affinity after: {aff}'.format(aff=aff))


if __name__ == '__main__':
    spawn()

Примечание: как прокомментировано, psutil.Process.cpu_affinity недоступно в macOS.

Минимальный пример:

def f(x):
    while 1:
        if (x * x) ^ 1 + 1 >= 1:  # run all parts
            pass  # but it is just pointless

import multiprocessing as mp
n_thread = mp.cpu_count()
with mp.Pool(n_thread) as p:
    p.map(f, range(n_thread))

Вариант использования: чтобы нагреть машину в холодный день (смените внутреннюю часть цикла "время" на что-то менее бессмысленное.)

Относительно фрагмента кода 1. Сколько ядер / процессоров у вас на тестовой машине? Бесполезно запускать 50 из этих процессов, если у вас только 2 ядра процессора. Фактически вы заставляете ОС тратить больше времени на переключение контекста, чтобы перевести процессы на процессор и выключать его, чем на фактическую работу.

Попробуйте уменьшить количество порождаемых процессов до количества ядер. Так что "для меня в диапазоне (50):" должно стать что-то вроде:

import os;
# assuming you're on windows:
for i in range(int(os.environ["NUMBER_OF_PROCESSORS"])):
    ...

Относительно фрагмента кода 2: Вы используете многопроцессорную блокировку, которая может удерживаться только одним процессом за раз, поэтому вы полностью ограничиваете параллелизм в этой версии программы. Вы сериализовали вещи так, что процесс с 1 по 50 запускается, случайный процесс (скажем, процесс 7) получает блокировку. Процессы 1-6 и 8-50 все сидят на линии:

l.acquire()

Пока они сидят там, они просто ждут, пока замок будет снят. В зависимости от реализации примитива Lock они, вероятно, не используют какой-либо ЦП, они просто сидят там, используя системные ресурсы, такие как ОЗУ, но не выполняют никакой полезной работы с ЦП. Процесс 7 считает и печатает до 1000, а затем снимает блокировку. Затем ОС может произвольно планировать запуск одного из оставшихся 49 процессов. Какой бы он ни проснулся первым, он получит замок следующим и побежит, пока оставшиеся 48 ждут на замке. Это будет продолжаться для всей программы.

По сути, фрагмент кода 2 является примером того, что затрудняет параллелизм. Вы должны управлять доступом большого количества процессов или потоков к некоторому общему ресурсу. В этом конкретном случае действительно нет причин, чтобы эти процессы должны были ждать друг друга.

Таким образом, Snippet 1 ближе к более эффективному использованию процессора. Я думаю, что правильная настройка количества процессов для соответствия количеству ядер даст намного улучшенный результат.

Я бы рекомендовал использовать библиотеку Joblib, это хорошая библиотека для многопроцессорной обработки, используемая во многих приложениях ML, в sklearn и т. Д.

from joblib import Parallel, delayed

Parallel(n_jobs=-1, prefer="processes", verbose=6)(
    delayed(function_name)(parameter1, parameter2, ...)
    for parameter1, parameter2, ... in object
)

куда n_jobsколичество одновременных работ. Задаватьn=-1 если вы хотите использовать все доступные ядра на машине, на которой выполняется ваш код.

Подробнее о параметрах здесь: https://joblib.readthedocs.io/en/latest/generated/joblib.Parallel.html

В вашем случае возможная реализация:

def worker(i):
    print('worker ', i)
    x = 0
    while x < 1000:
        print(x)
        x += 1

Parallel(n_jobs=-1, prefer="processes", verbose=6)(
        delayed(worker)(num)
        for num in range(50)
    )

Чтобы ответить на ваш вопрос (ы):

Есть ли какой-либо способ заставить "питона" использовать все 100%?

Не то чтобы я слышал о

ОС (windows 7, 64bit) ограничивает доступ Python к процессорам?

Да и Нет, да: если это питон взял 100%, окна будут зависать. Нет, вы можете предоставить привилегии администратора Python, что приведет к блокировке.

Как эти процессы связаны с процессорами?

На техническом уровне ОС они не являются теми "процессами" python, которые являются потоками, которые обрабатываются обработчиком ОС, так как он требует обработки.

Вместо этого, какие процессы используют? Они разделяют все ядра? И если так, то ОС заставляет процессы делить ядра?

Они совместно используют все ядра, если только вы не запустите один экземпляр Python, для которого привязка установлена ​​к определенному ядру (в многоядерной системе), ваши процессы будут разделены на обработку, которая когда-либо свободна от ядра. Так что да, ОС по умолчанию форсирует совместное использование ядра (или технически Python)

если вас интересует сходство с ядром python, посмотрите пакет affinity для python.

Другие вопросы по тегам