2014-09-02 86 views
4

我已经运行在Python 2.7.6以下的小测试:蟒蛇2.7写作 “x合组” 与 “设置.__包含__(x)的”

s = set(xrange(0, 1000000)) 
for i in xrange(0, 5000000): 
    if s.__contains__(i): 
     pass 

,得到了下面的输出运行time python py.py

real 0m0.616s 

然后我改变了我的代码:

s = set(xrange(0, 1000000)) 
for i in xrange(0, 5000000): 
    if i in s: 
     pass 

,并得到了运行的0.467s时间。我也得到了相同的结果dict。我的问题是:“为什么会有性能差别?”中,当使用s.__contains__(i)你需要做在Python属性查找,以及使Python中的方法调用Python是如何执行的s.__contains__(i)i in s

回答

10

电话或许有些解释。这些被作为单独的字节代码执行:

>>> import dis 
>>> dis.dis(compile('s.__contains__(i)', '<>', 'exec')) 
    1   0 LOAD_NAME    0 (s) 
       3 LOAD_ATTR    1 (__contains__) 
       6 LOAD_NAME    2 (i) 
       9 CALL_FUNCTION   1 
      12 POP_TOP    
      13 LOAD_CONST    0 (None) 
      16 RETURN_VALUE   

LOAD_ATTR加载__contains__属性,CALL_FUNCTION然后执行该方法。

使用i in s只需一个字节码:

>>> dis.dis(compile('i in s', '<>', 'exec')) 
    1   0 LOAD_NAME    0 (i) 
       3 LOAD_NAME    1 (s) 
       6 COMPARE_OP    6 (in) 
       9 POP_TOP    
      10 LOAD_CONST    0 (None) 
      13 RETURN_VALUE   

这里COMPARE_OP做所有的工作。

在C中,然后,查找__contains__插槽(或更确切地说,它的C等价物)并调用它更快。

请注意,比较Python中的两种方法时,使用timeit module而不是UNIX time命令要好得多;它会尝试消除环境因素和重复测试你:

>>> import timeit, random 
>>> testset = {random.randrange(50000) for _ in xrange(1000)} 
>>> tests = [random.randrange(5000) for _ in xrange(500)] 
>>> timeit.timeit('for i in tests: i in s', 
...    'from __main__ import testset as s, tests', 
...    number=100000) 
2.5375568866729736 
>>> timeit.timeit('for i in tests: s.__contains__(i)', 
...    'from __main__ import testset as s, tests', 
...    number=100000) 
4.208703994750977 

使用__contains__是由一些距离比较慢。

+0

对random.randrange()的调用使您的timeit测试淹没了十倍。 – msw 2014-09-02 15:37:04

+0

@msw:'random.randrange()'调用*不会在测试*之间变化。我们在这里比较相对时间。如果您愿意,您可以预先生成这些随机数字,但不会改变结果。 – 2014-09-02 15:43:31

+0

[结果是相反的](http://nbviewer.ipython.org/gist/zed/af2b99517838061ec7b9)在我的机器上 – jfs 2014-09-02 15:51:50