为什么多线程不能加速使用lxml解析HTML?

时间:2015-08-29 11:20:51

标签: python multithreading performance lxml gil

我试图理解为什么在并行线程中运行多个解析器不会加速解析HTML。一个线程完成100个任务的速度是两个线程的两倍,每个线程有50个任务。

这是我的代码:

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)

我的4核CPU机器上的输出:

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

根据FAQ(http://lxml.de/FAQ.html#can-i-use-threads-to-concurrently-access-the-lxml-api),两个线程的工作速度应该比一个线程快。

  

从1.1版开始,只要您使用默认解析器(为每个线程复制)或自己为每个线程创建解析器,lxml在从磁盘和内存进行解析时会在内部释放GIL(Python的全局解释器锁)

...

  

您的XML处理越多地进入lxml,您的收益就越高。如果您的应用程序受XML解析和序列化的约束,或者非常有选择性的XPath表达式和复杂的XSLT,那么您在多处理器计算机上的加速可能会很大。

所以,问题是为什么两个线程比一个线程慢?

我的环境:linux debian,lxml 3.3.5-1 + b1,python2和python3上的结果相同

顺便说一句,我的朋友试图在macos上运行这个测试,并为一个和两个线程获得相同的时间。无论如何,这不是根据文档所应的那样(两个线程应该快两倍)。

UPD:感谢观众。他指出需要在每个线程中创建一个解析器。 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)

输出结果为:

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

这正是我想要的! :)

2 个答案:

答案 0 :(得分:5)

文档在那里提供了良好的领先优势:"只要您使用默认解析器(为每个线程复制)或自己为每个线程创建解析器。"

您绝对不会为每个线程创建解析器。您可以看到,如果您自己未指定解析器,fromstring函数将使用全局解析器。

现在对于其他条件,您可以在文件底部看到html_parserlxml.etree.HTMLParser的子类。没有特殊行为,最重要的是没有线程本地存储。我不能在这里测试,但我相信你最终会在你的两个线程中共享一个解析器,它不符合"默认解析器"。

你能尝试自己实现解析器并将它们提供给fromstring吗?或者我会在一个小时左右的时间内完成并更新这篇文章。

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

答案 1 :(得分:-1)

那是因为线程如何在python中工作。 并且python 2.7和python 3之间存在差异。 如果你真的想加速解析,你应该使用多处理而不是多线程。 读这个: How do threads work in Python, and what are common Python-threading specific pitfalls?

这是关于多处理的: http://sebastianraschka.com/Articles/2014_multiprocessing_intro.html

只要它不是io操作,当你使用线程时,你会增加上下文切换的开销,因为一次只能运行一个线程。 When are Python threads fast?

祝你好运。

相关问题