Wydajność jest zauważalnie lepsza, bo korzystamy ze starych komponentów i tam różnice są o wiele bardziej widoczne, niż na zwykłym komputerze, gdzie oba rozwiązania przetwarzają zadania niemal natychmiast.
Zatem mamy część z kodem w Rust, ale ciągle część z obliczeń musimy wykonać w pojedynczym procesie pythona do którego wysyłamy resty z zapytaniami.
Jednak celery miało jeden duży plus - uruchamiało się jako osobne procesy(co wydłużało o kilka sekund proces uruchamiania programu), dzięki czemu operacje numpy/scipy na CPU, były ładnie rozdzielane po stronie pythona na wszystkie wątki(na urządzeniu są 4 rdzenie i tyle samo procesów celery było uruchamianych)
Operacje obliczania statystyk(mowa o powyższym zadaniu korzystającym z numpy/scipy) są często zlecane w większej ilości na raz, dlatego ważne jest by to zrównoleglić.
Próbowałem robić to przez proste rozdzielanie tasków na wiele wątków, jednak, czasy bywały nawet czasami gorsze, niż jak robiłem to w jednym wątku - domyślam się że to wina GIL i tego że nie działa zbytnio dobrze, gdy wątki wykonują masę rzeczy na cpu.
Drugim problemem jest to że w przypadku równoległych działań wyskakują dziwne ostrzeżenia, przez co myślę że np. matplotlib, nie jest przystosowany do działania z wielu wątków(mimo że zadania które wykonuję od początku do końca działają tylko w jednym wątku), bo przechowuje globalnie jakieś parametry ze swoim stanem.
Kojarzycie, w jaki sposób, mógłbym w miarę prosto, móc wykonywać te obliczenia po stronie pythona na wielu wątkach?
#python
@qarmin jest standardowy moduł w Pythonie, nazywa się multiprocessing. W nim znajdziesz Pool i metody typu starmap i map. Najczęściej z tego korzystam gdy trzeba zrównoległość obliczenia.
https://superfastpython.com/multiprocessing-pool-map/
# SuperFastPython.com
# example of parallel map() with the process pool
from random import random
from time import sleep
from multiprocessing.pool import Pool
# task executed in a worker process
def task(identifier):
# generate a value
value = random()
# report a message
print(f'Task {identifier} executing with {value}', flush=True)
# block for a moment
sleep(value)
# return the generated value
return value
# protect the entry point
if __name__ == '__main__':
# create and configure the process pool
with Pool() as pool:
# execute tasks in order
for result in pool.map(task, range(10)):
print(f'Got result: {result}', flush=True)
# process pool is closed automatically
Na zamieszczonym zdjęciu znajdziesz wszystkie metody z klasy Pool które możesz wykorzystać.
@markxvyarov Działa to lepiej, niż ręczne tworzenie i zarządzanie wątkami?
Boję się że również tutaj GIL pokaże swoje ograniczenia i w rzeczywistości będzie to wszystko działało, niemal tak jak w jednym wątku
@qarmin dla tego podałem multiprocessing a nie threading. Przy tym drugim, wątki są blokowane przez GIL, i są one obsługiwany tylko przez jeden rdzeń procesora. Najczęściej wykorzystuje się do zrównoleglenia operacji IO. Tym czasem multiprocessing uruchamia oddzielne procesy pochodne od głównego, dlatego każdy proces może być w tym samym czasie obsłużony przez CPU.
Metoda którą Ci podałem jest o tyle prosta że o nic nie trzeba dbać, a funkcje zwracają wyniki obliczeń.
Zaloguj się aby komentować