15 KiB
目标
本节使用一个爬虫任务和计算任务来展示如何追求代码的性能 。 充分理解线程、协程、进程、同步、异步、阻塞、非阻塞等概念,并能够根据具体场景选择合适的并发模型。 主线问题:如何解决IO和计算速度不匹配、如何任务分解、分发和协作 。
任务
1、CPU密集型/计算密集型:
将sum(i * i for i in range(10**8))
作为一个子任务,运行5次,分别使用单线程、多线程、多进程、多协程、异步等方式,并对比运行时间、内存使用、CPU使用等指标。
2、IO密集型:
讨论分析
1、CPU密集型/计算密集型:
为控制变量,代码在同一台设备上运行,且关闭其他进程,保证CPU资源不被其他进程占用。测试结果如下:
普通做法:
运行前:
CPU使用率:0.0%,内存使用率:53.3%
运行后:
CPU使用率:0.6%,内存使用率:53.0%,内存使用:0.003336MB,峰值内存使用:0.004216MB,单个子任务运行平均时间:7.86s,总运行时间:39.31s
运行结束30秒后:
CPU使用率:0.6%,内存使用率:53.0%
多进程:
运行前:
CPU使用率:0.0%,内存使用率:52.8%
运行后:
CPU使用率:1.3%,内存使用率:52.9%,内存使用:0.096089MB,峰值内存使用:0.121865MB,单个子任务运行平均时间:0.654s,总运行时间:1.86s
运行结束30秒后:
CPU使用率:0.6%,内存使用率:52.7%
多线程:
运行前:
CPU使用率:3.8%,内存使用率:53.1%
运行后:
CPU使用率:3.6%,内存使用率:53.1%,内存使用:0.001472MB,峰值内存使用:0.019296MB,单个子任务运行平均时间:38.89s,总运行时间:39.77s
运行结束30秒后:
CPU使用率:0.7%,内存使用率:52.9%
多线程 + 线程池:
运行前:
CPU使用率:0.0%,内存使用率:52.9%
运行后:
CPU使用率:0.7%,内存使用率:53.1%,内存使用:0.000952MB,峰值内存使用:0.036604MB,单个子任务运行平均时间:40.49s,总运行时间:41.16s
运行结束30秒后:
CPU使用率:0.6%,内存使用率:52.9%
多协程:
运行前:
CPU使用率:1.1%,内存使用率:53.4%
运行后:
CPU使用率:0.2%,内存使用率:53.4%,内存使用:0.009448MB,峰值内存使用:0.011027MB,单个子任务运行平均时间:9.14s,总运行时间:45.70s
运行结束30秒后:
CPU使用率:0.6%,内存使用率:53.4%
多进程 + 异步:
运行前:
CPU使用率:0.8%,内存使用率:54.9%
运行后:
CPU使用率:0.6%,内存使用率:54.9%,内存使用:0.123481MB,峰值内存使用:0.188774MB,单个子任务运行平均时间:0.63s,总运行时间:1.85s
运行结束30秒后:
CPU使用率:0.9%,内存使用率:55.0%
2、IO密集型:
为控制变量,代码在同一台设备上运行,关闭其他进程,并在相同的网络环境下运行,保证网络资源不被其他进程占用。
普通做法:
运行前:
CPU使用率:0.0%,内存使用率:52.5%
运行后:
CPU使用率:0.9%,内存使用率:52.6%,内存使用:0.074968MB,峰值内存使用:4.162339MB,总运行时间:5.56s
运行结束30秒后:
CPU使用率:0.6%,内存使用率:52.5%
多进程:
运行前:
CPU使用率:0.0%,内存使用率:52.7%
运行后:
CPU使用率:5.0%,内存使用率:52.8%,内存使用:0.03219MB,峰值内存使用:0.085236MB,总运行时间:5.81s
运行结束30秒后:
CPU使用率:1.2%,内存使用率:53.1%
多线程:
运行前:
CPU使用率:0.0%,内存使用率:52.9%
运行后:
CPU使用率:0.1%,内存使用率:53.0%,内存使用:MB,峰值内存使用:MB,总运行时间:1.63s
运行结束30秒后:
CPU使用率:0.5%,内存使用率:52.9%
多线程 + 线程池:
运行前:
CPU使用率:1.3%,内存使用率:53.2%
运行后:
CPU使用率:1.3%,内存使用率:53.1%,内存使用:0.092493MB,峰值内存使用:4.371028MB,总运行时间:1.47s
运行结束30秒后:
CPU使用率:0.8%,内存使用率:53.2%
多协程:
运行前:
CPU使用率:1.1%,内存使用率:54.4%
运行后:
CPU使用率:0.8%,内存使用率:54.4%,内存使用:0.753684MB,峰值内存使用:15.09151MB,总运行时间:1.45s
运行结束30秒后:
CPU使用率:0.8%,内存使用率:54.4%
多进程 + 异步:
运行前:
CPU使用率:1.5%,内存使用率:60.0%
运行后:
CPU使用率:0.8%,内存使用率:59.8%,内存使用:0.569592MB,峰值内存使用:6.360097MB,总运行时间:7.40s
运行结束30秒后:
CPU使用率:1.3%,内存使用率:59.6%
总结
1、CPU密集型/计算密集型:
普通做法 | 多进程 | 多线程 | 多线程+线程池 | 多协程 | 多进程+异步 | |
---|---|---|---|---|---|---|
运行前CPU使用率 | 0.0% | 0.0% | 3.8% | 0.0% | 1.1% | 0.8% |
运行后CPU使用率 | 0.6% | 1.3% | 3.6% | 0.7% | 0.2% | 0.6% |
运行结束30秒后CPU使用率 | 0.6% | 0.6% | 0.7% | 0.6% | 0.6% | 0.9% |
运行前内存使用率 | 53.3% | 52.8% | 53.1% | 52.9% | 53.4% | 54.9% |
运行后内存使用率 | 53.0% | 52.9% | 53.1% | 53.1% | 53.4% | 54.9% |
运行结束30秒后内存使用率 | 53.0% | 52.7% | 52.9% | 52.9% | 53.4% | 55.0% |
内存使用 | 0.003336MB | 0.096089MB | 0.001472MB | 0.000952MB | 0.009448MB | 0.123481MB |
峰值内存使用 | 0.004216MB | 0.121865MB | 0.019296MB | 0.036604MB | 0.011027MB | 0.188774MB |
单个子任务运行平均时间 | 7.86s | 0.654s | 38.89s | 40.49s | 9.14s | 0.63s |
总运行时间 | 39.31s | 1.86s | 39.77s | 41.16s | 45.70s | 1.85s |
根据表格中的数据,我们可以对不同的并发和并行编程方法进行详细的总结:
普通做法
- CPU使用率:在任务运行前、运行后和运行结束30秒后的CPU使用率都较低,表明普通做法没有充分利用CPU资源。
- 内存使用率:内存使用率变化不大,表明普通做法对内存的影响较小。
- 内存使用:内存使用量和峰值内存使用量都很低。
- 运行时间:单个子任务和总任务的运行时间都较长,表明普通做法效率较低。
多进程
- CPU使用率:在任务运行后CPU使用率显著增加,表明多进程方法能够充分利用多核CPU的优势。
- 内存使用率:内存使用率略有增加,但变化不大。
- 内存使用:内存使用量和峰值内存使用量较高,表明多进程方法对内存的需求较大。
- 运行时间:单个子任务和总任务的运行时间都显著减少,表明多进程方法在处理CPU密集型任务时效率最高。
多线程
- CPU使用率:在任务运行前、运行后和运行结束30秒后的CPU使用率都较低,表明多线程方法没有充分利用CPU资源。
- 内存使用率:内存使用率变化不大。
- 内存使用:内存使用量和峰值内存使用量较低。
- 运行时间:单个子任务和总任务的运行时间都较长,表明多线程方法在处理CPU密集型任务时效率较低。
多线程+线程池
- CPU使用率:在任务运行后CPU使用率略有增加,但变化不大。
- 内存使用率:内存使用率变化不大。
- 内存使用:内存使用量和峰值内存使用量较低。
- 运行时间:单个子任务和总任务的运行时间都较长,表明多线程+线程池方法在处理CPU密集型任务时效率较低。
多协程
- CPU使用率:在任务运行前、运行后和运行结束30秒后的CPU使用率都较低,表明多协程方法没有充分利用CPU资源。
- 内存使用率:内存使用率变化不大。
- 内存使用:内存使用量和峰值内存使用量较低。
- 运行时间:单个子任务和总任务的运行时间都较长,表明多协程方法在处理CPU密集型任务时效率较低。
多进程+异步
- CPU使用率:在任务运行后CPU使用率显著增加,表明多进程+异步方法能够充分利用多核CPU的优势。
- 内存使用率:内存使用率略有增加,但变化不大。
- 内存使用:内存使用量和峰值内存使用量较高,表明多进程+异步方法对内存的需求较大。
- 运行时间:单个子任务和总任务的运行时间都显著减少,表明多进程+异步方法在处理CPU密集型任务时效率最高。
结论
- 多进程和多进程+异步方法在处理CPU密集型任务时效率最高,能够充分利用多核CPU的优势,但对内存的需求较大。
- 多线程和多线程+线程池方法在处理CPU密集型任务时效率较低,主要是由于Python的全局解释器锁(GIL)的存在,无法实现真正的并行。
- 多协程方法在处理CPU密集型任务时效率较低,但在处理I/O密集型任务时可能会有更好的表现。
- 普通做法效率最低,无法充分利用系统资源。
2、IO密集型:
普通做法 | 多进程 | 多线程 | 多线程+线程池 | 多协程 | 多进程+异步 | |
---|---|---|---|---|---|---|
运行前CPU使用率 | 0.0% | 0.0% | 0.0% | 1.3% | 1.1% | 1.5% |
运行后CPU使用率 | 0.9% | 5.0% | 0.1% | 1.3% | 0.8% | 0.8% |
运行结束30秒后CPU使用率 | 0.6% | 1.2% | 0.5% | 0.8% | 0.8% | 1.3% |
运行前内存使用率 | 52.5% | 52.7% | 52.9% | 53.2% | 54.4% | 60.0% |
运行后内存使用率 | 52.6% | 52.8% | 53.0% | 53.1% | 54.4% | 59.8% |
运行结束30秒后内存使用率 | 52.5% | 53.1% | 52.9% | 53.2% | 54.4% | 59.6% |
内存使用 | 0.074968MB | 0.03219MB | N/A | 0.092493MB | 0.753684MB | 0.569592MB |
峰值内存使用 | 4.162339MB | 0.085236MB | N/A | 4.371028MB | 15.09151MB | 6.360097MB |
总运行时间 | 5.56s | 5.81s | 1.63s | 1.47s | 1.45s | 7.40s |
根据表格中的数据,我们可以对不同的并发和并行编程方法进行详细的总结:
普通做法
- CPU使用率:在任务运行前、运行后和运行结束30秒后的CPU使用率都较低,表明普通做法没有充分利用CPU资源。
- 内存使用率:内存使用率变化不大,表明普通做法对内存的影响较小。
- 内存使用:内存使用量和峰值内存使用量都很低。
- 运行时间:总运行时间为5.56秒,表明普通做法效率较低。
多进程
- CPU使用率:在任务运行后CPU使用率显著增加,表明多进程方法能够充分利用多核CPU的优势。
- 内存使用率:内存使用率略有增加,但变化不大。
- 内存使用:内存使用量和峰值内存使用量较低。
- 运行时间:总运行时间为5.81秒,略高于普通做法,表明多进程方法在处理IO密集型任务时效率较高。
多线程
- CPU使用率:在任务运行前、运行后和运行结束30秒后的CPU使用率都较低,表明多线程方法没有充分利用CPU资源。
- 内存使用率:内存使用率变化不大。
- 内存使用:内存使用量和峰值内存使用量较低。
- 运行时间:总运行时间为1.63秒,显著低于普通做法,表明多线程方法在处理IO密集型任务时效率较高。
多线程+线程池
- CPU使用率:在任务运行后CPU使用率略有增加,但变化不大。
- 内存使用率:内存使用率变化不大。
- 内存使用:内存使用量和峰值内存使用量较低。
- 运行时间:总运行时间为1.47秒,显著低于普通做法,表明多线程+线程池方法在处理IO密集型任务时效率较高。
多协程
- CPU使用率:在任务运行前、运行后和运行结束30秒后的CPU使用率都较低,表明多协程方法没有充分利用CPU资源。
- 内存使用率:内存使用率变化不大。
- 内存使用:内存使用量和峰值内存使用量较高。
- 运行时间:总运行时间为1.45秒,显著低于普通做法,表明多协程方法在处理IO密集型任务时效率较高。
多进程+异步
- CPU使用率:在任务运行后CPU使用率显著增加,表明多进程+异步方法能够充分利用多核CPU的优势。
- 内存使用率:内存使用率略有增加,但变化不大。
- 内存使用:内存使用量和峰值内存使用量较高。
- 运行时间:总运行时间为7.40秒,略高于普通做法,表明多进程+异步方法在处理IO密集型任务时效率较低。
结论
- 多线程、多线程+线程池和多协程方法在处理IO密集型任务时效率最高,能够显著减少总运行时间。
- 多进程方法在处理IO密集型任务时效率较高,但略低于多线程和多协程方法。
- 多进程+异步方法在处理IO密集型任务时效率较低,总运行时间较长。
- 普通做法效率最低,无法充分利用系统资源。