urllib2和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慢这么多?
而且为什么在第二次运行时,这种差距似乎消失了呢?
编辑:我在这里找到了一种解决这个问题的“黑科技”方法:强制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 个回答
你有没有试过反过来做?也就是说,先用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秒
这里有两个结论:
urllib2总是比asyncore花更多时间,这是因为urllib2没有指定socket的类型,而asyncore允许用户自己定义。在这个例子中,我们定义为AF_INET,也就是IPv4协议。
如果两个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点(印度标准时间)
这可能和你的操作系统有关:如果你的操作系统会缓存DNS请求,第一次请求需要由DNS服务器来回答,而后续对同一个名字的请求就可以直接使用缓存里的结果。
补充说明:根据评论的内容来看,这可能不是DNS的问题。我还是认为问题出在操作系统,而不是Python。我在Windows和FreeBSD上都测试过这段代码,没发现有什么明显的差别,这两个函数的耗时差不多。
这正是应该的,单个请求之间不应该有太大的差别。输入输出和网络延迟大概占了这些时间的90%。
我终于找到了一篇很好的解释,讲述了这个问题的原因和背后的原理:
这个问题出在DNS解析器上。
只要是DNS解析器不支持的请求,就会出现这个问题。解决这个问题的正确方法是修复DNS解析器。
事情是这样的:
- 程序支持IPv6。
- 当它查找一个主机名时,getaddrinfo()首先会请求一个AAAA记录。
- DNS解析器看到请求AAAA记录,心想:“嗯,我不知道这是什么,算了,丢掉它吧。”
- DNS客户端(libc中的getaddrinfo())在等回应……但因为没有回应,只能超时。(这就是延迟的原因)
- 还没有收到记录,所以getaddrinfo()接着请求A记录。这次成功了。
- 程序得到了A记录并使用这些记录。
这个问题不仅影响IPv6(AAAA)记录,也会影响任何其他解析器不支持的DNS记录。
对我来说,解决办法是安装dnsmasq(不过我想其他任何DNS解析器也可以)。