Разница в производительности между urllib2 и asyncore
У меня есть несколько вопросов о производительности этого простого скрипта Python:
import sys, urllib2, asyncore, socket, urlparse
from timeit import timeit
class HTTPClient(asyncore.dispatcher):
def __init__(self, host, path):
asyncore.dispatcher.__init__(self)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.connect( (host, 80) )
self.buffer = 'GET %s HTTP/1.0\r\n\r\n' % path
self.data = ''
def handle_connect(self):
pass
def handle_close(self):
self.close()
def handle_read(self):
self.data += self.recv(8192)
def writable(self):
return (len(self.buffer) > 0)
def handle_write(self):
sent = self.send(self.buffer)
self.buffer = self.buffer[sent:]
url = 'http://pacnet.karbownicki.com/api/categories/'
components = urlparse.urlparse(url)
host = components.hostname or ''
path = components.path
def fn1():
try:
response = urllib2.urlopen(url)
try:
return response.read()
finally:
response.close()
except:
pass
def fn2():
client = HTTPClient(host, path)
asyncore.loop()
return client.data
if sys.argv[1:]:
print 'fn1:', len(fn1())
print 'fn2:', len(fn2())
time = timeit('fn1()', 'from __main__ import fn1', number=1)
print 'fn1: %.8f sec/pass' % (time)
time = timeit('fn2()', 'from __main__ import fn2', number=1)
print 'fn2: %.8f sec/pass' % (time)
Вот вывод, который я получаю на Linux:
$ python2 test_dl.py
fn1: 5.36162281 sec/pass
fn2: 0.27681994 sec/pass
$ python2 test_dl.py count
fn1: 11781
fn2: 11965
fn1: 0.30849886 sec/pass
fn2: 0.30597305 sec/pass
Почему urllib2 намного медленнее, чем asyncore при первом запуске?
И почему несоответствие исчезает при втором запуске?
РЕДАКТИРОВАТЬ: нашел хакерское решение этой проблемы здесь: заставить Python Mechanize/urllib2 использовать только запросы?
Пятисекундная задержка исчезает, если я подключаю модуль сокета следующим образом:
_getaddrinfo = socket.getaddrinfo
def getaddrinfo(host, port, family=0, socktype=0, proto=0, flags=0):
return _getaddrinfo(host, port, socket.AF_INET, socktype, proto, flags)
socket.getaddrinfo = getaddrinfo
3 ответа
Наконец нашел хорошее объяснение того, что вызывает эту проблему и почему:
Это проблема с распознавателем DNS.
Эта проблема будет возникать для любого запроса DNS, который не поддерживает преобразователь DNS. Правильное решение заключается в исправлении распознавателя DNS.
Что просходит:
- Программа поддерживает IPv6.
- Когда он ищет имя хоста, getaddrinfo() сначала запрашивает запись AAAA
- DNS-распознаватель видит запрос на запись AAAA, говорит: "Я не знаю, что это, давайте выбросить"
- DNS-клиент (getaddrinfo() в libc) ожидает ответа..... время ожидания истекло, поскольку ответа нет. (ЭТО ЗАДЕРЖКА)
- Записи еще не получены, поэтому getaddrinfo () отправляется на запрос записи A. Это работает.
- Программа получает записи A и использует их.
Это влияет не только на записи IPv6 (AAAA), но и на любые другие записи DNS, которые не поддерживаются распознавателем.
Для меня решение было установить dnsmasq (но я полагаю, что любой другой преобразователь DNS будет делать).
Это, вероятно, в вашей ОС: если ваша ОС кэширует DNS-запросы, DNS-сервер должен ответить на первый запрос, а последующие запросы с тем же именем уже под рукой.
РЕДАКТИРОВАТЬ: как показывают комментарии, это, вероятно, не проблема DNS. Я до сих пор утверждаю, что это ОС, а не Python. Я тестировал код как в Windows, так и во FreeBSD и не видел разницы такого рода, обе функции должны работать примерно в одно и то же время.
Что именно так и должно быть, не должно быть существенной разницы для одного запроса. Задержка ввода / вывода и сети составляют, вероятно, около 90% этих временных интервалов.
Вы пытались сделать наоборот? т.е. сначала через syncore и urllib?
Случай 1: Сначала мы пытаемся использовать urllib, а затем - ayncore.
fn1: 1.48460957 sec/pass
fn2: 0.91280798 sec/pass
Наблюдение: Ayncore сделал ту же операцию за 0,57180159 секунд меньше
Давайте поменяем это.
Случай 2: Теперь мы попробуем с ayncore, а затем urllib.
fn2: 1.27898671 sec/pass
fn1: 0.95816954 sec/pass the same operation in 0.12081717
Наблюдение: на этот раз Урллибу потребовалось на 0,32081717 секунд больше, чем на асинкоре
Два вывода здесь:
urllib2 всегда будет занимать больше времени, чем asyncore, и это потому, что urllib2 определяет тип семейства сокетов как неопределенный, в то время как asyncore позволяет пользователю определять его, и в этом случае мы определили его как протокол AF_INET IPv4.
Если два сокета сделаны на одном сервере независимо от ayncore или urllib, второй сокет будет работать лучше. И это из-за поведения кэша по умолчанию. Чтобы понять больше, проверьте это: /questions/43151401/kak-ochistit-kesh-dlya-otveta-socketgethostbyname/43151410#43151410
Рекомендации:
Хотите общий обзор того, как работает сокет?
http://www.cs.odu.edu/~mweigle/courses/cs455-f06/lectures/2-1-ClientServer.pdf
Хотите написать свой собственный сокет в Python?
http://www.ibm.com/developerworks/linux/tutorials/l-pysocks/index.html
Чтобы узнать о семействах сокетов или общей терминологии, проверьте эту вики:
http://en.wikipedia.or g/wiki/Berkeley_sockets
Примечание. Этот ответ последний раз обновлялся 05 апреля 2012 года, 2:00 по восточному времени.