2015-08-29 4 views
9

Ich versuche zu verstehen, warum das Ausführen mehrerer Parser in parallelen Threads das Parsen von HTML nicht beschleunigt. Ein Thread erledigt 100 Aufgaben doppelt so schnell wie zwei Threads mit je 50 Aufgaben.Warum beschleunigt Multithreading das Parsen von HTML mit lxml nicht?

Hier ist mein Code:

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) 

Ausgabe auf meine 4 Kerne CPU Maschine:

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

Nach der FAQ (http://lxml.de/FAQ.html#can-i-use-threads-to-concurrently-access-the-lxml-api) zwei Threads soll schneller arbeiten als ein Thread.

Seit der Version 1.1, lxml befreit die GIL (Python globale Interpreter Lock) intern, wenn von der Festplatte und Speicher-Parsing, solange Sie entweder den Standard-Parser verwenden (die für jeden Thread repliziert wird) oder einen Parser erstellen für jeder Thread selbst.

...

Je mehr Ihrer XML-Verarbeitung geht in lxml, desto höher ist Ihr Gewinn jedoch. Wenn Ihre Anwendung durch XML-Parsing und -Serialisierung oder durch sehr selektive XPath-Ausdrücke und komplexe XSLTs gebunden ist, kann Ihre Beschleunigung auf Multiprozessor-Maschinen beträchtlich sein.

So ist die Frage, warum zwei Threads langsamer als ein Thread sind?

Meine Umgebung: Linux Debian, lxml 3.3.5-1 + b1, gleiche Ergebnisse auf python2 und python3

BTW, versuchte mein Freund diesen Test auf macos zu laufen und bekam gleichen Timings für ein und für zwei Threads . Wie auch immer, das ist nicht so, wie es laut Dokumentation sein sollte (zwei Threads sollten doppelt so schnell sein).

UPD: Dank Spektren. Er wies darauf hin, dass es in jedem Thread einen Parser erstellen muss. Der aktualisierte Code der func Funktion ist:

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) 

Die Ausgabe lautet:

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

Das ist genau das, was ich wollte! :)

+0

Ich war bereit, die GIL zu bringen, aber es scheint, dass Sie schon daran gedacht :). – Cyphase

Antwort

5

Die Dokumentation gibt einen guten Hinweis dort: "solange Sie entweder den Standardparser verwenden (der für jeden Thread repliziert wird) oder einen Parser für jeden Thread selbst erstellen."

Sie erstellen auf keinen Fall einen Parser für jeden Thread. Sie können sehen, dass die fromstring-Funktion eine globale verwendet, wenn Sie keinen Parser selbst angeben.

Jetzt für die andere Bedingung können Sie am Ende der Datei sehen, dass html_parser eine Unterklasse von lxml.etree.HTMLParser ist. Ohne besonderes Verhalten und vor allem ohne lokalen Thread-Speicher. Ich kann hier nicht testen, aber ich würde glauben, dass Sie am Ende einen Parser über Ihre beiden Threads teilen, der nicht als "Standard-Parser" gilt.

Können Sie versuchen, die Parser selbst zu instanziieren und sie an fromstring zu füttern? Oder ich mache es in einer Stunde oder so und aktualisiere diesen Beitrag.

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

Ja, ich kann es tun. Ich werde den Post in wenigen Minuten aktualisieren. –

+0

Danke. Threads mit eigenen HTML-Parsern arbeiten schneller als ein Thread. –

-1

Das liegt daran, wie Threads in Python arbeiten. Und es gibt Unterschiede zwischen Python 2.7 und Python 3. Wenn Sie wirklich das Parsen beschleunigen wollen, sollten Sie Multiprocessing und nicht Multithreading verwenden. Lesen Sie dazu: How do threads work in Python, and what are common Python-threading specific pitfalls?

Und das ist über Multiprozessing: http://sebastianraschka.com/Articles/2014_multiprocessing_intro.html

Solange es nicht ein IO-Operationen ist, wenn Sie Threads verwenden Sie Overhead des Kontext hinzufügen Schalt da nur ein Thread auf eine laufen kann Zeit. When are Python threads fast?

Viel Glück.

+1

lxmls Dokumentation erwähnt explizit, dass es die GIL beim Parsen von Daten freigibt. Das bedeutet, dass die üblichen Einschränkungen von Python-Threads nicht gelten sollten, solange die Bedingungen erfüllt sind, die die Freigabe der GIL durch libxml auslösen. – spectras

Verwandte Themen