Last Updated: 2023-05-03 07:31:14 Wednesday
-- TOC --
不同的数据结构在进行同样操作的时候,性能有所不同。我们应该根据业务场景选择最适合的数据结构。虽然都是顺序存储类结构,但C++中还是有vector,list和array的区分,同样在Python中,也有list,tuple,array,bytes,bytearray,以及deque的不同。
简要总结一下Python内各种顺序容器的特点和应用场景。
名称 | . | . | Commments |
---|---|---|---|
list |
mutable, container | 最常用的万能型顺序容器,支持\(O(1)\)任意访问,可以对应C++的vector。没有对内部元素数据类型一致性的强制要求。尾部的操作很快,但在头部做insert就比较慢了。适合快速的固定长度的操作。 | |
tuple |
immutable, container | 不仅仅是immutable list,tuple常常被用来当做各种record数据使用,each item in tuple holds the data for one field, and the position of the item gives its meaing. | |
collections.namedtuple |
immutable/container | Named tuples assign meaning to each position in a tuple and allow for more readable, self-documenting code. They can be used wherever regular tuples are used, and they add the ability to access fields by name instead of position index. | |
array.array |
mutable, flat | 类似list,Efficient arrays of numeric values. This module defines an object type which can compactly represent an array of basic values: characters, integers, floating point numbers. Arrays are sequence types and behave very much like lists, except that the type of objects stored in them is constrained. 支持存放基础数据类型,要求元素类型一致,内存消耗与存放数据类型有关,比list少。 | |
bytearray |
mutable, flat | array of byte,Python专门针对byte对象优化的array,性能比array('b',[]) 更好。 |
|
bytes |
immutable, flat | ||
str |
immutable, flat | 存放Unicode编码的字符 | |
collections.deque |
mutable, container | thread-safe | 双端队列,可以对应C++的list,虽然任意位置访问是\(O(n)\)级别,但在头部操作速度极快。deque并非内置,需要import,from collections import deque 。可以指定maxlen。(The deque's append(), appendleft(), pop(), popleft(), and len(d) operations are thread-safe in CPython) |
queue.Queue |
mutable, container | thread-safe | 实现建立在deque基础上,因此比deque慢一点。The Queue is slowed down a bit through locks, function indirection, and additional features such as maxsize, join, and task_done. |
Python标准库中的collections模块中全是各种container。
append(x)
测试在尾部添加元素,即append操作。
>>> tcode = """\
... for i in range(1000):
... a.append(i)
... """
>>>
>>> sum(repeat(tcode, setup="a=[]", timer=time.process_time, number=10000))/5
0.6905808886000001
>>> sum(repeat(tcode, setup="from array import array;a=array('i',[])", timer=time.process_time, number=10000))/5
1.3353027202000003
>>> sum(repeat(tcode, setup="from collections import deque;a=deque()", timer=time.process_time, number=10000))/5
0.7210645022000008
repeat默认重复5次。
list最快,deque次之,但非常接近,array要慢一些,但都在一个数量级上,差距不算特别大。
insert(0,x)
测试在头部添加元素,即在index=0的位置做insert。
>>> tcode02 = """\
... for i in range(1000):
... a.insert(0,i)
... """
>>>
>>> sum(repeat(tcode02, setup="a=[]", timer=time.process_time, number=100))/5
2.1469489754000035
>>> sum(repeat(tcode02, setup="from array import array;a=array('i',[])", timer=time.process_time, number=100))/5
0.9068540007999986
>>> sum(repeat(tcode02, setup="from collections import deque;a=deque()", timer=time.process_time, number=100))/5
0.011628347800001392
deque由于数据结构非常适合在头部做insert,它的速度超快!array比list要快一些,但与deque相比,完全不在一个数量级上。
>>> sum(repeat(tcode02, setup="a=[]", timer=time.process_time, number=200))/5
9.177822828399963
>>> sum(repeat(tcode02, setup="from array import array;a=array('i',[])", timer=time.process_time, number=200))/5
4.232576406399971
>>> sum(repeat(tcode02, setup="from collections import deque;a=deque()", timer=time.process_time, number=200))/5
0.022309611000014228
测试代码的特点,每次for迭代,a并没有被清空,因此number越大,a就越长。当把number从100增加到200后,可以看到,只有deque的执行时间是线性增加的,list和array的耗时增加,都是非线性的。因为a越长,每次在头部insert,需要移动的数据量就越大!
在访问array容器中的数据时,存在一种转换,从C primitive type转Python type,多种C primitive type转成Python的int或float。这种转换有一定消耗。
>>> timeit('t = a[1]', setup='a=[1,2,3]',timer=time.process_time, number=int(1e9), globals=globals())
15.515625
>>> timeit('t = a[1]', setup="a=array('i',[1,2,3])",timer=time.process_time, number=int(1e9), globals=globals())
19.75
先看一下各自的内存占用情况:
>>> sys.getsizeof(bytes(10000))
10033
>>> sys.getsizeof(bytearray(10000))
10057
>>> ab = array('b',[])
>>> ab.frombytes(bytes(10000))
>>> sys.getsizeof(ab)
11774
bytes和bytearray比array稍好一些。
再来看看速度,此时只能跟bytearray比较。
append
>>> tcode03 = """\
... for i in range(127):
... a.append(i)
... """
>>>
>>> sum(repeat(tcode03, setup="a=bytearray()", number=100000))/5
0.7459419548511506
>>> sum(repeat(tcode03, setup="from array import array;a=array('b',[])", number=100000))/5
1.5112565837800502
bytearray更快一些,不愧是专门为byte数据优化过的。
insert(0,x)
>>> tcode04 = """\
... for i in range(127):
... a.insert(0,i)
... """
>>>
>>> sum(repeat(tcode04, setup="a=bytearray()", number=1000))/5
0.28905207812786105
>>> sum(repeat(tcode04, setup="from array import array;a=array('b',[])", number=1000))/5
0.303518009185791
在头部的insert,耗时差不多,但还是bytearray要快一丢丢。
只有获取了GIL锁的线程才能操作Python对象或调用Python/C API。
CPython解释器每执行若干条python指令(缺省值100)后会尝试做线程切换(释放GIL)。注意这跟用Python/C API实现的函数不同,C函数中如果没有主动释放GIL,是不会有机会切走的。
deque的那几个接口实现,是C代码,在C代码中,没有释放GIL的机制。
要看源码呀....
本文链接:https://cs.pynote.net/sf/python/202212082/
-- EOF --
-- MORE --