urllib2和asyncore之间的性能差异

ekh*_*oro 12 python urllib2 ipv6 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)
Run Code Online (Sandbox Code Playgroud)

这是我在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
Run Code Online (Sandbox Code Playgroud)

为什么urllib2在第一次运行时比asyncore慢得多?

为什么差异似乎在第二轮就消失了?

编辑:在这里找到一个解决这个问题的hackish解决方案:强制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
Run Code Online (Sandbox Code Playgroud)

ekh*_*oro 1

终于找到了一个很好的解释,解释了这个问题的原因和原因:

这是 DNS 解析器的问题。

DNS 解析器不支持的任何 DNS 请求都会出现此问题。正确的解决方案是修复 DNS 解析器。

会发生什么:

  • 程序启用了 IPv6。
  • 当查找主机名时,getaddrinfo() 首先询问 AAAA 记录
  • DNS 解析器看到对 AAAA 记录的请求,会说“嗯,我不知道它是什么,让我们把它扔掉”
  • DNS 客户端(libc 中的 getaddrinfo())等待响应......由于没有响应而必须超时。(这就是延迟)
  • 尚未收到记录,因此 getaddrinfo() 会请求 A 记录。这有效。
  • 程序获取 A 记录并使用它们。

这不仅会影响 IPv6 (AAAA) 记录,还会影响解析器不支持的任何其他 DNS 记录。

对我来说,解决方案是安装dnsmasq(但我想任何其他 DNS 解析器都可以)。