web-dev-qa-db-fra.com

Utilisation de 100% de tous les cœurs avec le module de multitraitement

J'utilise deux morceaux de code pour en savoir plus sur le multitraitement dans Python 3.1. Mon objectif est d’utiliser 100% de tous les processeurs disponibles . Cependant, les extraits de code n’atteignent ici que 30% à 50% de tous les processeurs.

Est-il possible de forcer python à utiliser tous les 100%? Le système d'exploitation (Windows 7, 64bit) limite-t-il l'accès de Python aux processeurs? Pendant l'exécution des extraits de code ci-dessous, observez le pic du processeur, mais n'atteignez et ne maintenez jamais à 100% . De plus, je peux voir plusieurs processus python.exe créés et détruits en cours de route. Quel est le lien entre ces processus et les processeurs? Par exemple, si je génère 4 processus, chaque processus n'utilise pas son propre noyau. Au lieu de cela, quels sont les processus utilisent? Partagent-ils tous les cœurs? Et si oui, est-ce le système d'exploitation qui oblige les processus à partager les cœurs?

extrait de code 1

import multiprocessing

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

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

extrait de code 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 __== '__main__': 
    lock = Lock()
    for num in range(50):
        Process(target=f, args=(lock, num)).start()
36
Ggggggg

Pour utiliser 100% de tous les cœurs, ne créez et ne détruisez pas de nouveaux processus.

Créez quelques processus par cœur et associez-les à un pipeline.

Au niveau du système d'exploitation, tous les processus en pipeline s'exécutent simultanément.

Moins vous écrivez (et plus vous déléguez au système d'exploitation), plus vous avez de chances d'utiliser autant de ressources que possible.

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

Fera une utilisation maximale de votre processeur.

35
S.Lott

Vous pouvez utiliser psutil to épingler chaque processus généré par multiprocessing à un processeur spécifique:

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 __== '__main__':
    spawn()

Remarque: Comme indiqué, psutil.Process.cpu_affinity n'est pas disponible sur macOS.

11
Ioannis Filippidis

En ce qui concerne l'extrait de code 1: combien de cœurs/processeurs avez-vous sur votre machine de test? Exécuter 50 de ces processus ne vous sert à rien si vous ne disposez que de 2 cœurs de processeur. En fait, vous forcez le système d'exploitation à passer plus de temps à la commutation de contexte pour déplacer les processus sur le processeur que sur le travail réel.

Essayez de réduire le nombre de processus générés au nombre de cœurs. Ainsi, "pour i dans la plage (50):" devrait devenir quelque chose comme:

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

En ce qui concerne l'extrait de code 2: vous utilisez un multitraitement.Lock qui ne peut être tenu que par un seul processus à la fois, ce qui limite totalement le parallélisme de cette version du programme. Vous avez sérialisé des éléments pour que les processus 1 à 50 démarrent, un processus aléatoire (par exemple, processus 7) acquiert le verrou. Les processus 1-6 et 8-50 sont tous sur la ligne:

l.acquire()

Pendant qu'ils sont assis, ils attendent juste que la serrure soit libérée. Selon l’implémentation de la primitive Lock, ils n’utilisent probablement aucun processeur, ils utilisent simplement des ressources système telles que RAM, mais ne font aucun travail utile avec le processeur. Process 7 compte et imprime à 1000, puis libère le verrou. Le système d'exploitation est alors libre de planifier de manière aléatoire l'exécution de l'un des 49 processus restants. Celui qui se réveille le premier acquiert le verrou suivant et s'exécute pendant que les 48 autres attendent sur le verrou. Cela va continuer pour l'ensemble du programme. 

Fondamentalement, l'extrait de code 2 est un exemple de ce qui rend difficile l'accès simultané. Vous devez gérer l'accès de nombreux processus ou threads à une ressource partagée. Dans ce cas particulier, il n’ya vraiment aucune raison pour que ces processus s’attendent les uns les autres. 

Ainsi, Snippet 1 est plus proche d’une utilisation plus efficace de la CPU. Je pense que régler correctement le nombre de processus pour qu'il corresponde au nombre de cœurs donnera un résultat bien meilleur. 

6
stderr

Exemple minimum en Python pur:

def f(x):
    while 1:
        pass  # infinite loop

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

Utilisation: pour se réchauffer par une journée froide (mais n'hésitez pas à changer la boucle pour quelque chose de moins inutile.)

Attention: pour sortir, ne tirez pas sur la fiche, ne maintenez pas le bouton d'alimentation enfoncé, Ctrl-C à la place.

2
THN