Разница в производительности между urllib2 и asyncore - PullRequest
11 голосов
/ 07 октября 2011

У меня есть несколько вопросов о производительности этого простого скрипта 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 в первом запуске?

И почему расхождение, похоже, исчезает во втором запуске?

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

Ответы [ 3 ]

1 голос
/ 05 апреля 2012

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

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

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

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

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

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

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

0 голосов
/ 04 апреля 2012

Вы пытались сделать наоборот?то есть сначала через 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

Наблюдение: На этот раз Urllib занял на 0,32081717 секунд больше, чем асинкор

Здесь два вывода:

  1. urllib2 всегда будет занимать больше времени, чем асинхронный, и это потому, что urllib2 определяет тип семейства сокетов как неопределенный, в то время как асинхронныйпользователь определяет его, и в этом случае мы определили его как протокол AF_INET IPv4.

  2. Если два сокета подключены к одному серверу независимо от ayncore или urllib, второй сокет будет работать лучше.И это из-за поведения кэша по умолчанию.Чтобы понять больше, проверьте это: https://stackoverflow.com/a/6928657/1060337

Ссылки:

Хотите получить общий обзор работы сокета?

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 года, 02:00 IST

0 голосов
/ 07 октября 2011

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

РЕДАКТИРОВАТЬ: как показывают комментарии, это, вероятно, не проблема DNS. Я до сих пор утверждаю, что это ОС, а не Python. Я тестировал код как на Windows, так и на FreeBSD и не видел такого различия, обе функции должны быть примерно одинаковыми.

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

...