性能与优化
数据结构
多利用python本身的代码
- dict.get()示例
#dict.get(key, default=None)
#key -- 这是要搜索在字典中的键。
#default -- 这是要返回键不存在的的情况下默认值。
def get_fruits(basket, fruit):
return basket.get(fruit, set())
- set()示例
#集合做差集,判断是否有指定元素之外的元素
def has_invalid_fields(fields):
return bool(set(fields) - set(['foo', 'bar'])
- defaultdict()示例
import collections
def add_animal_in_family(species, animal, family):
species[family].add(animal)
species = collections.defaultdict(set)
add_animal_in_family(species, 'cat', 'felidea')
每次试图从字典中访问一个不存在的元素,defaultdict都会使用作为参数传入的这个函数去构造一个新值而不是抛出KeyError。
性能分析
- 使用cProfile模块
$ python -m cProfile myscript.py
可以使用-s选项按其他字段进行排序,如-s time
- C语言分析Valgrind以及可视化工具KCacheGrind
$ python -m cProfile -o myscript.cprof myscript.py
$ pyprof2calltree -k -i myscript.cprof
- dis模块:python字节码的反编译器
#### namedtuple和slots
- python中拥有一些固定属性的简单对象会存储所有的属性在一个字典内,这个字典本身被存在`__dict__`属性中:
```python
class Point(object):
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(1, 2)
p.__dict__
p.z = 42
- 可以通过
__slots__
声明来减少内存开销
class Foobar(object):
__slots__ = 'x'
def __init__(self, x):
self.x = x
- 使用python包
memory_profiler
检测内存使用情况
$ python -m memory_profiler object.py
缓存加速技术
- memoization是指通过缓存函数返回结果来加速函数调用的一种技术。仅当函数是纯函数时结果才可以被缓存,也就是说函数不能有任何副作用或输出,也不能依赖任何全局状态。
- 基本的memoization技术
import math
_SIN_MEMOIZED_VALUES = {}
def memoized_sin(x):
if x not in _SIN_MEMOIZED_VALUES:
_SIN_MEMOIZED_VALUES[x] = math.sin(x)
return _SIN_MEMOIZED_VALUES[x]
- 使用functools.lru_cache实现
import functools
import math
@functools.lru_cache(maxsize = 2)
def memoized_sin(x):
return math.sin(x)
使用后可以使用memoized_sin.cache_info()查看缓存情况
GIL(Global Interpreter Lock, 全局解释器锁)
利用memoryview实现浅拷贝(引用)
- 使用memoryview可以在内存区域的任何点放入数据
ba = bytearray(8)
ba_at_4 = memoryview(ba)[4:]
#引用bytearray,从其偏移索引4到其结尾
with open("/dev/urandom", "rb") as source:
source.readinto(ba_at_4)
#将/dev/urandom的内容写入bytearray中从偏移索引4到结尾的位置,精确且高效地只读写了4字节
多进程与多线程
- multiprocessing模块不仅可以有效地将负载分散到多个本地处理器上,而且可以通过它的multiprocessing.managers对象在网络中分散负载。它还提供了双向传输,以使进程间可以彼此交换信息。
- 每次考虑在一定时间内并行处理一些工作时,最好依靠多进程创建(fork)多个作业,以便能够在多个CPU核之间分散负载。
异步和事件驱动架构
- 事件驱动架构背后的技术是事件循环的建立。程序调用一个函数,它会一直阻塞直到收到事件。其核心思想是令程序在等待输入输出完成前保持忙碌状态,最基本的事件通常类似于“我有数据就绪可被读取”或者“我可以无阻塞地写入数据”
- 在Unix中,用于构建这种事件循环的标准函数是系统调用select(2)或者poll(2)。它们会对几个文件描述符进行监听,并在其中之一准备好读或写时做出响应。
- python中有select,asyncio,pyev
面向服务架构
使用ZeroMQ
RDBMS和ORM
SQLAlchemy PostgreSQL
上下文管理器
-
上下文管理协议:
- 调用方法A
- 执行一段代码
- 调用方法B
with (open) as :执行一段代码
这里希望调用方法B必须总是在调用方法A之后。open函数很好地阐明了这一模式,打开文件并在内部分配一个文件描述符的构造函数便是方法A。释放对应文件描述符的close方法就是方法B。显然,close方法总是应该在实例化文件对象之后调用。
contextlib标准库提供了contextmanager,通过生成器构造
__enter__
和__exit__
方法,从而简化了这一机制的实现。在流水线对象上使用上下文管理器
import contextlib
class Pipeline(object):
def _publish(self, objects):
#image publication code here
pass
def _flush(self):
#image flushing code here
pass
@contextlib.contextmanager
def publisher(self):
try:
yield self._publish
finally:
self._flush()
现在,当用户在使用流水线发布某些数据时,他们无需使用_publish或者_flush。用户只需请求一个使用了名组(eponym)函数的publisher并使用它。
pipeline = Pipeline()
with pipeline.publisher() as publisher:
publisher([1, 2, 3, 4])
#当提供这样一个API时,就不会遇到用户错误
- 通过一条with语句同时打开两个文件
with open("file1", "r") as source, open("file2", "w") as destination:
destination.write(source.read())