2013-07-02 8 views
7

podstawie tej question Sądziłem, że nowy proces tworzenia powinny być prawie tak szybko jak tworzenia nowy wątek w Linuksie. Jednak mały test wykazał bardzo różne wyniki. Oto mój kod:Python gwintowania vs. multiprzetwarzania w Linuksie

from multiprocessing import Process, Pool 
from threading import Thread 

times = 1000 

def inc(a): 
    b = 1 
    return a + b 

def processes(): 
    for i in xrange(times): 
     p = Process(target=inc, args=(i,)) 
     p.start() 
     p.join() 

def threads(): 
    for i in xrange(times): 
     t = Thread(target=inc, args=(i,)) 
     t.start() 
     t.join() 

Testy:

>>> timeit processes() 
1 loops, best of 3: 3.8 s per loop 

>>> timeit threads() 
10 loops, best of 3: 98.6 ms per loop 

więc procesy są prawie 40 razy wolniej tworzenia! Dlaczego tak się dzieje? Czy jest to specyficzne dla Pythona lub tych bibliotek? Czy po prostu źle zinterpretowałem powyższą odpowiedź?


UPD 1. Żeby było jasne. Rozumiem, że ten fragment kodu w rzeczywistości nie wprowadza żadnej współbieżności. Celem jest przetestowanie czasu potrzebnego do utworzenia procesu i wątku. Aby korzystać z prawdziwą współbieżność Pythonie można użyć czegoś takiego:

def pools(): 
    pool = Pool(10) 
    pool.map(inc, xrange(times)) 

który naprawdę działa znacznie szybciej niż wersja gwintowany.


UPD 2. dodałem wersję z os.fork():

for i in xrange(times): 
    child_pid = os.fork() 
    if child_pid: 
     os.waitpid(child_pid, 0) 
    else: 
     exit(-1) 

Wyniki są następujące:

$ time python test_fork.py 

real 0m3.919s 
user 0m0.040s 
sys  0m0.208s 

$ time python test_multiprocessing.py 

real 0m1.088s 
user 0m0.128s 
sys  0m0.292s 

$ time python test_threadings.py 

real 0m0.134s 
user 0m0.112s 
sys  0m0.048s 
+0

Cóż, pytanie, z którym się łączysz, polega na porównaniu kosztu wywołania 'fork (2)' vs. 'pthread_create (3)', podczas gdy twój kod robi całkiem sporo więcej. Co powiesz na porównanie 'os.fork()' z 'thread.start_new_thread()'? – Aya

+0

@Aya: Nie mogłem znaleźć żadnego rodzaju 'join' w module' thread', aby stworzyć podobny test, ale nawet w porównaniu z wersją 'threading' na wysokim poziomie z' os.fork() 'jest nadal znacznie wolniejsze. W rzeczywistości jest najwolniejsza (choć dodatkowe warunki mogą wpływać na wydajność). Zobacz moją aktualizację. – ffriend

+0

Musisz użyć muteksu, aby poczekać na wątek, jeśli używasz niskiego poziomu 'thread' module, tak jak moduł' threading' wyższego poziomu implementuje 'join()'. Ale jeśli próbujesz zmierzyć czas potrzebny do utworzenia nowego procesu/wątku, nie powinieneś wywoływać 'join()'. Zobacz także moją odpowiedź poniżej. – Aya

Odpowiedz

5

Pytanie, z którym się łączysz, polega na porównaniu kosztu połączenia z numerem fork(2) w porównaniu z pthread_create(3), podczas gdy Twój kod jest trochę większy, np. użycie join() do oczekiwania na zakończenie procesów/wątków.

Jeśli, jak mówisz ...

Naszym celem jest sprawdzenie czasu potrzebnego do stworzenia procesu i wątku.

... nie powinieneś czekać na ich zakończenie. należy używać programów testowych bardziej jak te ...

fork.py

import os 
import time 

def main(): 
    for i in range(100): 
     pid = os.fork() 
     if pid: 
      #print 'created new process %d' % pid 
      continue 
     else: 
      time.sleep(1) 
      return 

if __name__ == '__main__': 
    main() 

thread.py

import thread 
import time 

def dummy(): 
    time.sleep(1) 

def main(): 
    for i in range(100): 
     tid = thread.start_new_thread(dummy,()) 
     #print 'created new thread %d' % tid 

if __name__ == '__main__': 
    main() 

... co daje następujące wyniki ..

$ time python fork.py 
real 0m0.035s 
user 0m0.008s 
sys  0m0.024s 

$ time python thread.py 
real 0m0.032s 
user 0m0.012s 
sys  0m0.024s 

... s o nie ma dużej różnicy w czasie tworzenia wątków i procesów.

+0

Ale czy twój 'fork.py' po prostu nie utworzy nowych wątków i nie wyjdzie, czekając na zakończenie procesów potomnych? – ffriend

+0

Co więcej, uruchamiasz następny wątek/proces, nie czekając na wcześniejsze zakończenie, więc działają one równolegle, podczas gdy wydaje się, że poprawniej jest je uruchamiać sekwencyjnie, aby uniknąć GIL i wszystkich takich rzeczy. – ffriend

+0

@ffriend Cóż, twoje pytanie zostało powiedziane (podkreślenie moje) "Przyjąłem, że ** tworzenie ** nowego procesu powinno być prawie tak szybkie, jak tworzenie nowego wątku w Linuksie", i tak jest. Cały punkt używania wątków jest dla współbieżności, więc jaki byłby sens sekwencyjnego uruchamiania wątków? Co dokładnie próbujesz osiągnąć tutaj? – Aya

2

Tak, to prawda. Rozpoczęcie nowego procesu (zwanego procesem ciężkim) jest kosztowne.

jako przegląd ...

OS musi (w przypadku systemu Linux) widelec pierwszy proces, skonfigurować rachunkowość dla nowego procesu, utwórz nowy stos, czy przełącznik kontekstowego, kopiowanie każda pamięć, która ulega zmianie, i oderwij to wszystko, gdy powróci nowy proces.

Wątek po prostu przydziela nowy stos i strukturę wątków, zmienia kontekst i zwraca po zakończeniu pracy.

... dlatego używamy wątków.

+0

masz go od tyłu. proces to tylko proces. wątek jest lekkim procesem :) Myślę, że możesz nazwać proces wątkiem wagi ciężkiej, ale nie sądzę, żeby ktoś to zrobił. jaki jest proces wagi ciężkiej? – thang

+0

@High westchnienie. Jeśli nie wiesz czegoś, to przynajmniej możesz to zrobić Google. Wypróbuj Googling "proces ciężki" i sprawdź, czy * ktoś to robi *. – andy256

1

Z mojego doświadczenia wynika, że ​​istnieje znacząca różnica między tworzeniem wątku (z pthread_create) a rozwidlaniem procesu.

Na przykład stworzyłem test C podobny do testu Pythona z kodu wątku jak ten:

pthread_t thread; 
pthread_create(&thread, NULL, &test, NULL); 
void *res; 
pthread_join(thread, &res); 

i procesu rozwidlone kodu:

pid_t pid = fork(); 
if (!pid) { 
    test(NULL); 
    exit(0); 
}   
int res; 
waitpid(pid, &res, 0); 

w moim systemie kod rozwidlania odbyła około 8 razy dłużej do wykonania.

Warto jednak zauważyć, że implementacja Pythona jest jeszcze wolniejsza - dla mnie było to około 16 razy wolniejsze.Podejrzewam, że tak jest, ponieważ oprócz zwykłego narzutu tworzenia nowego procesu, jest również więcej narzutów Pythona związanych z nowym procesem.