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! :)
Ich war bereit, die GIL zu bringen, aber es scheint, dass Sie schon daran gedacht :). – Cyphase