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

Why does multithreading do not speed up parsing HTML with lxml?

我试图理解为什么 运行 在并行线程中使用多个解析器不会加快解析速度 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

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

Since version 1.1, lxml frees the GIL (Python's global interpreter lock) internally when parsing from disk and memory, as long as you use either the default parser (which is replicated for each thread) or create a parser for each thread yourself.

...

The more of your XML processing moves into lxml, however, the higher your gain. If your application is bound by XML parsing and serialisation, or by very selective XPath expressions and complex XSLTs, your speedup on multi-processor machines can be substantial.

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

我的环境: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

这正是我想要的! :)

那是因为线程在 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?

祝你好运。

文档在那里提供了很好的线索:"as long as you use either the default parser (which is replicated for each thread) or create a parser for each thread yourself."

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

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

您可以尝试自己实例化解析器并将它们提供给 fromstring 吗?或者我会在一个小时左右完成并更新此 post.

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