Почему многопоточность не ускоряет синтаксический анализ HTML с помощью 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-ядерном процессоре:
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 и сложные XSLTs, ваше ускорение на многопроцессорных машинах может быть существенным.
Итак, вопрос в том, почему два потока медленнее, чем один поток?
моя среда: Linux debian, lxml 3.3.5-1+b1, те же результаты на python2 и python3
кстати, мой друг попытался запустить этот тест на macos и получил одинаковые тайминги для одного и для двух потоков. Во всяком случае, это не так, как должно быть в соответствии с документацией (два потока должна быть в два раза быстрее).
UPD: благодаря spectras. Он указал, что ему нужно создать парсер в каждом потоке. Обновленный код
2 ответов
документация дает хорошее руководство там: " пока вы используете парсер по умолчанию (который реплицируется для каждого потока) или создаете парсер для каждого потока самостоятельно."
вы определенно не создаете парсер для каждого потока. Вы можете видеть, что, если вы не укажете парсер себе,fromstring
функция использует глобальную.
теперь для другого условия, вы можете увидеть в нижней части файла, что html_parser
- это подкласс lxml.etree.HTMLParser
. Без специального поведения и, самое главное, без локального хранилища потоков. Я не могу проверить здесь, но я бы поверил, что вы в конечном итоге разделяете парсер через два потока, который не квалифицируется как "парсер по умолчанию".
не могли бы вы попробовать создать экземпляр парсеров самостоятельно и кормить их fromstring
? Или я сделаю это через час или около того и обновлю этот пост.
def func(number):
parser = HTMLParser()
for x in range(number):
fromstring(DATA, parser=parser)
Это потому, что потоки работают в python. И есть различия между python 2.7 и python 3. Если вы действительно хотите ускорить синтаксический анализ, вы должны использовать многопоточность, а не многопоточность. Читать это: как работают потоки в Python и каковы общие подводные камни Python-threading?
а это про многопроцессорность : http://sebastianraschka.com/Articles/2014_multiprocessing_intro.html
пока это не операции ввода-вывода при использовании потоков вы добавляете накладные расходы на переключение контекста, потому что одновременно может выполняться только один поток. когда потоки Python быстро?
удачи.