diff --git a/B 高性能模式/000 普通做法.py b/B 高性能模式/000 普通做法.py index a97e3b2..ed0a43b 100644 --- a/B 高性能模式/000 普通做法.py +++ b/B 高性能模式/000 普通做法.py @@ -1,74 +1,22 @@ -""" -根据提供的关键词列表,爬取天水市人民政府网站上指定日期内与关键词相关的新闻的标题,并将其存储至数据库中。 - -考虑到相关因素,因此本代码只爬取前10页的新闻内容,即最多100条新闻作为测试。 - -此方法为普通做法,即使用requests库通过Post请求爬取网页内容,再使用json提取新闻内容。 - -注意:本代码中的关键词列表默认为['灾害'],日期范围默认为2018年1月1日至2018年12月31日。 - -Args: - keywords: 用于搜索新闻的关键词列表 - begin_date: 开始日期,用于搜索 - end_date: 结束日期,用于搜索 - size: 一次请求返回的新闻或政策的最大数量 - -Examples: - ``` - main(keywords=['灾害'], - begin_date='2018-01-01', - end_date='2018-12-31', - size=10) - ``` -""" import util -import logging -from typing import List - -import tqdm - -@util.timeit -def main(keywords: List[str], begin_date: str, end_date: str, size: int = 10): - """ - 爬取与提供的关键词列表相关的新闻. - Args: - keywords: 用于搜索新闻的关键词列表 - begin_date: 开始日期,用于搜索 - end_date: 结束日期,用于搜索 - size: 一次请求返回的新闻或政策的最大数量 - """ - logging.basicConfig(level=logging.INFO, - format="%(asctime)s - %(levelname)s - %(message)s", - datefmt="%Y-%m-%d %H:%M:%S", - filename='log.txt', - encoding='utf-8') +@util.measure_performance +def compute_task(): + for _ in range(5): + util.compute_task() - logging.info("开始运行普通爬取") - spider = util.Spider(keywords=keywords, - begin_date=begin_date, - end_date=end_date, - size=size) +@util.measure_performance +def io_task(): + for url in util.urls: + util.fetch_url(url) - pbar = tqdm.tqdm(total=size * 10, desc='普通爬取进度', unit='条', ncols=80) - title_list = [] - for keyword in keywords: - for current in range(1, 11): - logging.info(f'keyword: {keyword}, current: {current}') - config = spider.get_config(keyword, current) - data = spider.fetch(config) - title_list += spider.parse(data) - pbar.update(size) - spider.save(title_list) - pbar.close() - logging.info("爬取完成") +def main(): + compute_task() + io_task() if __name__ == "__main__": - main(keywords=['灾害'], - begin_date='2018-01-01', - end_date='2018-12-31', - size=10) + main() diff --git a/B 高性能模式/010 多进程.py b/B 高性能模式/010 多进程.py index 94177bc..9899329 100644 --- a/B 高性能模式/010 多进程.py +++ b/B 高性能模式/010 多进程.py @@ -1,86 +1,34 @@ -""" -根据提供的关键词列表,爬取天水市人民政府网站上指定日期内与关键词相关的新闻的标题,并将其存储至数据库中。 - -考虑到相关因素,因此本代码只爬取前10页的新闻内容,即最多100条新闻作为测试。 - -此方法为多进程做法,即使用多进程并发爬取网页内容,再使用json提取新闻内容。 - -注意:本代码中的关键词列表默认为['灾害'],日期范围默认为2018年1月1日至2018年12月31日。 - -Args: - keywords: 用于搜索新闻的关键词列表 - begin_date: 开始日期,用于搜索 - end_date: 结束日期,用于搜索 - size: 一次请求返回的新闻或政策的最大数量 - -Examples: - ``` - main(keywords=['灾害'], - begin_date='2018-01-01', - end_date='2018-12-31', - size=10) - ``` -""" import util -import logging -from typing import List import multiprocessing -import tqdm - -lock = multiprocessing.Lock() - - -@util.timeit -def main(keywords: List[str], begin_date: str, end_date: str, size: int = 10): - """ - 爬取与提供的关键词列表相关的新闻. - - Args: - keywords: 用于搜索新闻的关键词列表 - begin_date: 开始日期,用于搜索 - end_date: 结束日期,用于搜索 - size: 一次请求返回的新闻或政策的最大数量 - """ - logging.basicConfig(level=logging.INFO, - format="%(asctime)s - %(levelname)s - %(message)s", - datefmt="%Y-%m-%d %H:%M:%S", - filename='log.txt', - encoding='utf-8') - - logging.info("开始运行普通做法") - - spider = util.Spider(keywords=keywords, - begin_date=begin_date, - end_date=end_date, - size=size) - title_list = [] - pbar = tqdm.tqdm(total=size * 10, desc='多进程爬取进度', unit='条', ncols=80) +@util.measure_performance +def compute_task(): + processes = [ + multiprocessing.Process(target=util.compute_task) for _ in range(5) + ] + for process in processes: + process.start() + for process in processes: + process.join() - with multiprocessing.Pool(processes=5) as pool: - results = [] - for keyword in keywords: - for current in range(1, 11): - logging.info(f'keyword: {keyword}, current: {current}') - config = spider.get_config(keyword, current) - results.append(pool.apply_async(spider.fetch, (config, ))) - for result in results: - data = result.get() - title_list += spider.parse(data) +@util.measure_performance +def io_task(): + processes = [ + multiprocessing.Process(target=util.fetch_url, args=(url, )) + for url in util.urls + ] + for process in processes: + process.start() + for process in processes: + process.join() - lock.acquire() - pbar.update(size) - lock.release() - spider.save(title_list) - pbar.close() - logging.info("爬取完成") +def main(): + compute_task() + io_task() if __name__ == "__main__": - main(keywords=['灾害'], - begin_date='2018-01-01', - end_date='2018-12-31', - size=10) + main() diff --git a/B 高性能模式/020 多线程.py b/B 高性能模式/020 多线程.py index 19bc3c4..44fb35d 100644 --- a/B 高性能模式/020 多线程.py +++ b/B 高性能模式/020 多线程.py @@ -1,89 +1,52 @@ -""" -根据提供的关键词列表,爬取天水市人民政府网站上指定日期内与关键词相关的新闻的标题,并将其存储至数据库中。 - -考虑到相关因素,因此本代码只爬取前10页的新闻内容,即最多100条新闻作为测试。 - -此方法为多线程做法,即使用多线程并行爬取网页内容,再使用json提取新闻内容。 - -注意:本代码中的关键词列表默认为['灾害'],日期范围默认为2018年1月1日至2018年12月31日。 - -Args: - keywords: 用于搜索新闻的关键词列表 - begin_date: 开始日期,用于搜索 - end_date: 结束日期,用于搜索 - size: 一次请求返回的新闻或政策的最大数量 - -Examples: - ``` - main(keywords=['灾害'], - begin_date='2018-01-01', - end_date='2018-12-31', - size=10) - ``` -""" - from concurrent.futures import ThreadPoolExecutor, as_completed import threading import util -import logging -from typing import List -import tqdm -lock = threading.Lock() +@util.measure_performance +def compute_task(): + threads = [threading.Thread(target=util.compute_task) for _ in range(5)] + for thread in threads: + thread.start() + for thread in threads: + thread.join() -@util.timeit -def main(keywords: List[str], begin_date: str, end_date: str, size: int = 10): - """ - 爬取与提供的关键词列表相关的新闻. +@util.measure_performance +def compute_task_threadpool(): + with ThreadPoolExecutor(max_workers=5) as executor: + tasks = [executor.submit(util.compute_task) for _ in range(5)] + for future in as_completed(tasks): + future.result() - Args: - keywords: 用于搜索新闻的关键词列表 - begin_date: 开始日期,用于搜索 - end_date: 结束日期,用于搜索 - size: 一次请求返回的新闻或政策的最大数量 - """ - logging.basicConfig(level=logging.INFO, - format="%(asctime)s - %(levelname)s - %(message)s", - datefmt="%Y-%m-%d %H:%M:%S", - filename='log.txt', - encoding='utf-8') - logging.info("开始运行多线程爬取") +@util.measure_performance +def io_task(): + threads = [ + threading.Thread(target=util.fetch_url, args=(url, )) + for url in util.urls + ] + for thread in threads: + thread.start() + for thread in threads: + thread.join() - spider = util.Spider(keywords=keywords, - begin_date=begin_date, - end_date=end_date, - size=size) - pbar = tqdm.tqdm(total=size * 10, desc='多线程爬取进度', unit='条', ncols=80) - title_list = [] - tasks = [] +# 多线程+线程池优化 +@util.measure_performance +def io_task_threadpool(): with ThreadPoolExecutor(max_workers=5) as executor: - for keyword in keywords: - for current in range(1, 11): - logging.info(f'keyword: {keyword}, current: {current}') - - config = spider.get_config(keyword, current) - future = executor.submit(spider.fetch, config) - tasks.append(future) - # 更新进度条 - lock.acquire() - pbar.update(size) - lock.release() - + tasks = [executor.submit(util.fetch_url, url) for url in util.urls] for future in as_completed(tasks): - data = future.result() - title_list += spider.parse(data) + future.result() + - spider.save(title_list) - pbar.close() - logging.info("爬取完成") +def main(): + compute_task() + compute_task_threadpool() + io_task() + io_task_threadpool() if __name__ == "__main__": - main(keywords=['灾害'], - begin_date='2018-01-01', - end_date='2018-12-31', - size=10) + main() diff --git a/B 高性能模式/030 协程.py b/B 高性能模式/030 协程.py index 291736f..05e6b43 100644 --- a/B 高性能模式/030 协程.py +++ b/B 高性能模式/030 协程.py @@ -1,89 +1,21 @@ -""" -根据提供的关键词列表,爬取天水市人民政府网站上指定日期内与关键词相关的新闻的标题,并将其存储至数据库中。 - -考虑到相关因素,因此本代码只爬取前10页的新闻内容,即最多100条新闻作为测试。 - -此方法为协程做法,即使用gevent库通过协程并发爬取网页内容,再使用json提取新闻内容。 - -注意:本代码中的关键词列表默认为['灾害'],日期范围默认为2018年1月1日至2018年12月31日。 - -Args: - keywords: 用于搜索新闻的关键词列表 - begin_date: 开始日期,用于搜索 - end_date: 结束日期,用于搜索 - size: 一次请求返回的新闻或政策的最大数量 - -Examples: - ``` - main(keywords=['灾害'], - begin_date='2018-01-01', - end_date='2018-12-31', - size=10) - ``` -""" - -import gevent -from gevent import monkey - -# 打补丁,使标准库能够与gevent协同工作 -monkey.patch_all() - +import asyncio import util -import logging -from typing import List - -import tqdm - - -@util.timeit -def main(keywords: List[str], begin_date: str, end_date: str, size: int = 10): - """ - 爬取与提供的关键词列表相关的新闻. - - Args: - keywords: 用于搜索新闻的关键词列表 - begin_date: 开始日期,用于搜索 - end_date: 结束日期,用于搜索 - size: 一次请求返回的新闻或政策的最大数量 - """ - logging.basicConfig(level=logging.INFO, - format="%(asctime)s - %(levelname)s - %(message)s", - datefmt="%Y-%m-%d %H:%M:%S", - filename='log.txt', - encoding='utf-8') - - logging.info("开始运行协程爬取") - spider = util.Spider(keywords=keywords, - begin_date=begin_date, - end_date=end_date, - size=size) - pbar = tqdm.tqdm(total=size * 10, desc='协程爬取进度', unit='条', ncols=80) - title_list = [] +@util.measure_performance_async +async def async_compute_task(): + await asyncio.gather(*[util.async_compute_task() for _ in range(5)]) - def fetch_and_parse(keyword, current): - logging.info(f'keyword: {keyword}, current: {current}') - config = spider.get_config(keyword, current) - data = spider.fetch(config) - titles = spider.parse(data) - title_list.extend(titles) - pbar.update(size) - jobs = [ - gevent.spawn(fetch_and_parse, keyword, current) for keyword in keywords - for current in range(1, 11) - ] +@util.measure_performance_async +async def async_io_task(): + await asyncio.gather(*[util.fetch_url_async(url) for url in util.urls]) - gevent.joinall(jobs) - spider.save(title_list) - pbar.close() - logging.info("爬取完成") +def main(): + asyncio.run(async_compute_task()) + asyncio.run(async_io_task()) if __name__ == "__main__": - main(keywords=['灾害'], - begin_date='2018-01-01', - end_date='2018-12-31', - size=10) + main() diff --git a/B 高性能模式/040 异步.py b/B 高性能模式/040 异步.py index fb2b1c1..6bed1fe 100644 --- a/B 高性能模式/040 异步.py +++ b/B 高性能模式/040 异步.py @@ -1,85 +1,34 @@ -""" -根据提供的关键词列表,爬取天水市人民政府网站上指定日期内与关键词相关的新闻的标题,并将其存储至数据库中。 - -考虑到相关因素,因此本代码只爬取前10页的新闻内容,即最多100条新闻作为测试。 - -此方法为多线程做法,即使用异步并行爬取网页内容,再使用json提取新闻内容。 - -注意:本代码中的关键词列表默认为['灾害'],日期范围默认为2018年1月1日至2018年12月31日。 - -Args: - keywords: 用于搜索新闻的关键词列表 - begin_date: 开始日期,用于搜索 - end_date: 结束日期,用于搜索 - size: 一次请求返回的新闻或政策的最大数量 - -Examples: - ``` - asyncio.run( - main_async(keywords=['灾害'], - begin_date='2018-01-01', - end_date='2018-12-31', - size=10)) - ``` -""" - import asyncio +from multiprocessing import Process +from concurrent.futures import ProcessPoolExecutor import util -import logging -from typing import List -import tqdm - -@util.timeit_async -async def main_async(keywords: List[str], - begin_date: str, - end_date: str, - size: int = 10): - """ - 使用异步方式爬取与提供的关键词列表相关的新闻. - Args: - keywords: 用于搜索新闻的关键词列表 - begin_date: 开始日期,用于搜索 - end_date: 结束日期,用于搜索 - size: 一次请求返回的新闻或政策的最大数量 - """ - logging.basicConfig(level=logging.INFO, - format="%(asctime)s - %(levelname)s - %(message)s", - datefmt="%Y-%m-%d %H:%M:%S", - filename='log.txt', - encoding='utf-8') +@util.measure_performance +def async_compute_task(): + with ProcessPoolExecutor() as pool: + futures = [pool.submit(util.compute_task) for _ in range(5)] + for future in futures: + future.result() - logging.info("开始运行异步爬取") - spider = util.Spider(keywords=keywords, - begin_date=begin_date, - end_date=end_date, - size=size) +@util.measure_performance_async +async def async_io_task(): + chunk_size = len(util.urls) // 5 + chunks = [ + util.urls[i:i + chunk_size] + for i in range(0, len(util.urls), chunk_size) + ] - pbar = tqdm.tqdm(total=size * 10, desc='异步爬取进度', unit='条', ncols=80) - title_list = [] - tasks = [] - for keyword in keywords: - for current in range(1, 11): - logging.info(f'keyword: {keyword}, current: {current}') - config = spider.get_config(keyword, current) - task = asyncio.create_task(spider.fetch_async(config)) - tasks.append(task) + for chunk in chunks: + tasks = [util.fetch_url_async(url) for url in chunk] + await asyncio.gather(*tasks) - for task in asyncio.as_completed(tasks): - data = await task - title_list += spider.parse(data) - pbar.update(size) - spider.save(title_list) - pbar.close() - logging.info("爬取完成") +def main(): + # async_compute_task() + asyncio.run(async_io_task()) if __name__ == "__main__": - asyncio.run( - main_async(keywords=['灾害'], - begin_date='2018-01-01', - end_date='2018-12-31', - size=10)) + main() diff --git a/B 高性能模式/os 机制.md b/B 高性能模式/os 机制.md new file mode 100644 index 0000000..9dc2024 --- /dev/null +++ b/B 高性能模式/os 机制.md @@ -0,0 +1,17 @@ + +段:存放的是全局变量和静态变量 +栈:系统自动分配释放,函数参数值,局部变量,返回地址等在此 +堆:存放动态分配的数据,由开发人员自行管理 + +进程表会记录进程在内存的位置,PID 是多少,以及当前什么状态,内存给它分配了多大使用空间以及属于哪个用户 + +每个用户态线程通过系统调用创建一个绑定的内核线程,Windows NT 即采用这种模型 ; +n 个用户态线程对应 m 个内核态线程。m 通常设置为核数,Linux 即采用的这种模型 + +在 Linux 中,操作系统采用虚拟内存管理技术,使得进程都拥有独立的虚拟内存空间,理由也比较直接,物理内存不够用且不安全(用户不能直接访问物理内存)。Linux 内核看来只有进程而没有线程。Linux所谓的线程其实是与其他进程共享资源的轻量级进程。为什么说是轻量级呢?在于它只有一个执行上下文和调度程序所需的信息,与父进程共享进程地址空间 。 + +虚拟内存技术,把进程虚拟地址空间划分成用户空间和内核空间。 +在 32 位的操作系统中,4GB 的进程地址空间分为,用户空间和内核空间,用户空间为 0~3G,内核地址空间占据 3~4G, +用户不能直接操作内核空间虚拟地址,只有通过系统调用的方式访问内核空间。 + +线程共享虚拟内存和全局变量等资源,线程拥有自己的私有数据比如栈和寄存器。 \ No newline at end of file diff --git a/B 高性能模式/readme.md b/B 高性能模式/readme.md index 7470dbf..4b07e7e 100644 --- a/B 高性能模式/readme.md +++ b/B 高性能模式/readme.md @@ -1,25 +1,269 @@ # 目标 -本节使用一个爬虫任务来展示如何追求代码的性能 。 +本节使用一个爬虫任务和计算任务来展示如何追求代码的性能 。 充分理解线程、协程、进程、同步、异步、阻塞、非阻塞等概念,并能够根据具体场景选择合适的并发模型。 主线问题:如何解决IO和计算速度不匹配、如何任务分解、分发和协作 。 # 任务 +## 1、CPU密集型/计算密集型: +  将```sum(i * i for i in range(10**8))```作为一个子任务,运行5次,分别使用单线程、多线程、多进程、多协程、异步等方式,并对比运行时间、内存使用、CPU使用等指标。 +## 2、IO密集型: # 讨论分析 -普通做法连续进行了五次测试,时间分别为34.231s、34.091s、34.164s、34.226s、33.958s,平均时间为34.134s -多进程(进程数=5)连续进行了五次测试,时间分别为7.719s、7.716s、7.690s、7.730s、7.711s,平均时间为7.7132s -多线程(线程数=5)连续进行了五次测试,时间分别为7.185s、7.964s、6.983s、6.969s、7.035s,平均时间为7.2272s -协程连续进行了五次测试,时间分别为3.775s、3.807s、3.733s、3.824s、3.744s,平均时间为3.776s -异步连续进行了五次测试,时间分别为6.975s、7.675s、7.018s、7.032s、7.049s,平均时间为7.1498s -注:为保证公平性,每一次Post请求后休眠3秒 - -可以看出,协程的性能最好,普通做法的性能最差,多线程、多进程和异步的性能介于两者之间。 -考虑到多进程和多线程是故意开的5个进程和线程,而协程是单线程,所以协程的性能最好。 -另外,异步的性能最差,可能是由于异步的并发模型需要频繁地切换线程,导致性能下降。 -总的来说,协程的性能最好,多线程和多进程的性能介于两者之间,普通做法的性能最差。 +## 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% # 总结 -协程的性能最好,多线程和多进程的性能介于两者之间,普通做法的性能最差。 \ No newline at end of file +## 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密集型任务时效率较低,总运行时间较长。 +- **普通做法**效率最低,无法充分利用系统资源。 + + \ No newline at end of file diff --git a/B 高性能模式/util.py b/B 高性能模式/util.py index 5d7495a..814ed32 100644 --- a/B 高性能模式/util.py +++ b/B 高性能模式/util.py @@ -1,188 +1,107 @@ -""" - -""" -import re import time -import functools -import json -import asyncio +import psutil +import tracemalloc import requests -from typing import Any, Dict, List - - -class Spider: - """ - 爬虫类。 - - Args: - keywords (List[str]): 用于搜索新闻的关键词列表 - begin_date (str): 开始日期,用于搜索 - end_date (str): 结束日期,用于搜索 - size (int): 一次请求返回的新闻或政策的最大数量 - - Attributes: - URL (str): 网址 - """ - # 天水市人民政府网站 - URL = ('https://www.tianshui.gov.cn/aop_component/' - '/webber/search/search/search/queryPage') - - def __init__(self, keywords: List[str], begin_date: str, end_date: str, - size: int): - self.keywords = keywords - self.begin_date = begin_date - self.end_date = end_date - self.size = size - - def get_config(self, keyword: str, current: int) -> Dict[str, Any]: - """ - 获取配置信息。 - - Args: - keyword (str): 关键词 - size (int): 一次请求返回的新闻的最大数量 - - Returns: - Dict[str, Any]: 配置信息 - """ - - return { - "aliasName": "article_data,open_data,mailbox_data,article_file", - "keyWord": keyword, - "lastkeyWord": keyword, - "searchKeyWord": False, - "orderType": "score", - "searchType": "text", - "searchScope": "3", - "searchOperator": 0, - "searchDateType": "custom", - "searchDateName": f"{self.begin_date}-{self.end_date}", - "beginDate": self.begin_date, - "endDate": self.end_date, - "showId": "c2ee13065aae85d7a998b8a3cd645961", - "auditing": ["1"], - "owner": "1912126876", - "token": "tourist", - "urlPrefix": "/aop_component/", - "page": { - "current": current, - "size": self.size, - "pageSizes": [2, 5, 10, 20, 50, 100], - "total": 0, - "totalPage": 0, - "indexs": [] - }, - "advance": False, - "advanceKeyWord": "", - "lang": "i18n_zh_CN" - } - - def generate_headers(self) -> dict: - """ - 生成请求头。 - - Returns: - dict: 请求头 - """ - return { - 'Authorization': - 'tourist', - 'User-Agent': - ('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit' - '/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari' - '/537.36 Edg/124.0.0.0') - } - - def fetch(self, config: Dict[str, Any]) -> Dict[str, Any]: - """ - 普通做法。 - Post请求获取网页内容,并返回请求结果。 - - Args: - config (Dict[str, Any]): 配置信息 - - Returns: - Dict[str, Any]: 请求结果 - """ - response = requests.post(self.URL, - headers=self.generate_headers(), - json=config).text - time.sleep(3) - return json.loads(response) - - async def fetch_async(self, config: Dict[str, Any]) -> Dict[str, Any]: - """ - 异步做法。 - Post请求获取网页内容,并返回请求结果。 - - Args: - config (Dict[str, Any]): 配置信息 - - Returns: - Dict[str, Any]: 请求结果 - """ - response = requests.post(self.URL, - headers=self.generate_headers(), - json=config).text - await asyncio.sleep(3) - return json.loads(response) - - def parse(self, data: Dict[str, Any]) -> List[str]: - """ - 解析网页内容。 - - Args: - data (Dict[str, Any]): 网页内容 - - Returns: - List[str]: 标题列表 - """ - title_list = [] - records = data['data']['page']['records'] - for i in range(self.size): - title = records[i]['title'] - title = re.sub('<[^>]*>', '', title) # 去除html标签 - title_list.append(title) - # print(title) - return title_list - - def save(self, title_list: List[str]): - """ - 保存数据。 - """ - pass - - -# 时间装饰器 -def timeit(func): - """ - 计算函数运行时间。 - - Args: - func: 函数 - - Return: - 函数 - """ +import aiohttp + + +def compute_task(): + start_time = time.time() + result = sum(i * i for i in range(10**7)) + end_time = time.time() + print(f"compute_task耗时:{end_time - start_time}秒") + + +async def async_compute_task(): + start_time = time.time() + result = sum(i * i for i in range(10**7)) + end_time = time.time() + print(f"async_compute_task耗时:{end_time - start_time}秒") - def wrapper(*args, **kwargs): - start = time.time() - result = func(*args, **kwargs) - print(f'{func.__name__} cost: {time.time() - start}') - return result +def measure_performance(func): + + def wrapper(*args, **kwargs): + print(f"开始运行{func.__name__}") + + time.sleep(30) + print(f"运行前的CPU使用率:{psutil.cpu_percent()}%") + print(f"运行前的内存使用率:{psutil.virtual_memory().percent}%") + print() + + tracemalloc.start() + start_time = time.time() + func(*args, **kwargs) + end_time = time.time() + current, peak = tracemalloc.get_traced_memory() + tracemalloc.stop() + print(f"{func.__name__}耗时:{end_time - start_time}秒") + print(f"当前内存使用:{current / 10**6}MB, 峰值内存使用:{peak / 10**6}MB") + print(f"CPU使用率:{psutil.cpu_percent(interval=1)}%") + print(f"内存使用率:{psutil.virtual_memory().percent}%") + print() + + time.sleep(30) + print(f"运行结束30秒后的CPU使用率:{psutil.cpu_percent()}%") + print(f"运行结束30秒后的内存使用率:{psutil.virtual_memory().percent}%") + print(f"运行结束{func.__name__}结束") + print() return wrapper -def timeit_async(func): +def measure_performance_async(func): - @functools.wraps(func) async def wrapper(*args, **kwargs): - start = time.time() - result = await func(*args, **kwargs) - - print(f'{func.__name__} cost: {time.time() - start}') - return result + print(f"开始运行{func.__name__}") + + time.sleep(30) + print(f"运行前的CPU使用率:{psutil.cpu_percent()}%") + print(f"运行前的内存使用率:{psutil.virtual_memory().percent}%") + print() + + tracemalloc.start() + start_time = time.time() + await func(*args, **kwargs) + end_time = time.time() + current, peak = tracemalloc.get_traced_memory() + tracemalloc.stop() + print(f"{func.__name__}耗时:{end_time - start_time}秒") + print(f"当前内存使用:{current / 10**6}MB, 峰值内存使用:{peak / 10**6}MB") + print(f"CPU使用率:{psutil.cpu_percent(interval=1)}%") + print(f"内存使用率:{psutil.virtual_memory().percent}%") + print() + + time.sleep(30) + print(f"运行结束30秒后的CPU使用率:{psutil.cpu_percent()}%") + print(f"运行结束30秒后的内存使用率:{psutil.virtual_memory().percent}%") return wrapper + + +def fetch_url(url): + response = requests.get(url) + + +async def fetch_url_async(url): + async with aiohttp.ClientSession() as session: + async with session.get(url, ssl=False) as response: + await response.read() + + +urls = [ + "https://www.baidu.com", "https://www.qq.com", "https://www.taobao.com", + "https://www.tmall.com", "https://www.jd.com", "https://www.sina.com.cn", + "https://www.weibo.com", "https://www.163.com", "https://www.sohu.com", + "https://www.ifeng.com", "https://www.alipay.com", "https://www.youku.com", + "https://www.iqiyi.com", "https://www.tudou.com", "https://www.douyin.com", + "https://www.meituan.com", "https://www.xiaomi.com", + "https://www.oppo.com", "https://www.vivo.com", "https://www.dianping.com", + "https://www.autohome.com.cn", "https://www.hupu.com", + "https://www.xinhuanet.com", "https://www.chinadaily.com.cn", + "https://www.people.com.cn", "https://www.cctv.com", "https://www.cntv.cn", + "https://www.zol.com.cn", "https://www.pconline.com.cn", + "https://www.techweb.com.cn", "https://www.cs.com.cn", + "https://www.eastmoney.com", "https://www.hexun.com", + "https://www.yicai.com", "https://www.21jingji.com" +] diff --git a/B 高性能模式/概念.md b/B 高性能模式/概念.md new file mode 100644 index 0000000..afec5fc --- /dev/null +++ b/B 高性能模式/概念.md @@ -0,0 +1,54 @@ + +## 进程、线程、协程 + +运行一个软件就是开了一个进程 + +一个游戏,启动后为一个进程 +但一个游戏需要图形渲染,联网 +操作能同时运行 +所以将其各个部分设计为线程 +即一个进程有多个线程 + +从操作系统层面而言 +进程是分配资源的基本单位 +进程之间是独立的 +一个进程无法访问另一个进程的空间 +一个进程运行的失败也不会影响其他进程的运行 + +一个进程内可以包含多个线程 +线程是程序执行的基本单位,是进程中的实际运作单位 +线程是操作系统分配处理器时间的基本单元 +线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉 +协程运行在线程之上 +协程的调度完全由用户控制,协程拥有自己的寄存器上下文和栈 + + +## 并发/并行 +【顺序】 你做作业,然后看综艺, +【并发】 你写程序到一半,综艺开始,看完综艺后继续写程序。两件事情都处于启动状态 +?并发把CPU运行划分成若干个时间段,在一个时间段线程代码运行时,其他线程挂起状态 +【并行】 你写程序到一半,综艺开始,你一边做作业一边写程序。两件事情同时做 + + +## 阻塞/非阻塞 +等候消息的过程中有没有干其他事 +“阻塞”是不能 + +## 同步异步 +指的是消息通知的机制 +主动听消息则为同步(一直等,轮流取)、被动听消息则为异步 +异步过程调用发出后,可以继续执行其它操作 +通知调用者的三种方式,如下 +状态:即监听被调用者的状态(轮询),调用者没隔一段时间检查一次,效率会很低。 +通知:当被调用者执行完成后,发出通知告知调用者,无需消耗太多性能。 +回调:与通知类似,当被调用者执行完成后,会调用调用者提供的回调函数。 + +老张要做两件事:烧水,看电视 +同步阻塞:老张烧水,等水开,然后看电视 +同步非阻塞:老张烧水,去客厅看电视,时不时去看看水开了没有(检查状态) +老张买了把水开之后会提示音的水壶。 +异步阻塞:老张烧水,然后坐在旁边等着听那个烧开的提示音。(异步阻塞) +异步非阻塞:老张烧水,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶。(异步非阻塞) +实现异步可以采用多线程技术或则交给另外的进程来处理 + +如果老张委派小张去烧水,自己看电视,那就是并行 \ No newline at end of file diff --git a/B 高性能模式/进程、线程、协程.md b/B 高性能模式/进程、线程、协程.md new file mode 100644 index 0000000..36e52dc --- /dev/null +++ b/B 高性能模式/进程、线程、协程.md @@ -0,0 +1,54 @@ + +Python中多进程、多线程、协程区别和应用场景 + +多任务简单地说,就是操作系统可以同时运行多个任务。分为并行和并发两种。 +并行:指的是任务数小于等于CPU核数,即任务真的是一起执行的 +并发:指的是实际上一个时间只有一个任务在执行,那效率是怎么提升的?常常程序运行的时候,需要等待IO操作,比如访问数据库,等待网络服务器回传数据 。这个时候并发切换到其它任务就能节省时间了。 + +进程是资源分配的单位 +线程是操作系统调度的单位 +进程切换需要的资源大 +线程切换需要的资源小 +协程切换任务资源很小,协程是在一个线程中所以是并发 + +IO密集型指的是系统的CPU性能相对硬盘、内存要好很多,此时,系统运作,大部分的状况是CPU在等I/O (硬盘/内存) 的读/写操作,此时CPU Loading并不高。这可能是因为任务本身不太需要访问I/O设备。 +并行做i/o操作,多线程好些。简单的异步操作,用协程 + +进程:进程是并行。没有运行的代码叫程序,运行的程序或代码就是一个进程。进程是系统进行资源分配的最小单位,进程拥有自己的内存空间,所以进程间数据不共享。多进程适合计算密集型任务 +通常我们所说的电脑配置几核就是最大可以创建的进程,注意因为电脑会切换进程,所以看起来电脑会同时运行的进程数会超过核数 +数据无法内存共享。各个进程之间无影响,更稳定和安全,不因个别崩溃而整个系统崩溃。 + +当需要创建的子进程数量巨大时,就可以用到multiprocessing模块提供的Pool方法 +进程是通过Queue实现多进程之间的数据传递,Queue本身是一个消息队列程序。 + +线程:线程是并发。 一个进程至少有一个线程,叫主线程,多个线程共享内存(数据和全局变量共享)。 +多线程适合 IO 密集型操作 。 +缺点就是,如果多个线程同时对同一个全局变量操作,会出现资源竞争问题,从而数据结果会不正确 +当多个线程几乎同时修改某个共享数据的时候,需要进行同步控制。 +某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。 + +如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。 +可以添加超时时间等,解决死锁 + +协程:用户态的轻量级线程,调度有用户控制。 +协程是一种 允许在特定位置暂停或恢复的子程序。 +和生成器 不同的是,协程 可以控制子程序暂停之后代码的走向,而 生成器 仅能被动地将控制权交还给调用者。 +协程看上去像是线程,但是实际使用非线程的方式。 +协程 可以只利用一个线程更加轻便地实现 多任务,将任务切换的开销降至最低。 +和 回调 等其他异步技术相比, +协程 维持了正常的代码流程,在保证代码可读性的同时最大化地利用了 阻塞 IO 的空闲时间。它的高效与简洁赢得了开发者们的拥戴。 + +协程实现方式分为 +以gevent,第二种语法上以async和await关键字实现的协程,可读性更好一些。(asyncio) +具体的说,协程是函数体中包含yield或者yield from 的函数。一个协程可以通常处于四种状态之一('GEN_CREATED',,'GEN_RUNNING','GEN_SUSPENDED','GEN_CLOSED'). + +协程和线程都区别就是,线程调度看系统,协程你可以自己控制。yield 就可以实现了 + +很多时候系统瓶颈都在 IO,很多涉及到 IO 特别是网络的程序都需要用到线程 /协程。 +本来线程就复杂,还实现不完整,能用协程就协程 +大部分用于 generator 这个特定场景 +我自己的话有一个场景,每个登录用户定时拉取外部信息的需求,如果每个用户开一个线程开销太大,所以就用协程还是很方便的 + +模块 +thread模块 是比较底层的模块 +threading模块对thread做了一些包装