2015-08-29 26 views
9

Sto cercando di capire perché l'esecuzione di più parser in thread paralleli non acceleri l'analisi dell'HTML. Un thread esegue 100 attività due volte più velocemente di due thread con 50 attività ciascuno.Perché il multithreading non velocizza l'analisi dell'HTML con lxml?

Ecco il mio codice:

from lxml.html import fromstring 
import time 
from threading import Thread 
try: 
    from urllib import urlopen 
except ImportError: 
    from urllib.request import urlopen 

DATA = urlopen('http://lxml.de/FAQ.html').read() 


def func(number): 
    for x in range(number): 
     fromstring(DATA) 


print('Testing one thread (100 job per thread)') 
start = time.time() 
t1 = Thread(target=func, args=[100]) 
t1.start() 
t1.join() 
elapsed = time.time() - start 
print('Time: %.5f' % elapsed) 

print('Testing two threads (50 jobs per thread)') 
start = time.time() 
t1 = Thread(target=func, args=[50]) 
t2 = Thread(target=func, args=[50]) 
t1.start() 
t2.start() 
t1.join() 
t2.join() 
elapsed = time.time() - start 
print('Time: %.5f' % elapsed) 

uscita sulla mia macchina CPU 4 core:

Testing one thread (100 job per thread) 
Time: 0.55351 
Testing two threads (50 jobs per thread) 
Time: 0.88461 

Secondo la FAQ (http://lxml.de/FAQ.html#can-i-use-threads-to-concurrently-access-the-lxml-api) due thread dovrebbero lavorare più velocemente di un thread.

A partire dalla versione 1.1, lxml libera la GIL (blocco interprete globale di Python) internamente durante l'analisi da disco e della memoria, fino a quando si utilizza il parser di default (che viene replicato per ogni thread) o creare un parser per ogni filo te stesso.

...

La più del vostro elaborazione XML si muove in lxml, tuttavia, più alto sarà il guadagno. Se la tua applicazione è vincolata dall'analisi e serializzazione XML, o da espressioni XPath molto selettive e XSLT complessi, la tua velocità su macchine multiprocessore può essere notevole.

Quindi, la domanda è: perché due thread sono più lenti di un thread?

Il mio ambiente: linux debian, lxml 3.3.5-1 + b1, stessi risultati su python2 e python3

BTW, il mio amico ha cercato di eseguire questo test su MacOS e ottenuto stessi tempi per uno e per due fili . Ad ogni modo, non è come dovrebbe essere secondo la documentazione (due thread dovrebbero essere il doppio più veloce).

UPD: Grazie agli extra. Ha indicato che è necessario creare un parser in ogni thread. Il codice aggiornato della funzione func è:

from lxml.html import HTMLParser 
from lxml.etree import parse 

def func(number): 
    parser = HTMLParser() 
    for x in range(number): 
     parse(StringIO(DATA), parser=parser) 

L'output è:

Testing one thread (100 jobs per thread) 
Time: 0.53993 
Testing two threads (50 jobs per thread) 
Time: 0.28869 

che è esattamente quello che volevo! :)

+0

Mi stavo preparando a far apparire il GIL, ma sembra che tu lo abbia già pensato :). – Cyphase

risposta

5

La documentazione fornisce un buon risultato: "finché si utilizza il parser predefinito (che viene replicato per ogni thread) o si crea un parser per ogni thread."

Non si sta sicuramente creando un parser per ogni thread. È possibile vedere che, se non si specifica un parser da soli, la funzione fromstring utilizza uno globale.

Ora per l'altra condizione, è possibile vedere nella parte inferiore del file che html_parser è una sottoclasse di lxml.etree.HTMLParser. Senza alcun comportamento particolare e, soprattutto, nessuna memoria locale del thread. Non posso testare qui, ma credo che finirai per condividere un parser attraverso i tuoi due thread, che non si qualifica come "parser predefinito".

Potresti provare a instanciare i parser da solo e alimentarli a fromstring? O lo farò tra un'ora o così e aggiorno questo post.

def func(number): 
    parser = HTMLParser() 
    for x in range(number): 
     fromstring(DATA, parser=parser) 
+0

Sì, posso farlo. Aggiornerò il post in pochi minuti –

+0

Grazie. I thread con i loro parser HTML funzionano più velocemente di un thread. –

-1

Ecco perché i thread funzionano in python. E ci sono differenze tra python 2.7 e python 3. Se vuoi davvero velocizzare l'analisi, devi usare multiprocessing e non il multithreading. Leggi questo: How do threads work in Python, and what are common Python-threading specific pitfalls?

E si tratta di multiprocessing: http://sebastianraschka.com/Articles/2014_multiprocessing_intro.html

Finché non è un operazioni io, quando si utilizzano le discussioni si aggiunge dall'alto del cambio di contesto perché solo un thread può essere eseguito in un tempo. When are Python threads fast?

Buona fortuna.

+1

La documentazione di lxml indica esplicitamente che rilascia GIL durante l'analisi dei dati. Ciò significa che le normali avvertenze sui thread di Python non dovrebbero applicarsi, purché siano soddisfatte le condizioni che attivano il rilascio di GIL da libxml. – spectras