
上线前夜我盯着那段爬虫代码,三秒钟抓一个页面,三千个页面要两个半小时。同事催我下班,我说再等等。那一晚我把循环里的重复请求改成了会话复用,把字符串拼接换成了join,把没用的日志关掉。第二天早上跑完,结果两分多钟。还不够。
真正的转机是我发现目标服务器支持gzip压缩。我用requests的stream参数,配合iter_content,一次拿到的响应体比之前小了一半。加上asyncio和aiohttp,把同步请求改成异步并发,单线程内同时挂着十个连接。改动不大,效果直接翻了十几倍。同样的数据量,最终稳定在0.1秒左右。
我说几个具体的点。第一个,能用生成器就别用列表。比如读取大文件,普通readlines会把整个文件装进内存,我用yield一行行吐出来,内存占用从几百兆掉到几兆。第二个,字典和集合的查找是O(1),列表是O(n)。当时我有一段代码在循环里反复用in去判断某个值是否在列表里,三千次循环看不出毛病,放到三十万次就卡死了。改成集合以后,速度肉眼可见地提上去了。
还有个坑。正则表达式在循环里反复编译,每次都要重新解析模式,我用re.compile预编译一次,后面全部复用。这个改动表面上没人注意,实际跑起来能省掉百分之二十的时间。函数调用的开销也别小看,python里每调一次函数都有栈操作,循环里能内联的逻辑就别单独拆函数。我那个同事喜欢写一堆短小的辅助函数,好看是好看了,性能上吃了大亏。
再说个不太容易想到的。局部变量比全局变量快,因为python查找局部变量不需要走全局命名空间。我把频繁用到的全局配置,在函数开头用局部变量引用一下,速度就上来了。还有一个,不要在循环里频繁打印日志,print本身有IO开销,更不要用f-string拼日志字符串,那个字符串即使不输出也会被创建。改成logging模块的延迟求值,加个判断条件,日志级别低就不拼字符串。
多线程在这个场景里没用,因为GIL锁把CPU密集型的并行给卡死了。我换成了多进程,用concurrent.futures里的ProcessPoolExecutor,把任务拆成块丢给四个核,每个核自己跑一段。数据量大的时候效果明显。但要注意进程间通信的开销,任务太小反而得不偿失。
最后说一个我一开始忽略的点。内存分配和回收会拖慢速度。我循环里创建了大量临时对象,python的垃圾回收机制每隔一段时间就扫描一次,扫描停顿会卡住程序。我手动调用了gc.disable(),然后确认代码里没有循环引用,等程序退出前再手动回收。这一下省了将近零点几秒,在频繁创建对象的场景里收益很大。
后来我把这套优化方法整理成脚本模板,新写的代码从一开始就避开这些坑。现在的项目基本维持在毫秒级响应。那天晚上一起加班的同事问我图什么,我说图明天不用加班。
性能优化的核心其实就三条,减少重复计算,降低IO等待,利用好CPU。写程序的时候脑子多转一下,别偷懒。
以上就是“Python性能优化实战,让我的程序从3秒变0.1秒!”的详细内容,想要了解更多Python教程欢迎持续关注编程学习网。
扫码二维码 获取免费视频学习资料

- 本文固定链接: http://www.phpxs.com/post/14492/
- 转载请注明:转载必须在正文中标注并保留原文链接
- 扫码: 扫上方二维码获取免费视频资料