您的位置:首页 > 编程语言 > Python开发

Python 性能优化【2】 -- 高效的使用序列与字典、集合

2017-09-11 08:30 537 查看
本篇主要对 Python 常用的几种数据结构: 列表、元组、字典、集合以及 numpy 进行简单的分析,并根据其增删改查的内存分配特点提供相应的使用建议。

一. 可变数组: 列表

列表 list 是一个动态的数组,支持 resize,其元素个数是可变的。其可变性的代价在于列表存储需要额外的内存和额外的计算。

列表可变的内存变化

当长度为 N 的 Python 列表第一次需要添加数据时,Python 会创建一个新的列表,足够存放原来的 N 个元素以及需要额外添加的新元素,同时会额外的预留出空间,为将来的新增元素作准备。假设此时的最大容量为 M,后来新加元素时,如果新加后的元素个数不超过 M 个,可以直接保存,如果超过 M 个,则会创建新的列表,将旧列表的元素和新加的元素存储到新列表中,清除旧的列表。新列表也会留出额外的空间。

可以看出,列表的可变性代价就是其需要额外预留的存储空间和计算新列表的长度的运算过程。当需要创建大量的小列表或者存在一个长度很大的大型列表时,其额外的存储空间消耗将会非常严重,需要考虑使用更优的存储方式

二. 静态数组: 元组

元组是固定且不可变的,一旦创建,其内容和大小就不能在被修改。比较适合存储一个不会改变的事物的多个属性。

元组因为其不可变性,因此并不会预留额外的存储空间,因此相对于列表,其资源占用更少。另外对元组并没有提供类似列表 append 的操作,可以通过元组相加来实现元组的变化,不过返回的是一个新的元组。任何对元组的元素就行修改的操作都会导致新的内存分配和复制操作,然后返回一个新的元组

元组其他特性

资源缓存: Python 有自己的垃圾回收机制,然是元组即使不在被使用,也不会被立刻清理将内存还给系统,还是留待未来使用,当需要一个同样大小的内存时,就不用在请求系统进行新的内存资源分配了。这意味着元组的创建非常轻量级。其比列表创建要快得多。示例如下



下面总结下 Python 列表与元组的异同:

列表元素与长度可变,元组不可变,其内部数据一旦创建便无法修改

元组缓存于 Python 的运行环境,因此每次创建元组时无需访问内核去分配内存

列表适合保存多个互相独立对象的数据集合;元素更合适描述一个不会改变的事物的多个属性

下面是 《Python 高性能编程》 中记在的一个例子:

前 20 个质数: 数据是静态且不会改变,使用元组

一个人的年龄,身高,体重: 数据是变化的,使用列表

编程语言的名字: 数据是会不断变化的,使用列i表

一个人的生日和出生地: 出生之后不会变化,使用元组

某次台球的游戏结果: 结束后不会变化,使用元组

一系列台球的游戏结果: 随着游戏次数的变化而变化,使用列表

高效搜索

在很多时候,先排序后搜索是最佳的查找方案。排序后使用二分搜索进行查找,其平均情况的复杂度是 O(logN)。关于二分搜索可以使用 Python 的 bisect 模块,具体使用可以参阅其文档 bisect 模块

三. 字典和集合

字典和集合内存的数据结构是散列表,通过散列表其查询和插入的效率可以达到 O(1)。字典是键值对的组合,而集合就是一堆键的组合

1. 字典的插入与获取原理

新数据的插入主要取决的数据的散列值和其散列值如何跟其他对象进行比较,其插入过程如下:

插入数据时,首先计算键的散列值并掩码来得到一个有效的数组索引

通过索引找到对应的桶(即存储空间),如果该空间已经被使用,则需要通过一个简单的线性函数计算出新的索引,这一方法称为嗅探

根据嗅探后获得的新索引找到对应的存储空间,如果空间未被使用,则将键或者键值插入到这一内存块中。

以上就是字典与集合的插入过程,查询也与此过程类似,首先计算键的散列值得到索引,然后根据索引检索到对应的内存空间,如果该处空间的键与现有键不一致,则通过嗅探继续

计算新的索引进行查找,直到找到数据或者找到一个空桶,空桶意味着数据不存在。

2. 字典与命名空间

【1】 Python 访问一个变量、函数或者模块的顺序

查找 locals 数组,其内保存了所有本地变量的条目,这是唯一一处不需要字典查询的部分

如果不在 locals 数组中,则搜索 globals() 字典

如果依旧不在字典中,则搜索 builtin 对象

当我们需要引入其他模块的函数使用时,更好的方式就是显式的导入函数,而不是只导入模块,这样可以减少一次查询。当需要在循环中使用某些函数时,更好的方式是创建一个本地变量来保存一个

函数的全局引用。

import datetime # 使用 datetime 时会查询两次,一次查询 datetime 模块,另一次查询 datetime 函数
from datetime import datetime # 这里只有一次查询 datetime 函数的操作

# 使用本地变量保存函数的全局引用,可以减少查询,提高性能
from math import sin
def high_loop(n):
local_sin = sin
result = 0
for i in range(n):
result += sin(i)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: