urllib2和asyncore之间的性能差异

时间:2011-10-07 17:32:52

标签: 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)

这是我在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慢得多?

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

编辑:在此处找到了解决此问题的黑客解决方案:Force python mechanize/urllib2 to only use A requests?

如果我按如下方式修补套接字模块,那么五秒延迟就会消失:

_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 个答案:

答案 0 :(得分:1)

最后找到导致此问题的原因good explanation,以及原因:

  

这是DNS解析器的问题。

     

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

     

会发生什么:

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

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

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

答案 1 :(得分:0)

这可能在您的操作系统中:如果您的操作系统缓存DNS请求,则第一个请求必须由DNS服务器应答,后续的同名请求已经在手边。

编辑:正如评论所示,它可能不是DNS问题。我仍然认为这是操作系统,而不是python。我已经在Windows和FreeBSD上测试了代码,并没有看到这种差异,这两种功能大约需要同时进行。

究竟应该如何,单个请求不应该有显着差异。 I / O和网络延迟大约占这些时间的90%。

答案 2 :(得分:0)

你尝试反过来了吗?即首先通过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比asyncore花了0.32081717秒

这里有两个结论:

  1. urllib2总是比asyncore花费更多时间,这是因为urllib2将套接字族类型定义为未指定,而asyncore让用户定义它,在这种情况下,我们将其定义为AF_INET IPv4协议。

    < / LI>
  2. 如果两个套接字都是针对同一个服务器而不管ayncore或urllib,那么第二个套接字会表现得更好。这是因为默认缓存行为。要了解更多信息,请查看以下内容:https://stackoverflow.com/a/6928657/1060337

  3. <强>参考文献:

    想要了解套接字如何工作的一般概述吗?

    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

    要了解套接字系列或一般术语,请查看此wiki:

    http://en.wikipedia.org/wiki/Berkeley_sockets

    注意:此答案最后更新于2012年4月5日,上午2点IST