如何优化 Python 代码的执行效率?我分享 3 种加速技巧!

说白了,提升Python速度的方法只有三种:Numba、多线程、Modin。
根据场景选择即可。

Numba 是一个 JIT 加速工具。
当我们去年进行蒙特卡罗模拟时,它几乎卡住了。
使用 @jit(nopython=True) 后,点击几下就快了 1 0 倍 - 但不要将它用于 pandas 工作,这是一种浪费。
还要注意的一点是,如果 Windows 上的多线程需要 __name__ == '__main__' ,则会发生其他情况。
等等,还有别的事。
Numba 对内存使用也非常敏感。
少量数据可能不如原始版本快。

对于多线程,只需使用多处理模块即可。
例如,将数据分成四部分并在四个核上运行。
然而,有一个细节非常重要:不要将其用于 I/O 密集型任务。
例如,硬盘在读取文件时成为瓶颈。
我去年测试过,发现多线程处理几MB的小文件时很慢。
该值只有在速度超过3 000时才实现。

Modin最简单,只需将modin.pandas导入为pd并替换即可,但还需要安装Dask。
例如,对于 1 00 万行数据,groupby 过程显着加速。
然而,如果你使用得太频繁,你会遇到一个陷阱:对用户定义函数(UDF)的支持不完整。
这是你应该注意的。

建议先运行百行级别的函数来测试Numba,然后根据数据量选择pandas或多线程。
莫丁是最后的选择。
老实说,这很令人沮丧。
经常会出现加速效果远不如预期的情况。

Python 的 GIL 是什么鬼,多线程性能究竟如何

那天我在咖啡厅调试一个多线程Python脚本,我花了很长时间才意识到它可能与GIL有关。
这件事真是一个令人恼火的细节。

CPython的GIL,全称是Global Interpreter Lock,本质上是一个互斥锁。
官方的解释是防止多个原生线程同时执行Python字节码,主要是因为CPython的内存管理不是线程安全的。
这听起来像是一个设计缺陷,但这是有历史原因的。

想象一下,如果 CPython 没有 GIL,多线程对于 CPU 密集型任务可能会更有效。
然而问题是,大部分库代码已经基于GIL的线程安全假设,改变它太麻烦了。
MySQL 花了五年时间才拆分 BufferPoolMutex,Python 社区实施类似的改变只会变得更加困难。

我进行了一个小测试,比较双核 Mac Pro 上单线程和多线程执行 1 亿次计数的效率。
单线程连续执行两次,模拟单线程。
多个线程同时执行两个计数器。
结果,多线程比单线程慢,证实了GIL的瓶颈。

但是等等,还有别的事情。
如果你使用像 PyPy 这样没有 GIL 的实现,情况就不同了。
然而,PyPy 兼容性是另一个问题,许多库无法在 PyPy 上运行。
所以虽然GIL并不完美,但它至少保证了与Python的兼容性。

突然想到,也许Python以后会开发出更灵活的线程管理机制?或者使用像Java这样的JIT编译器来解决这个问题?但就目前而言,GIL 仍然是一个难以放弃的环境。
这就像一场婚姻。
你知道它有缺陷,但为了大局,你只能继续维持它。

Python里GIL锁机制 全局解释器锁GIL对Python多线程的影响解析

GIL 是 CPython 中的内存安全锁,可确保单核 CPU 上的线程安全,以牺牲并行性为代价简化内存管理。

CPU密集型任务受GIL影响较大,多线程性能不如单线程。

GIL对IO密集型操作影响不大,多线程仍然有效。

GIL相关方法:多进程、C扩展、无GIL的Python实现、异步IO。

IO密集型任务:多线程或异步IO。

CPU 密集型任务:多进程或 C 扩展。

爬虫:多线程或异步IO。

图像处理:多进程。

数值计算:C 扩展或多处理。

总结:了解GIL并选择合理的并发方案是优化Python性能的关键。

Python怎样实现多线程编程?threading模块详解

哦,我在学习的时候就被你提到的Python多线程搞糊涂了。
该参数模块实际上是这样使用的。
如果你想一想,要创建一个线程,只需new 创建一个线程即可。
读取线程对象,将函数作为目标传递,并将参数列表作为 args 传递。
这很简单。

Python 导入线程
def 任务(名称): print(f "线程 {name}: 开始执行"); 模拟 I/O 操作 print(f "Thread {name}: 执行完成")
thread1 = threading.Thread(target=task, args=("T1 ",)); 线程2 = 线程。
线程(目标=函数,args=(“T2 ”,))
然后thread1 .start(),thread2 .start(),启动线程。
开始之后你得等,不然他跑了你找谁?所以 thread1 .join(), thread2 .join() 主线程卡在这里等待子线程完成运行。

Python 线程1 .start() 线程2 .start() 线程1 .join() thread2 .join() print("所有线程都已完成")
但是!但是你提到的GIL,全局解释器锁,实在是让人头疼。
对于 I/O 密集型任务,例如发出网络请求,多线程可以提高性能。
因为等待I/O时,GIL被释放,其他线程可以运行。
但对于计算密集型任务,例如做一些数学运算,多线程是没有意义的,因为 GIL 被卡在那里,并且只有一个线程可以同时工作。
在这种情况下,您应该使用多处理模块来创建多个进程。

你提到的锁,那个锁,对于防止竞争条件非常重要。
共享资源时,必须锁定它们。

Python 计数器 = 0 锁定=线程。
lock()
removeIncrement(): 国际柜台 对于 _ 在范围内(1 00000): 带锁:自动查找并释放锁 计数器 += 1
线程 = [threading.Thread(target=increment) for _ in range(5 )] 对于 t 螺纹: t.start() 对于 t 螺纹: 加入() print(f "最后计数: {counter}") 输出 5 00000
其他同步前提条件如 RLock、Semaphore、Condition 和 Event 当然有用,但使用起来很复杂,不常用。

队列,那个队列。
队列,线程安全的队列,对于生产者-消费者模型很有用。
点数据的产生和数据的使用非常方便。

Python 导入队列
def 生产者(q): 对于我来说在范围(5 )内: q.put(f"data-{i}"). print(f"product:data-{i}")
客户端 def(q); 说实话: 尝试: 项目 = q.get(超时=1 ); print(f"消耗:{item}").q.task_done() 除了排队。
空: 中断
q = 队列。
队列() 线程(目标=生产者,args=(q,)).start()。
threading.Thread(target=consumer, args=(q,)))start()
线程池,即ThreadPoolExecutor,非常好用。
自动管理弯头的生命周期,不用一一开始混合,省去了很多麻烦。

Python 从并发.futures 导入 ThreadPoolExecutor
process_data(data): print(f"准备:{数据}"); 使用 ThreadPoolExecutor(max_workers=3 ) 作为执行器回答 data.upper()
: futures = [executor.submit(process_data, f"item_{i}") for i in range(1 0)] 对于未来: 打印(future.result())
当然,线程池的优点是它自动化了线程的管理,限制了交互的数量并简化了输出检索。
实用建议:优先使用线程池,明确任务类型。
使用多线程处理 I/O 繁重的任务和计算多进程任务。
应尽可能少地使用它们,以避免死锁和性能下降。
队列生产者消费者模型简单易用,代码可维护性也很高。

就是这样。
无论如何,多线程编程的关键是理解 GIL,选择正确的同步工具,然后为手头的任务选择合适的并行化模型。