代码之家  ›  专栏  ›  技术社区  ›  Delta76

为什么在Python中tuple比list快?

  •  55
  • Delta76  · 技术社区  · 16 年前

    "Dive into Python" “元组比列表快”。

    元组是不可变的,列表是可变的,但我不太明白为什么元组更快。

    7 回复  |  直到 7 年前
        1
  •  98
  •   Alex Martelli    16 年前

    报告中的“建设速度”比率仅适用于 元组(其项由文字表示的元组)。仔细观察(并在您的机器上重复——您只需在shell/命令窗口中键入命令即可!):

    $ 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
    

    答:由常量文本组成的元组可以很容易地被Python编译器识别为一个不可变的常量文本本身:因此它基本上只构建一次,编译器将源代码转换为字节码,并隐藏在相关函数或模块的“常量表”中。当这些字节码执行时,它们只需要恢复预先构建的常量元组——嘿,presto!)

    这种简单的优化方法不能应用于列表,因为列表是一个可变的对象,所以如果相同的表达式 [1, 2, 3] 执行两次(在循环中-- timeit 模块代表您进行循环;-),每次都会重新构造一个新的list对象,而这种构造(就像构造元组时,编译器不能简单地将它标识为编译时常量和不可变对象)确实需要一点时间。

    也就是说,元组结构(当两种结构都必须 那个

        2
  •  30
  •   Raymond Hettinger    8 年前

    执行摘要

    元组的性能往往优于列表

    1) 元组可以是 constant folded .

    4) 元组直接引用它们的元素。

    元组可以固定折叠

    常量元组可以由Python的窥视孔优化器或AST优化器预先计算。另一方面,列表是从零开始构建的:

        >>> from dis import dis
    
        >>> dis(compile("(10, 'abc')", '', 'eval'))
          1           0 LOAD_CONST               2 ((10, 'abc'))
                      3 RETURN_VALUE   
    
        >>> dis(compile("[10, 'abc']", '', 'eval'))
          1           0 LOAD_CONST               0 (10)
                      3 LOAD_CONST               1 ('abc')
                      6 BUILD_LIST               2
                      9 RETURN_VALUE 
    

    跑步 tuple(some_tuple) 立即返回。由于元组是不可变的,因此不必复制它们:

    >>> a = (10, 20, 30)
    >>> b = tuple(a)
    >>> a is b
    True
    

    相反, list(some_list) 要求将所有数据复制到新列表:

    >>> a = [10, 20, 30]
    >>> b = list(a)
    >>> a is b
    False
    

    元组不会过度分配

    由于元组的大小是固定的,所以它可以比需要过度分配才能生成的列表存储得更紧凑 运营效率高。

    这为元组提供了很好的空间优势:

    >>> import sys
    >>> sys.getsizeof(tuple(iter(range(10))))
    128
    >>> sys.getsizeof(list(iter(range(10))))
    200
    

    以下是来自 Objects/listobject.c 这就解释了列表的作用:

    /* This over-allocates proportional to the list size, making room
     * for additional growth.  The over-allocation is mild, but is
     * enough to give linear-time amortized behavior over a long
     * sequence of appends() in the presence of a poorly-performing
     * system realloc().
     * The growth pattern is:  0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ...
     * Note: new_allocated won't overflow because the largest possible value
     *       is PY_SSIZE_T_MAX * (9 / 8) + 6 which always fits in a size_t.
     */
    

    元组直接引用它们的元素

    这使元组在索引查找和解包方面具有较小的速度优势:

    $ python3.6 -m timeit -s 'a = (10, 20, 30)' 'a[1]'
    10000000 loops, best of 3: 0.0304 usec per loop
    $ python3.6 -m timeit -s 'a = [10, 20, 30]' 'a[1]'
    10000000 loops, best of 3: 0.0309 usec per loop
    
    $ python3.6 -m timeit -s 'a = (10, 20, 30)' 'x, y, z = a'
    10000000 loops, best of 3: 0.0249 usec per loop
    $ python3.6 -m timeit -s 'a = [10, 20, 30]' 'x, y, z = a'
    10000000 loops, best of 3: 0.0251 usec per loop
    

    Here 元组 (10, 20) 存储时间:

        typedef struct {
            Py_ssize_t ob_refcnt;
            struct _typeobject *ob_type;
            Py_ssize_t ob_size;
            PyObject *ob_item[2];     /* store a pointer to 10 and a pointer to 20 */
        } PyTupleObject;
    

    Here 名单是怎么写的 [10, 20]

        PyObject arr[2];              /* store a pointer to 10 and a pointer to 20 */
    
        typedef struct {
            Py_ssize_t ob_refcnt;
            struct _typeobject *ob_type;
            Py_ssize_t ob_size;
            PyObject **ob_item = arr; /* store a pointer to the two-pointer array */
            Py_ssize_t allocated;
        } PyListObject;
    

    请注意,tuple对象直接合并了两个数据指针,而list对象有一个额外的间接层,指向保存这两个数据指针的外部数组。

        3
  •  28
  •   Duncan    16 年前

    亚历克斯给出了一个很好的答案,但我将试着扩展一些我认为值得一提的事情。任何性能差异通常都很小,并且是特定于实现的:所以不要把赌注押在它们身上。

    在CPython中,元组存储在单个内存块中,因此创建一个新元组最糟糕的情况是只需要一个调用来分配内存。列表分为两个块:一个是包含所有Python对象信息的固定块,另一个是大小可变的数据块。这是创建元组速度更快的部分原因,但也可能解释了索引速度的细微差别,因为后面的指针少了一个。

    CPython中还进行了优化,以减少内存分配:取消分配的列表对象保存在空闲列表中,以便可以重用,但分配非空列表仍然需要为数据分配内存。元组保存在20个不同大小的元组的空闲列表中,因此分配一个小元组通常根本不需要任何内存分配调用。

    像这样的优化在实践中是有帮助的,但它们也可能会使过于依赖“timeit”的结果变得有风险,当然,如果您使用类似IronPython的方法,内存分配的工作方式会完全不同。

        4
  •  18
  •   Alec Thomas    16 年前

    以上帝的力量 timeit

    $ 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
    

    这表明tuple的迭代速度要比list快得多。对于索引,我得到了类似的结果,但是对于构造,tuple破坏了列表:

    $ 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
    

        5
  •  5
  •   Dan Breslau    16 年前

    本质上是因为tuple的不变性意味着解释器可以使用比list更精简、更快的数据结构。

        6
  •  2
  •   Dan Passaro Andry    9 年前

    一个列表明显更快的领域是生成器的构造,尤其是列表理解要比最接近的元组等价物快得多, 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] 比他们两个都快得多。

        7
  •  2
  •   M. Rostami Pramod yadav    6 年前

    在python中,我们有两种类型的对象。 1可变的 2不变的 .
    在python中,列表位于可变对象之下,元组位于不可变对象之下。

    • 因此,它不需要额外的空间来存储新对象。

    • 列表被分配到两个块中:一个是固定的块,包含所有Python代码

    • 这就是创建元组比创建列表快的原因。

    • 比列表,因为在元组索引中,它遵循更少的指针。

    • 列表。

    • 我们可以使用元组和列表中的索引访问元素。

    元组的缺点:

    • 我们不能向元组添加元素,但可以向列表添加元素。

    • 我们不能对元组排序,但在列表中,我们可以通过调用 list.sort() 方法。

    • 我们不能删除元组中的元素,但在列表中,我们可以删除 元素。

    • 我们不能替换元组中的元素,但可以替换列表中的元素。


    Source

        8
  •  0
  •   y durga prasad    9 年前

    python编译器将元组标识为一个不可变常量

    列表是可变的对象。所以当我们更新列表时,编译器会更新条目 所以它比元组慢一点

    推荐文章