元组和列表在元素的实例化和检索方面有什么性能差异吗?


当前回答

元组表现更好,但如果元组的所有元素都是不可变的。如果元组中的任何元素是可变的、列表或函数的,则编译它将花费更长的时间。这里我编译了3个不同的对象:

在第一个例子中,我编译了一个元组。它将元组加载为常量,加载并返回值。编译只需要一步。这叫做常数折叠。当我用相同的元素编译一个列表时,它必须首先加载每个单独的常量,然后构建列表并返回它。在第三个例子中,我使用了一个包含列表的元组。我为每个操作计时。

——内存分配

When mutable container objects such as lists, sets, dictionaries, etc are created, and during their lifetime, the allocated capacity of these containers (the number of items they can contain) is greater than the number of elements in the container. This is done to make adding elements to the collection more efficient, and is called over-allocating. Thus size of the list doesn't grow every time we append an element - it only does so occasionally. Resizing a list is very expensive, so not resizing every time an item is added helps out but you don't want to overallocate too much as this has a memory cost.

另一方面,由于不可变容器的项数在创建后是固定的,因此不需要这种过度分配——因此它们的存储效率更高。随着元组变大,它们的大小也会增加。

——复制

对不可变序列做一个浅拷贝是没有意义的,因为无论如何你都不能改变它。复制tuple只是返回它自己,还有内存地址。这就是为什么复制tuple更快

检索元素

我计时从元组和列表中检索元素:

从元组中检索元素比从列表中检索元素要快得多。因为在CPython中,元组可以直接访问(指针)它们的元素,而列表需要首先访问另一个数组,其中包含指向列表元素的指针。

其他回答

Tuple在读取时非常高效的主要原因是因为它是不可变的。

为什么不可变对象容易读取?

原因是元组可以存储在内存缓存中,不像列表。程序总是从列表的内存位置读取,因为它是可变的(可以随时更改)。

元组应该比列表更高效,因为它们是不可变的。

这里是另一个小基准,只是为了它的缘故。

In [11]: %timeit list(range(100))
749 ns ± 2.41 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [12]: %timeit tuple(range(100))
781 ns ± 3.34 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [1]: %timeit list(range(1_000))
13.5 µs ± 466 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [2]: %timeit tuple(range(1_000))
12.4 µs ± 182 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [7]: %timeit list(range(10_000))
182 µs ± 810 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [8]: %timeit tuple(range(10_000))
188 µs ± 2.38 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [3]: %timeit list(range(1_00_000))
2.76 ms ± 30.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [4]: %timeit tuple(range(1_00_000))
2.74 ms ± 31.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [10]: %timeit list(range(10_00_000))
28.1 ms ± 266 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [9]: %timeit tuple(range(10_00_000))
28.5 ms ± 447 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

让我们平均一下:

In [3]: l = np.array([749 * 10 ** -9, 13.5 * 10 ** -6, 182 * 10 ** -6, 2.76 * 10 ** -3, 28.1 * 10 ** -3])

In [2]: t = np.array([781 * 10 ** -9, 12.4 * 10 ** -6, 188 * 10 ** -6, 2.74 * 10 ** -3, 28.5 * 10 ** -3])

In [11]: np.average(l)
Out[11]: 0.0062112498000000006

In [12]: np.average(t)
Out[12]: 0.0062882362

In [17]: np.average(t) / np.average(l)  * 100
Out[17]: 101.23946713590554

你可以说这几乎是不确定的。

但可以肯定的是,与列表相比,元组花费了101.239%的时间,或额外1.239%的时间来完成这项工作。

通常,您可能希望元组稍微快一点。但是,您一定要测试您的特定情况(如果差异可能会影响程序的性能—记住“过早的优化是万恶之源”)。

Python让这变得非常简单:时间是你的朋友。

$ python -m timeit "x=(1,2,3,4,5,6,7,8)"
10000000 loops, best of 3: 0.0388 usec per loop

$ python -m timeit "x=[1,2,3,4,5,6,7,8]"
1000000 loops, best of 3: 0.363 usec per loop

和…

$ python -m timeit -s "x=(1,2,3,4,5,6,7,8)" "y=x[3]"
10000000 loops, best of 3: 0.0938 usec per loop

$ python -m timeit -s "x=[1,2,3,4,5,6,7,8]" "y=x[3]"
10000000 loops, best of 3: 0.0649 usec per loop

因此,在这种情况下,元组的实例化几乎快了一个数量级,但列表的项访问实际上要快一些!因此,如果你创建了一些元组,并多次访问它们,实际上,使用列表可能会更快。

当然,如果你想要改变一个元素,列表肯定会更快,因为你需要创建一个全新的元组来改变其中的一个元素(因为元组是不可变的)。

元组表现更好,但如果元组的所有元素都是不可变的。如果元组中的任何元素是可变的、列表或函数的,则编译它将花费更长的时间。这里我编译了3个不同的对象:

在第一个例子中,我编译了一个元组。它将元组加载为常量,加载并返回值。编译只需要一步。这叫做常数折叠。当我用相同的元素编译一个列表时,它必须首先加载每个单独的常量,然后构建列表并返回它。在第三个例子中,我使用了一个包含列表的元组。我为每个操作计时。

——内存分配

When mutable container objects such as lists, sets, dictionaries, etc are created, and during their lifetime, the allocated capacity of these containers (the number of items they can contain) is greater than the number of elements in the container. This is done to make adding elements to the collection more efficient, and is called over-allocating. Thus size of the list doesn't grow every time we append an element - it only does so occasionally. Resizing a list is very expensive, so not resizing every time an item is added helps out but you don't want to overallocate too much as this has a memory cost.

另一方面,由于不可变容器的项数在创建后是固定的,因此不需要这种过度分配——因此它们的存储效率更高。随着元组变大,它们的大小也会增加。

——复制

对不可变序列做一个浅拷贝是没有意义的,因为无论如何你都不能改变它。复制tuple只是返回它自己,还有内存地址。这就是为什么复制tuple更快

检索元素

我计时从元组和列表中检索元素:

从元组中检索元素比从列表中检索元素要快得多。因为在CPython中,元组可以直接访问(指针)它们的元素,而列表需要首先访问另一个数组,其中包含指向列表元素的指针。