为什么元组比列表更快?
我刚刚读过“潜入Python” ,“元组比列表更快”。
元组是不可变的,列表是可变的,但我不太明白为什么元组更快。
有人对此做过性能testing吗?
所报道的“build设速度”比率只适用于常量元组(项目用文字表示)。 仔细观察(并在机器上重复 – 只需在shell /命令窗口input命令!)…:
$ python3.1 -mtimeit -s'x,y,z=1,2,3' '[x,y,z]' 1000000 loops, best of 3: 0.379 usec per loop $ python3.1 -mtimeit '[1,2,3]' 1000000 loops, best of 3: 0.413 usec per loop $ python3.1 -mtimeit -s'x,y,z=1,2,3' '(x,y,z)' 10000000 loops, best of 3: 0.174 usec per loop $ python3.1 -mtimeit '(1,2,3)' 10000000 loops, best of 3: 0.0602 usec per loop $ python2.6 -mtimeit -s'x,y,z=1,2,3' '[x,y,z]' 1000000 loops, best of 3: 0.352 usec per loop $ python2.6 -mtimeit '[1,2,3]' 1000000 loops, best of 3: 0.358 usec per loop $ python2.6 -mtimeit -s'x,y,z=1,2,3' '(x,y,z)' 10000000 loops, best of 3: 0.157 usec per loop $ python2.6 -mtimeit '(1,2,3)' 10000000 loops, best of 3: 0.0527 usec per loop
我没有在3.0上进行测量,因为当然我没有它 – 它已经完全过时了,完全没有理由保留它,因为3.1在各方面都优于它(Python 2.7,如果你可以升级到2.6,在每个任务中的速度比2.6快了近20%,正如你所看到的,速度比3.1快 – 所以,如果你关心性能的话,Python 2.7真的是唯一的版本要去!)。
无论如何,关键在于,在每个Python发行版中,从常量字面值构build列表的速度大致相同,或者稍微低于通过variables引用的值构build列表; 但是元组的performance却非常不同 – 从常量字面值构build元组的速度通常比构buildvariables引用的值快3倍! 你可能想知道这是怎么回事吧?)
答案:由常量字面值构成的元组可以很容易地被Python编译器识别为一个不变的常量字面本身:所以它本质上是一次构build的,当编译器将源代码变成字节码时,隐藏在“常量表” “的相关function或模块。 当这些字节码执行时,他们只需要恢复预build的常量元组 – 嘿presto! – )
这个简单的优化不能应用到列表中,因为列表是一个可变对象,所以至关重要的是,如果相同的expression式如[1, 2, 3]
执行两次(在一个循环中 – timeit
模块使循环开启你的代表;-),每次都重新构造一个新的列表对象,而且构造(如编译器无法简单地将其识别为编译时常量和不可变对象时构造的元组)需要一点时间。
也就是说,元组构造(当两个构造实际上必须发生时)仍然是列表构造的两倍 – 这种差异可以用元组的简单性来解释,其他答案已经多次提到。 但是,这种简单性并没有考虑到六倍或六倍以上的加速,正如你所观察到的,如果你只是将列表和元组的构造与简单的常量文字作为它们的项目进行比较,
通过timeit
模块的强大function,您可以自己解决与性能有关的问题:
$ python2.6 -mtimeit -s 'a = tuple(range(10000))' 'for i in a: pass' 10000 loops, best of 3: 189 usec per loop $ python2.6 -mtimeit -s 'a = list(range(10000))' 'for i in a: pass' 10000 loops, best of 3: 191 usec per loop
这表明元组迭代比列表快得多,速度可以忽略不计。 我得到了类似的索引结果,但是对于构造,元组毁坏列表:
$ python2.6 -mtimeit '(1, 2, 3, 4)' 10000000 loops, best of 3: 0.0266 usec per loop $ python2.6 -mtimeit '[1, 2, 3, 4]' 10000000 loops, best of 3: 0.163 usec per loop
所以如果迭代或索引速度是唯一的因素,那么实际上没有区别,但是对于构build来说,元组胜利。
亚历克斯给出了一个很好的答案,但是我会试着扩展一些我认为值得一提的东西。 任何性能差异通常都很小,并且具体实现:所以不要在农场上下注。
在CPython中,元组被存储在一个单独的内存块中,所以创build一个新的元组最多只涉及一次调用来分配内存。 列表分配在两个块中:固定的一个包含所有的Python对象信息和一个可变大小的数据块。 这就是为什么创build一个元组更快的原因的一部分,但这也可能解释了索引速度的细微差别,因为有一个更less的指针要遵循。
CPython中也有优化来减less内存分配:解除分配的列表对象保存在空闲列表中,以便可以重用,但分配非空列表仍然需要为数据分配内存。 元组被保存在20个空闲列表中,用于不同大小的元组,因此分配一个小元组通常不需要任何内存分配调用。
像这样的优化在实践中是有帮助的,但是它们也可能使得过于依赖'timeit'的结果是有风险的,当然,如果移动到像内存分配工作方式不同的IronPython那样完全不同。
基本上,因为元组的不变性意味着解释器可以使用更精简,更快的数据结构,与列表相比。
列表显着更快的一个领域是来自生成器的构build,特别是,列表parsing要比具有生成器参数的最接近的元tuple()
快得多。
$ python --version Python 3.6.0rc2 $ python -m timeit 'tuple(x * 2 for x in range(10))' 1000000 loops, best of 3: 1.34 usec per loop $ python -m timeit 'list(x * 2 for x in range(10))' 1000000 loops, best of 3: 1.41 usec per loop $ python -m timeit '[x * 2 for x in range(10)]' 1000000 loops, best of 3: 0.864 usec per loop
特别要注意, tuple(generator)
似乎比list(generator)
快一点点,但是[elem for elem in generator]
比两者快得多。
元组被python编译器识别为一个不可变的常量,所以编译器在哈希表中只创build了一个条目,从不改变
列表是可变对象。所以当我们更新列表的时候,编译器会更新这个条目,所以它比元组更慢一些