Подтвердить что ты не робот

Разница в производительности между urllib2 и asyncore

У меня есть некоторые вопросы о производительности этого простого python script:

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

Почему urlib2 намного медленнее, чем asyncore в первом запуске?

И почему разница во второй перспективе исчезает?

EDIT. Здесь найдено хакерское решение этой проблемы: Принудительно использовать python mechanize/urllib2 только для запросов A?

Пятисекундная задержка исчезает, если я обезвредил модуль сокета следующим образом:

_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
4b9b3361

Ответ 1

Наконец нашел хорошее объяснение о том, что вызывает эту проблему, и почему:

Это проблема с DNS-преобразователем.

Эта проблема будет возникать для любого DNS-запроса, который DNS-преобразователь не поддерживает. Правильное решение - исправить DNS-резольвер.

Что происходит:

  • Программа поддерживает IPv6.
  • Когда он ищет имя хоста, getaddrinfo() сначала запрашивает запись AAAA
  • DNS-распознаватель видит запрос на запись AAAA, идет "uhmmm, я не знаю, что это такое, давайте выбросим его".
  • DNS-клиент (getaddrinfo() в libc) ждет ответа..... должен тайм-аут, так как ответа нет. (ЭТО ЗАДЕРЖКА)
  • До сих пор не получено записей, поэтому getaddrinfo() отправляется для запроса записи A. Это работает.
  • Программа получает записи A и использует их.

Это не только влияет на записи IPv6 (AAAA), но также влияет на любые другая DNS-запись, которую не поддерживает распознаватель.

Для меня решение заключалось в установке dnsmasq (но я полагаю, что любой другой DNS-реверсор будет делать).

Ответ 2

Это, вероятно, в вашей ОС: если ваша ОС кэширует DNS-запросы, на первый запрос должен быть дан ответ DNS-сервер, последующие запросы с тем же именем уже под рукой.

EDIT: как показывают комментарии, это, вероятно, не проблема DNS. Я все еще утверждаю, что это ОС, а не питон. Я протестировал код как на Windows, так и на FreeBSD и не видел такого различия, обе функции нуждаются примерно в том же времени.

Именно так и должно быть, не должно быть существенной разницы для одного запроса. Задержка ввода-вывода и сети составляет, вероятно, около 90% этих таймингов.

Ответ 3

Вы пытались сделать обратное? сначала через 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 определяет тип семейства сокетов как неуказанный, а асинхронный позволяет пользователю определить его, и в этом случае мы определили его как протокол AF_INET IPv4.

  • Если два сокета сделаны на одном сервере независимо от ayncore или urllib, второй сокет будет работать лучше. И это из-за поведения кэша по умолчанию. Чтобы это понять, проверьте это: fooobar.com/info/475269/...

Литература:

Хотите общий обзор работы сокета?

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.org/wiki/Berkeley_sockets

Примечание: Этот ответ был последний раз обновлен 05 апреля 2012 г., 2AM IST