urllib2和asyncore的性能差异

13 投票
3 回答
1420 浏览
提问于 2025-04-17 03:53

我对这个简单的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只使用A请求?

如果我像下面这样修改socket模块,五秒的延迟就消失了:

_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

你有没有试过反过来做?也就是说,先用asyncore再用urllib?

案例 1:我们先用urllib,然后再用asyncore。

fn1: 1.48460957 sec/pass
fn2: 0.91280798 sec/pass

观察结果:asyncore的操作比urllib快了0.57180159秒

现在我们反过来做。

案例 2:这次我们先用asyncore,然后再用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没有指定socket的类型,而asyncore允许用户自己定义。在这个例子中,我们定义为AF_INET,也就是IPv4协议。

  2. 如果两个socket连接到同一个服务器,不管是用asyncore还是urllib,第二个socket的表现会更好。这是因为默认的缓存机制。想了解更多,可以看看这个链接:https://stackoverflow.com/a/6928657/1060337

参考资料:

想了解socket是怎么工作的吗?

http://www.cs.odu.edu/~mweigle/courses/cs455-f06/lectures/2-1-ClientServer.pdf

想在Python中写自己的socket吗?

http://www.ibm.com/developerworks/linux/tutorials/l-pysocks/index.html

想了解socket的类型或一般术语,可以查看这个维基百科链接:

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

注意:这个回答最后更新于2012年4月5日,凌晨2点(印度标准时间)

0

这可能和你的操作系统有关:如果你的操作系统会缓存DNS请求,第一次请求需要由DNS服务器来回答,而后续对同一个名字的请求就可以直接使用缓存里的结果。

补充说明:根据评论的内容来看,这可能不是DNS的问题。我还是认为问题出在操作系统,而不是Python。我在Windows和FreeBSD上都测试过这段代码,没发现有什么明显的差别,这两个函数的耗时差不多。

这正是应该的,单个请求之间不应该有太大的差别。输入输出和网络延迟大概占了这些时间的90%。

1

我终于找到了一篇很好的解释,讲述了这个问题的原因和背后的原理:

这个问题出在DNS解析器上。

只要是DNS解析器不支持的请求,就会出现这个问题。解决这个问题的正确方法是修复DNS解析器。

事情是这样的:

  • 程序支持IPv6。
  • 当它查找一个主机名时,getaddrinfo()首先会请求一个AAAA记录。
  • DNS解析器看到请求AAAA记录,心想:“嗯,我不知道这是什么,算了,丢掉它吧。”
  • DNS客户端(libc中的getaddrinfo())在等回应……但因为没有回应,只能超时。(这就是延迟的原因)
  • 还没有收到记录,所以getaddrinfo()接着请求A记录。这次成功了。
  • 程序得到了A记录并使用这些记录。

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

对我来说,解决办法是安装dnsmasq(不过我想其他任何DNS解析器也可以)。

撰写回答