Salut, Khabrovsk. En ce moment, OTUS a ouvert un ensemble pour le cours de Machine Learning , à cet égard, nous avons traduit pour vous un «conte de fées» trÚs intéressant. Aller.
Il était une fois, dans une galaxie lointaine, lointaine ... Unsorcier sage et puissant vivait dans un petit village au milieu du désert. Et leur nom était Dumbledalf. Non seulement il était sage et puissant, mais il a également aidé des gens venus de pays lointains à demander l'aide d'un sorcier. Notre histoire a commencé lorsqu'un voyageur a apporté au sorcier un parchemin magique. Le voyageur ne savait pas ce qu'il y avait dans le parchemin, il savait seulement que si quelqu'un pouvait révéler tous les secrets du parchemin, alors c'était Dumbledalf.Chapitre 1: Single-Thread, Single-Process
Si vous n'avez pas encore deviné, j'ai fait une analogie avec le processeur et ses fonctions. Notre assistant est un processeur, et un parchemin est une liste de liens qui mÚnent à la puissance et aux connaissances de Python pour le maßtriser.La premiÚre pensée d'un sorcier qui a déchiffré la liste sans aucune difficulté a été d'envoyer son fidÚle ami (Garrigorn? Je sais, je sais que cela semble horrible) à chacun des endroits décrits dans le parchemin afin de trouver et d'apporter ce qu'il pouvait y trouver.In [1]:
import urllib.request
from concurrent.futures import ThreadPoolExecutor
In [2]:
urls = [
'http://www.python.org',
'https://docs.python.org/3/',
'https://docs.python.org/3/whatsnew/3.7.html',
'https://docs.python.org/3/tutorial/index.html',
'https://docs.python.org/3/library/index.html',
'https://docs.python.org/3/reference/index.html',
'https://docs.python.org/3/using/index.html',
'https://docs.python.org/3/howto/index.html',
'https://docs.python.org/3/installing/index.html',
'https://docs.python.org/3/distributing/index.html',
'https://docs.python.org/3/extending/index.html',
'https://docs.python.org/3/c-api/index.html',
'https://docs.python.org/3/faq/index.html'
]
In [3]:
%%time
results = []
for url in urls:
with urllib.request.urlopen(url) as src:
results.append(src)
CPU times: user 135 ms, sys: 283 ”s, total: 135 ms
Wall time: 12.3 s
In [ ]:
Comme vous pouvez le voir, nous parcourons simplement les URL une par une en utilisant la boucle for et lisons la réponse. Grùce à %% time et à la magie d' IPython , nous pouvons voir que cela a pris environ 12 secondes avec mon triste Internet.Chapitre 2: Multithreading
Ce n'Ă©tait pas sans raison que le sorcier Ă©tait cĂ©lĂšbre pour sa sagesse; il a rapidement rĂ©ussi Ă trouver un moyen beaucoup plus efficace. Au lieu d'envoyer une personne Ă chaque endroit dans l'ordre, pourquoi ne pas rĂ©unir une Ă©quipe d'associĂ©s fiables et les envoyer dans diffĂ©rentes parties du monde en mĂȘme temps! L'assistant pourra unir toutes les connaissances qu'il apporte en mĂȘme temps!C'est vrai, au lieu d'afficher la liste dans une boucle en sĂ©quence, nous pouvons utiliser le multithreading pour accĂ©der Ă plusieurs URL Ă la fois.In [1]:
import urllib.request
from concurrent.futures import ThreadPoolExecutor
In [2]:
urls = [
'http://www.python.org',
'https://docs.python.org/3/',
'https://docs.python.org/3/whatsnew/3.7.html',
'https://docs.python.org/3/tutorial/index.html',
'https://docs.python.org/3/library/index.html',
'https://docs.python.org/3/reference/index.html',
'https://docs.python.org/3/using/index.html',
'https://docs.python.org/3/howto/index.html',
'https://docs.python.org/3/installing/index.html',
'https://docs.python.org/3/distributing/index.html',
'https://docs.python.org/3/extending/index.html',
'https://docs.python.org/3/c-api/index.html',
'https://docs.python.org/3/faq/index.html'
]
In [4]:
%%time
with ThreadPoolExecutor(4) as executor:
results = executor.map(urllib.request.urlopen, urls)
CPU times: user 122 ms, sys: 8.27 ms, total: 130 ms
Wall time: 3.83 s
In [5]:
%%time
with ThreadPoolExecutor(8) as executor:
results = executor.map(urllib.request.urlopen, urls)
CPU times: user 122 ms, sys: 14.7 ms, total: 137 ms
Wall time: 1.79 s
In [6]:
%%time
with ThreadPoolExecutor(16) as executor:
results = executor.map(urllib.request.urlopen, urls)
CPU times: user 143 ms, sys: 3.88 ms, total: 147 ms
Wall time: 1.32 s
In [ ]:
Bien mieux! Presque comme ... de la magie. L'utilisation de plusieurs threads peut accĂ©lĂ©rer considĂ©rablement de nombreuses tĂąches d'E / S. Dans mon cas, la plupart du temps passĂ© Ă lire les URL est dĂ» Ă la latence du rĂ©seau. Les programmes liĂ©s aux E / S passent la majeure partie de leur vie Ă attendre, vous l'aurez devinĂ©, une entrĂ©e ou une sortie (tout comme un sorcier attend que ses amis se rendent Ă des endroits du dĂ©filement et reviennent). Cela peut ĂȘtre entrĂ© / sorti Ă partir du rĂ©seau, de la base de donnĂ©es, du fichier ou de l'utilisateur. Ces E / S prennent gĂ©nĂ©ralement beaucoup de temps, car la source peut avoir besoin d'effectuer un prĂ©traitement avant de transfĂ©rer les donnĂ©es vers les E / S. Par exemple, un processeur comptera beaucoup plus rapidement qu'une connexion rĂ©seau transmettra des donnĂ©es (Ă une vitesse d'environcomme Flash contre votre grand-mĂšre).Remarque : multithreading
peut ĂȘtre trĂšs utile dans des tĂąches telles que le nettoyage de pages Web.Chapitre 3: Multiprocessing
Les annĂ©es ont passĂ©, la renommĂ©e du bon sorcier a grandi, et avec elle a grandi l'envie d'un sorcier noir impartial (Sarumort? Ou peut-ĂȘtre Volandeman?). ArmĂ© d'une ruse incommensurable et poussĂ© par l'envie, le sorcier noir jeta une terrible malĂ©diction sur Dumbledalf. Lorsque la malĂ©diction le rattrapa, Dumbledalf se rendit compte qu'il n'avait que quelques instants pour le repousser. DĂ©sespĂ©rĂ©, il fouilla dans ses livres de sorts et trouva rapidement un contre-sort qui Ă©tait censĂ© fonctionner. Le seul problĂšme Ă©tait que l'assistant devait calculer la somme de tous les nombres premiers infĂ©rieurs Ă 1 000 000. Un sort Ă©trange, bien sĂ»r, mais ce que nous avons.Le sorcier savait que calculer la valeur serait trivial s'il avait assez de temps, mais il n'avait pas ce luxe. MalgrĂ© le fait qu'il soit un grand sorcier, il est limitĂ© par son humanitĂ© et ne peut vĂ©rifier la simplicitĂ© qu'un seul numĂ©ro Ă la fois. S'il dĂ©cidait de simplement additionner les nombres premiers les uns aprĂšs les autres, cela prendrait trop de temps. Lorsqu'il ne restait que quelques secondes avant d'appliquer le contre-sort , il se souvint soudain du sort de multi- traitement , qu'il avait appris du parchemin magique il y a de nombreuses annĂ©es. Ce sort lui permettra de se copier afin de rĂ©partir les nombres entre ses copies et d'en vĂ©rifier plusieurs en mĂȘme temps. Et Ă la fin, tout ce qu'il doit faire, c'est simplement additionner les chiffres que lui et ses copies dĂ©couvriront.In [1]:
from multiprocessing import Pool
In [2]:
def if_prime(x):
if x <= 1:
return 0
elif x <= 3:
return x
elif x % 2 == 0 or x % 3 == 0:
return 0
i = 5
while i**2 <= x:
if x % i == 0 or x % (i + 2) == 0:
return 0
i += 6
return x
In [17]:
%%time
answer = 0
for i in range(1000000):
answer += if_prime(i)
CPU times: user 3.48 s, sys: 0 ns, total: 3.48 s
Wall time: 3.48 s
In [18]:
%%time
if __name__ == '__main__':
with Pool(2) as p:
answer = sum(p.map(if_prime, list(range(1000000))))
CPU times: user 114 ms, sys: 4.07 ms, total: 118 ms
Wall time: 1.91 s
In [19]:
%%time
if __name__ == '__main__':
with Pool(4) as p:
answer = sum(p.map(if_prime, list(range(1000000))))
CPU times: user 99.5 ms, sys: 30.5 ms, total: 130 ms
Wall time: 1.12 s
In [20]:
%%timeit
if __name__ == '__main__':
with Pool(8) as p:
answer = sum(p.map(if_prime, list(range(1000000))))
729 ms ± 3.02 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [21]:
%%timeit
if __name__ == '__main__':
with Pool(16) as p:
answer = sum(p.map(if_prime, list(range(1000000))))
512 ms ± 39.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [22]:
%%timeit
if __name__ == '__main__':
with Pool(32) as p:
answer = sum(p.map(if_prime, list(range(1000000))))
518 ms ± 13.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [23]:
%%timeit
if __name__ == '__main__':
with Pool(64) as p:
answer = sum(p.map(if_prime, list(range(1000000))))
621 ms ± 10.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [ ]:
Les processeurs modernes ont plus d'un cĆur, nous pouvons donc accĂ©lĂ©rer les tĂąches en utilisant le multiprocessing du module de traitement multiprocess . Les tĂąches associĂ©es au processeur sont des programmes qui effectuent la plupart du temps des calculs sur le processeur (calculs mathĂ©matiques triviaux, traitement d'image, etc.). Si les calculs peuvent ĂȘtre effectuĂ©s indĂ©pendamment les uns des autres, nous avons la possibilitĂ© de les diviser entre les cĆurs de processeur disponibles, obtenant ainsi une augmentation significative de la vitesse de traitement.Tout ce que tu dois faire est:- DĂ©terminez la fonction Ă appliquer.
- Préparer une liste d'éléments auxquels la fonction sera appliquée;
multiprocessing.Pool
. , Pool()
, . with
, .- Pool
map
. map , , .
Remarque : Une fonction peut ĂȘtre dĂ©finie pour effectuer n'importe quelle tĂąche pouvant ĂȘtre effectuĂ©e en parallĂšle. Par exemple, une fonction peut contenir du code pour Ă©crire le rĂ©sultat d'un calcul dans un fichier.Alors, pourquoi devons-nous sĂ©parermultiprocessing
etmultithreading
? Si vous avez dĂ©jĂ essayĂ© d'amĂ©liorer les performances d'une tĂąche sur le processeur en utilisant le multithreading, le rĂ©sultat est exactement le contraire. C'est tout simplement terrible! Voyons comment cela s'est produit.Tout comme un assistant est limitĂ© par sa nature humaine et ne peut calculer qu'un seul nombre par unitĂ© de temps, Python est livrĂ© avec une chose appelĂ©e Global Interpreter Lock (GIL) . Python est heureux de vous permettre de gĂ©nĂ©rer autant de threads que vous le souhaitez, mais GILgarantit qu'un seul de ces threads s'exĂ©cutera Ă un moment donnĂ©.Pour une tĂąche liĂ©e aux E / S, cette situation est tout Ă fait normale. Un thread envoie une demande Ă une URL et attend une rĂ©ponse, alors ce thread peut ĂȘtre remplacĂ© par un autre, qui enverra une autre demande Ă une URL diffĂ©rente. Ătant donnĂ© que le thread n'a rien Ă faire jusqu'Ă ce qu'il reçoive une rĂ©ponse, cela ne fait aucune diffĂ©rence qu'Ă un moment donnĂ©, un seul thread est en cours d'exĂ©cution.Pour les tĂąches effectuĂ©es sur le processeur, avoir plusieurs threads est presque aussi inutile que les mamelons dans l'armure. Ătant donnĂ© qu'un seul thread peut ĂȘtre exĂ©cutĂ© par unitĂ© de temps, mĂȘme si vous en gĂ©nĂ©rez plusieurs, chacun se verra attribuer un numĂ©ro pour vĂ©rifier la simplicitĂ©, le processeur fonctionnera toujours avec un seul thread. En fait, ces chiffres seront toujours vĂ©rifiĂ©s un par un. Et la surcharge de travail avec plusieurs threads contribuera Ă rĂ©duire les performances, ce que vous pouvez simplement observer lorsqu'il est utilisĂ© multithreading
dans des tùches effectuées sur le processeur.Pour contourner cette «restriction», nous utilisons le modulemultiprocessing
. Au lieu d'utiliser des threads, le multitraitement utilise, comme vous le dites ... plusieurs processus. Chaque processus a son propre interprĂšte et son propre espace mĂ©moire, donc le GIL ne vous limitera pas. En fait, chaque processus utilisera son propre cĆur de processeur et fonctionnera avec son propre numĂ©ro unique, et cela sera effectuĂ© simultanĂ©ment avec le travail d'autres processus. Comme c'est gentil de leur part!Vous remarquerez peut-ĂȘtre que la charge du processeur sera plus Ă©levĂ©e lorsque vous l'utiliserez multiprocessing
par rapport Ă une boucle for rĂ©guliĂšre ou mĂȘme multithreading
. Cela se produit car votre programme utilise non pas un cĆur, mais plusieurs. Et c'est bien!rappelez-vous, quemultiprocessing
Il a ses propres frais généraux de gestion de plusieurs processus, ce qui est généralement plus grave que le coût du multithreading. (Le multitraitement génÚre des interprÚtes séparés et attribue sa propre zone de mémoire à chaque processus, alors oui!) C'est-à -dire, en rÚgle générale, il est préférable d'utiliser une version allégée du multithreading lorsque vous souhaitez sortir de cette maniÚre (rappelez-vous des tùches liées aux E / S). Mais lorsque le calcul sur le processeur devient un goulot d'étranglement, l'heure du module arrive multiprocessing
. Mais souvenez-vous qu'avec une grande puissance, il y a une grande responsabilitĂ©.Si vous gĂ©nĂ©rez plus de processus que votre processeur ne peut en traiter par unitĂ© de temps, vous remarquerez que les performances commenceront Ă dĂ©cliner. Cela se produit car le systĂšme d'exploitation a besoin de faire plus de travail en mĂ©langeant les processus entre les cĆurs de processeur, car il y a plus de processus. En rĂ©alitĂ©, tout peut ĂȘtre encore plus compliquĂ© que je ne vous l'ai dit aujourd'hui, mais j'ai vĂ©hiculĂ© l'idĂ©e principale. Par exemple, sur mon systĂšme, les performances chutent lorsque le nombre de processus est de 16. C'est parce qu'il n'y a que 16 cĆurs logiques dans mon processeur.Chapitre 4: Conclusion
- Dans les tùches liées aux E / S, cela
multithreading
peut améliorer les performances. - Dans les tùches liées aux E / S,
multiprocessing
cela peut également augmenter la productivité, mais les coûts sont généralement plus élevés que lors de l'utilisation multithreading
. - L'existence de Python GIL nous montre clairement qu'à tout moment dans un programme, un seul thread peut s'exécuter.
- Dans les tùches liées au processeur, l'utilisation
multithreading
peut réduire les performances. - Dans les tùches liées au processeur, l'utilisation
multiprocessing
peut améliorer les performances. - Les sorciers sont géniaux!
C'est lĂ que nous terminerons notre introduction Ă multithreading
et multiprocessing
en Python aujourd'hui . Maintenant, allez gagner!
"Modélisation de COVID-19 à l'aide d'une analyse graphique et d'une analyse des données ouvertes." Leçon gratuite.