https://www.linuxjournal.com/content/multiprocessing-python
Hátha hasznos valakinek.
- 1760 megtekintés
Hozzászólások
ezt is megnezheted: http://zguide.zeromq.org/py:all
- A hozzászóláshoz be kell jelentkezni
Régebbi méréseim szerint a threading és a multiprocessing bár sok szálra dob szét, mégsem kapsz hamarabb eredményt. Átverésnek érzem ezeket.
Jobban jársz, ha a Python kódot egyszálasra hagyod és PyPy-vel futtatod.
Persze azóta lehet, hogy jobb lett.
- A hozzászóláshoz be kell jelentkezni
Volt egy kis projectem, ahol ciklusban numpy számolt, majd fájlba tárolta és megjelenítette az eredményt. Sorosan implementálva 3s volt a ciklusidő, multiprocessing-gel 1.2s-ra lecsökkent. IO kezelésnél is hasznos lehet a multiprocessing.
- A hozzászóláshoz be kell jelentkezni
Nagy tömegű I/O esetében a multithreading is segíthet, ott kevésbé kavar be a GIL.
- A hozzászóláshoz be kell jelentkezni
A numpy az nyereség, de a multiprocessing számomra csekély nyereségnek tűnik Pyton esetén, amikor tényleg kell a tempó. Például:
https://pastebin.com/40aPrUMv
https://pastebin.com/1mz5DWpj - rustc-vel egyszerűen lefordítva (-O itt hülyére optimalizál, az itt most csalás lenne)
A futásidőben látható különbség magáért beszél.
- A hozzászóláshoz be kell jelentkezni
Nem feledkeztél meg a már említett GIL létezéséről?
Az nagyon nagy hátrány lehet, a Rust meg azt hiszem, mentes tőle.
Nomeg arról, hogy a Rust az gépi kód, a Python az interpreter. Esetleg nézd meg pypy-vel, ha működik! Valamennyit gyorsít a memóriában végzett műveleteken.
- A hozzászóláshoz be kell jelentkezni
Igen, a GIL miatt a Python nem profitál a CPU korlátolt, multi-thread kódból.
De multi-process-t nem nehezebb csinálni: https://pastebin.com/e4ffs2QD
Multi-Thread: 0.780s
Multi-Process: 0.232s
Single: 0.808s
- A hozzászóláshoz be kell jelentkezni
Igen, ez nagyon kihozza, mennyire gáz az a GIL :(
A multithreadnek sokkal gyorsabbnak illene lennie.
- A hozzászóláshoz be kell jelentkezni
Kíváncsiságból lefuttattam különböző Python interpretereken (Mindegyik teszt ugyanazon a i5 6600k procin, Win10-en futott, a stabilabb eredmény miatt az n-et átírtam 3000*10000 -re, illetve 3000*10000*100 -ra Cython esetében):
Érdekességek:
- Python 2 esetében WSL vs "natív" win32-es Python 2 között 2.5-3x sebességkülönbség van
- PyPy WSL vs "natív" win32-es PYPY között: 10-20x különbség
- Első Cython kísérletem volt, de fájdalommentesen ment a dolog (pip install cython, váltózótípusok definiálása)
- Az IronPython elengedi a GIL-t, de sebességben még nem az igazi
GIL nélküli hello.pyx fájl:
def hello(long thid, long n):
cdef long res = 0
with nogil:
res = _hello(thid, n)
print res
cdef inline long _hello(long thid, long n) nogil:
cdef long i = 0
cdef long res = 0
while i < n:
res += i
i += 1
return res
ezután az eredeti fájlba simán beimportáltam:
import pyximport
pyximport.install()
import hello
a thread indítást átírtam
hello.hello
function-re, mást nem módosítottam, python3 test_multi.py -val indítva a fordítást észrevétlenül végzi a Cython.
- A hozzászóláshoz be kell jelentkezni
Kipróbáltam egy (számomra) kevésbé ismert Jittert: https://numba.pydata.org/ eléggé meggyőző eredményekkel (3000*10000*100 futás esetében):
Python 3.6.4 + Numba * 100
Multi-Thread: 0.099s
Multi-Process: 0.097s
Single: 0.001s
- "from numba import jit" + "@jit" dekorátolon kívül mást nem módosítottam az eredeti forráskódon még a típusokat se definiáltam.
- Próbáltam növelni 3000*10000*10000*i -re az iterációszámot, de sehogy se tudtam elmozdítani 0.001s-ről, szóval valami durva optimalizációt csinál
Kicsit módosítottam a kódon, hogy kicselezzem az optimizert, így már mérhető volt az eredmény (n = 3000*10000*10)
@jit
def hello(thid, n):
i = 1
res = 1
while i < n:
res += i / res
i += 1
print(res)
Multi-Thread: 7.751s, Multi-Process: 2.015s, Single: 7.666s
@jit(nogil=True, fastmath=True) -re módosítva a dekorátort:
Numba: Multi-Thread: 1.677s, Multi-Process: 1.643s, Single: 6.167s
Pypy: Multi-Thread: 13.35s, Multi-Process: 3.422s, Single: 13.268s
Cython: Multi-Thread: 1.723s, Multi-Process: 1.846s, Single: 6.310s
Tud CUDA-ra meg AOT is fordítani, de azokat nem próbáltam. (Nincs GPU driver egyelőre WSL-ben)
Tehát módosítatlan forráskód mellett ugyanolyan gyors, mint a típus definíciókkal ellátott Cython kód, 2x gyorsabb, mint Pypy és a GIL elengedése is egyszerű.
Update: int-et double-ra cserélve a Cython kódba 3x gyorsulást eredményezett, így már hasonló eredményeket hozott, mint a numba
- A hozzászóláshoz be kell jelentkezni
Ügyes. Arra figyelj, hogy a for() ciklusba rejtett összegzést egy erősebb optimalizáció a fordítóban ma már gyakran kiszámolja.
C esetén ezért azt szoktam csinálni, hogy önállóan fordított fájlban van a számolás és az értékeket a meghívótól kapja, ami külön lesz fordítva és a linker már nem szimulálja végig.
A két fájlt tehát önállóan fordítom és csak a linkelés közös. Ellenkező esetben a fordító szimulátora -O2 esetén konstanst helyettesít be. Jópárszor átvert engem, ahogy a fenti Rust példában is megjegyeztem, hogy a -O szintén konstanst dobhat a ciklusok szolgai végrehajtása helyett.
FFT-nél az algoritmus annyira nyakatekert, hogy a fordító nem tudja végigszimulálni. Főleg ha külön fájlban van fordítva az FFT és másik fájlból jön rá az adathalmaz. Rust esetén crate-t ("lib") csináltam magából az FFT algoritmusból, hogy véletlenül se tudja összeoptimalizálni az őt meghívó programban levő adatokkal.
Illetve a másik jó megoldás, ha a feldolgozandó adatok külső adatfájlból jönnek. Ekkor viszont az I/O-t is beleméred. Lényeg, hogy a fordító ne tudja kiszámolni a ciklusok eredményét.
- A hozzászóláshoz be kell jelentkezni
Próbáltam úgy is, hogy a "res += i" -t számoló function az n-t input()-ból kapja, de úgy is 1ms lett a futásidő, szóval nem tudom, hogy ilyen esetben hogyan tudja kiszámolni előre.
Viszont, ha kicserélem if i % 100: res += i -re, akkor már rendes időt kapok (1.6s), szóval biztosan valami csalás van benne. :)
- A hozzászóláshoz be kell jelentkezni
Itt egy átverős példa:
#include <stdio.h>
void hello(long long n) {
long long i = 0;
long long res = 0;
while (i < n) {
res += i;
i += 1;
}
printf("result: %Lu\n", res);
}
int main() {
long long n = 3000*1000;
hello(n);
puts("Done!");
return 0;
}
Fordítsd le olvasható asm kódra és nézz bele.
$ gcc -O2 -S test.c
Meglesz a függvény és tényleg ciklust látsz benne. Hurrá.
De a főprogram cseszik meghívni. Ellenben:
movabsq $4499998500000, %rsi <---- és itt a végeredmény
xorl %eax, %eax
call printf@PLT
Ettől mindig tartok, hogy a hamar összedobott egyszerű tesztjeimnél átver egy mai normális fordító.
- A hozzászóláshoz be kell jelentkezni
Hát, jobb de nem az igazi. FFT benchmarkommal néztem imént J1900-as 4 magos HT mentes procin:
Egyszálas: Python3 --- 40 másodperc
Egyszálas: PyPy --- 2,29 másodperc
Multithread Python3 --- 10,9 másodperc ... rosszabb az egyszálas PyPy-nél.
Multithreas PyPy --- 2,78 másodperc ... kiterhelte a 4 CPU-t, de nem lett gyorsabb.
Rust simán egy szálon --- 0,35 másodperc
Rust 4 thread + SIMD --- 0,0466 másodperc
Még mindig úgy érzem, hogy ha sebességérzékeny a feladat, akkor nem a Python és a multithread-del való varázslás lesz a barátom. Inkább a PyPy egyszálon. Ha meg az sem elég, akkor a Rust, ami a szripthez képest egyetlen művelettel több (rustc -O teszt.rs).
- A hozzászóláshoz be kell jelentkezni
Izé... sebességérzékeny feladat esetén az interpreteres nyelvek általában kiesnek. Nem arra valók.
A pypy egy JIT-szerű eszköz, de épp azt néztem, hogy amit én próbáltam egy processzel és többel, azt egy processzes verzióban lényegesen gyorsabban futtatja, mint a multiprocesszes változatot. :)
És még1x: Rust-ot C-vel hasonlíts, ne Pythonnal! :)
- A hozzászóláshoz be kell jelentkezni
Attól függ, mit csinálsz. (történetesen a fenti cikkbe csak belebotlottam, nekem nem mondott sok újat)
Pár éve próbáltam egy a "tűzfal" (gyk: packet filter) logokat analizáló programot összedobni. Két magos, HT-s processzoron ha szekvenciálisan dolgoztam fel, jóval tovább tartott, mint amikor szétszedtem négy szálra, amiből egy olvasta a fájlt, tolta be a queue-ba, a többi meg boncolgatta a talált sorokat.
Ennyit találtam meg a maradványokból: https://github.com/haa-zee/python-sandbox/tree/master/probak/multiproce…
A multiprocess változat 4300000 soron 8, a szekvenciális 15 másodpercig futott a notebookomon.
De most direkt kipróbáltam, hogy egyetlen processzben megy a paralell feldolgozás, úgy már 18mp kell neki. (4-nél több nem gyorsít jelentősen, valahol 7.5mp körül van a leggyorsabb)
- A hozzászóláshoz be kell jelentkezni
En ugy tanultam, hogy attol fugg, van-e haszna, hogy mit probal a programod csinalni.
Ha egymastol fuggetlen tobb szamolast, pl. akkor ezeket kulon processzorok tudjak futtatni, es hamarabb jon eredmeny. Ha egy logikai sorban van minden, akkor egy proci fog izzadni, a tobbi meg malmozik.
Szamolason kivul mas dolgokra is irtak, hogy jo. Pl. van egy interaktiv szal meg egy masik, ami szamol vagy valami mas lassu dolgot csinal, es nem lassitja be az elsot. Ebben az esetben lehet, hogy nem kapsz hamarabb eredmenyt, de a felhasznaloi elmeny jobb.
Es persze siman lehet az is, hogy valami olyan dolog hatarozza meg a sebesseget (pl. memoria, halozat, hattertar sebessege, felhasznaloi tevekenyseg, stb.) ami miatt teljesen mindegy, hogy a program maga egy vagy tobb szalon fut, mert nem ettol fugg.
- A hozzászóláshoz be kell jelentkezni
Pl. van egy interaktiv szal meg egy masik, ami szamol...
Igen, például egy tkinteres process meg egy számolós process. És ezeket meg lehet úgy írni, hogy a számolós akár több gépen is fusson egyidejűleg, a grafikus pedig mindegyiktől gyűjtse be az adatokat.
(Bár értem, hogy inkább az egy adott gépen megosztott erőforrások és a végrehajtási sebesség a téma tárgya.)
--
eutlantis
- A hozzászóláshoz be kell jelentkezni