Python 进阶
Python 协程实现原理
dict 和 set 实现原理
Python 线程安全
Python 抽象语法树(AST)
Python 日志输出
Python 扩展入门(一)
Python 程序执行原理
Python 垃圾回收
Python 动态创建类
检查工具
PyFrameObject
yield 生成器工作原理
dict 设计与实现
Python 性能分析原理
PyCodeObject
Python 弱引用
Python 性能分析原理(二)
Python 源码分析(一)
Python Annotated
Python 依赖注入
python freelist
python代码编译成pyc
Python mmap 内存映射文件
Python值得学习的内容
async Future 对象
asyncio loop的实现
asyncio.sleep 的实现
asyncio 原理
Python 代码加密
Python Token类型
Python 扩展入门(二)
Python 性能优化
本文档使用 MrDoc 发布
-
+
首页
Python 性能优化
1. **关于GIL锁** Python解释器被一个全局解释器锁保护着,它确保任何时候都只有一个Python线程执行。 GIL最大的问题就是使得多线程程序并不能利用多核CPU(比如一个使用了多个线程的计算密集型程序只会在一个单CPU上面运行)。 > 在讨论普通的GIL之前,有一点要强调的是GIL只会影响到那些严重依赖CPU的程序(比如计算型的)。 如果你的程序大部分只会涉及到I/O,比如网络交互,那么使用多线程就很合适, 因为它们大部分时间都在等待。实际上,你完全可以放心的创建几千个Python线程, 现代操作系统运行这么多线程没有任何压力,没啥可担心的。 我们有两种方案来解决GIL的缺点。 第一种方案,如果你完全工作于Python环境中,你可以使用 multiprocessing 模块来创建一个进程池, 并像协同处理器一样的使用它。例如,假如你有如下的线程代码: ```python3 # Performs a large calculation (CPU bound) def some_work(args): ... return result # A thread that calls the above function def some_thread(): while True: ... r = some_work(args) ... ``` 修改代码,使用进程池: ```python3 # Processing pool (see below for initiazation) pool = None # Performs a large calculation (CPU bound) def some_work(args): ... return result # A thread that calls the above function def some_thread(): while True: ... r = pool.apply(some_work, (args)) ... # Initiaze the pool if __name__ == '__main__': import multiprocessing pool = multiprocessing.Pool() ``` 这个通过使用一个技巧利用进程池解决了GIL的问题。 当一个线程想要执行CPU密集型工作时,会将任务发给进程池。 然后进程池会在另外一个进程中启动一个单独的Python解释器来工作。 当线程等待结果的时候会释放GIL。 并且,由于计算任务在单独解释器中执行,那么就不会受限于GIL了。 在一个多核系统上面,你会发现这个技术可以让你很好的利用多CPU的优势。 另外一个解决GIL的方案是使用C扩展编程技术。主要思想是将计算密集型任务转移给C,跟Python独立,在工作的时候在C代码中释放GIL。 这可以通过在C代码中插入下面这样的特殊宏来完成: ``` #include "Python.h" ... PyObject *pyfunc(PyObject *self, PyObject *args) { ... Py_BEGIN_ALLOW_THREADS // Threaded C code ... Py_END_ALLOW_THREADS ... } ``` 如果你使用其他工具访问C语言,比如对于Cython的ctypes库,你不需要做任何事。 例如,ctypes在调用C时会自动释放GIL。 > C扩展最重要的特征是它们和Python解释器是保持独立的。 也就是说,如果你准备将Python中的任务分配到C中去执行, 你需要确保C代码的操作跟Python保持独立, 这就意味着不要使用Python数据结构以及不要调用Python的C API。 另外一个就是你要确保C扩展所做的工作是足够的,值得你这样做。 也就是说C扩展担负起了大量的计算任务,而不是少数几个计算。 2. **使用__slot__** 3. **获取局部变量的值要比获取对象的属性要快一点** 获取局部变量实现方式是使用数组下标取值,因此时间复杂度是O(1)。 获取对象的属性,其实就是从对象的`__dict__`字典中获取kv,字典的查找理论上也是O(1),但是有可能存在哈希冲突,因此理论上比局部变量慢一点。 > 获取局部变量的字节码是`LOAD_FAST`,从源码`ceval.c`可以看到,它的实现方式是使用数组下标取值; 字节码可以通过dis.dis函数查看; 4. **减少使用list.append** `append`扩容时会重新创建一个列表并将数据迁移过去,比较耗时。 5. **正确判断元素是否存在** 判断列表中是否存在某个元素,复杂度为O(n); 判断集合中是否存在某个元素,复杂度为O(1); 因为set是用哈希表实现的; 6. **尽量避免频繁往字典中插入数据** 字典的实现方式是哈希表,会存在哈希冲突的情况,为了减少哈希冲突,Python中的哈希表装载率超过0.6的时候就会自动扩容,扩容的时候会重新创建一个更大的哈希表并将数据迁移过去,耗时较多。 ### 参考 > [Python 全局锁的问题](https://python3-cookbook.readthedocs.io/zh_CN/latest/c12/p09_dealing_with_gil_stop_worring_about_it.html "Python的全局锁问题") [Python 性能优化方法总结](https://hanfeng.ink/post/python_performance/ "Python代码性能优化方法总结")
gaojian
2022年1月20日 16:44
分享文档
收藏文档
上一篇
下一篇
微信扫一扫
复制链接
手机扫一扫进行分享
复制链接
关于 MrDoc
觅思文档MrDoc
是
州的先生
开发并开源的在线文档系统,其适合作为个人和小型团队的云笔记、文档和知识库管理工具。
如果觅思文档给你或你的团队带来了帮助,欢迎对作者进行一些打赏捐助,这将有力支持作者持续投入精力更新和维护觅思文档,感谢你的捐助!
>>>捐助鸣谢列表
微信
支付宝
QQ
PayPal
Markdown文件
分享
链接
类型
密码
更新密码