bvm*_*ode 6 python arrays list data-structures python-internals
我正在翻阅Fluent Python这本书。它指出,对于所有数字的序列,数组比列表更有效,更快。从我收集到的信息来看,它还减少了内存开销。它指出:“ Python数组和C数组一样精简。”
我很好奇为什么这里的数组显示的内存比列表大。
import array
from random import random
import sys
floats = array.array('d', (random() for i in range(10**7)))
L = [random() for i in range(10**7)]
print(sys.getsizeof(floats))
print(sys.getsizeof(L))
Run Code Online (Sandbox Code Playgroud)
输出
81940352
81528056
Run Code Online (Sandbox Code Playgroud)
抱歉,但我认为@millimoose 的答案没有很好地解释,当他说数组比列表快时,到底发生了什么或作者的意思。
内存占用:
double 需要 8 个字节,如果将它存储在一个中array,这正是需要多少内存- 它不是存储为 Python-Float,而是存储为原始 8 字节值。然而,由于过度分配和数组对象中保存的一些额外数据(数组大小、缓冲区大小、数组中值的类型等),会产生少量开销。
一个 Python-Float 需要超过 8 个字节:
>>> import sys
>>> f=1.0
>>> sys.getsizeof(f)
24
Run Code Online (Sandbox Code Playgroud)
24 字节 - 对于 Python 对象来说非常小!例如,通常需要一个空的 Python 对象(Python3):
>>> class A:
pass
>>> a=A()
>>> sys.getsizeof(a)
56
Run Code Online (Sandbox Code Playgroud)
56 字节。有一些技巧可以减少所需的字节数,它们都用于 Python-Floats,但您仍然需要 8 个字节用于双精度值,另外 8 个字节用于引用计数器,还有 8 个字节用于指向类型描述的指针(所以对象知道它是一个浮动对象)。
此外,存储在列表中的不是对象本身,而是指向它的引用(即指针),它本身需要 8 个字节。因此,在列表中保存 Python 浮点数基本上需要 32 个字节 - 所用内存量的 4 倍。
那么为什么在调用sys.getsizeof列表时会看到不同的东西呢?答案是:sys.getsizeof是不是递归:
sys.getsizeof(object[, 默认])
....
只考虑直接归因于对象的内存消耗,而不是它所引用的对象的内存消耗。
这意味着getsizeof列表只计算引用浮点对象(每个引用 8 字节)所需的内存,而不计算对象的大小。为了说明这一点:
>>> lst=[list(range(1000))]
>>> sys.getsizeof(lst)
72
Run Code Online (Sandbox Code Playgroud)
显然,使用了比报告的 72 字节更多的内存。
要查看实际内存消耗,您需要考虑解释器所需的内存:
>>> /usr/bin/time -fpeak_used_memory:%M python -c "l=list((float(i) for i in range(10**7)))"
peak_used_memory:326832
>>> /usr/bin/time -fpeak_used_memory:%M python -c "import array; a=array.array('d',(float(i) for i in range(10**7)))"
peak_used_memory:88076
Run Code Online (Sandbox Code Playgroud)
正如我们所看到的,差异(320 MB 与 80 MB)大约是预期的因子 4。
速度:
作者并不是说,array.array与 python-interpreter 一起使用会加快速度。相反,使用array.arraypython-operations 会使它变慢,因为首先必须将原始 double 值转换为 Python-Floats:
lst=list(range(10**6))
%timeit sum(lst)
7.19 ms ± 461 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
import array
a=array.array('i',range(10**6))
%timeit sum(a)
17.9 ms ± 43.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Run Code Online (Sandbox Code Playgroud)
几乎慢了 3 倍!
然而,有可能加快速度——只是没有那么简单。为此,将使用 numpy、cython 或 numba。例如:
import numpy as np
b=np.array(range(10**6), dtype=np.int32)
%timeit b.sum()
1.07 ms ± 24.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Run Code Online (Sandbox Code Playgroud)
快了近 10 倍!
你只是选错了例子。使用的重点array是当您需要存储其本机表示小于 Python 对象引用的项目时。(这里似乎是 8 个字节。)例如,如果您这样做:
from array import array
from os import urandom
a = array('B', urandom(1024))
l = list(a)
sys.getsizeof(a) # => 1155
sys.getsizeof(l) # => 9328
Run Code Online (Sandbox Code Playgroud)
由于doubles 也是 8 字节宽,因此确实没有比不同的 8 字节更紧凑的方式来存储它们。
至于书中的其余主张,请持保留态度 - 你无法运行 Python 代码 - 也就是说,让 Python 解释器执行操作 - 并且速度与 C 一样快。你仍然会招致将 Python 对象写入数组或从数组中读取它们时的开销,更快的方法是在本机函数中对整个数组执行某种大型操作。