Python 性能优化全攻略:提升 3 倍执行速度的实用技巧

Python 性能优化全攻略:提升 3 倍执行速度的实用技巧

这篇文章不谈玄学,不堆概念。从实际工程出发,带你把 Python 程序从“能跑”优化到“跑得快”,并且知道为什么快。


一、先别优化:定位瓶颈才有意义

很多人一上来就:

  • 改成多线程
  • 换成异步
  • 上缓存
  • 用 C 扩展

结果性能没提升,复杂度却上去了。

1️⃣ 性能优化第一原则:量化

没有数据的优化都是猜测。

推荐工具:

  • time:简单测量耗时
  • cProfile:函数级分析
  • line_profiler:行级分析
  • memory_profiler:内存分析

示例:cProfile 分析

import cProfile def slow_function(): total = 0 for i in range(10_000_000): total += i return total cProfile.run("slow_function()") 

输出结果中重点关注:

  • 调用次数(ncalls)
  • 单次耗时(percall)
  • 总耗时(tottime)
经验:80% 的时间通常集中在 20% 的代码上。

二、算法优化:性能提升的天花板

如果你用的是 O(n²) 算法,换成 O(n log n),
性能提升远大于任何“语法优化”。

示例:列表查找优化

错误写法:

for item in big_list: if item in another_list: ... 

时间复杂度:

O(n²)

优化写法:

another_set = set(another_list) for item in big_list: if item in another_set: ... 

时间复杂度:

O(n)

这种改动往往是“数量级”的提升。


算法复杂度示意图

O(1) 常数级 O(log n) 对数级 O(n) 线性 O(n log n) O(n²) O(2^n) 

如果程序慢,先问:

是代码慢,还是算法慢?

三、数据结构选型决定性能上限

Python 内置结构性能差异非常大:

结构查找插入适用场景
listO(n)O(1) 末尾顺序数据
dictO(1)O(1)快速查找
setO(1)O(1)去重判断
dequeO(1) 头尾O(1)队列

例如:

# 不要频繁在 list 头部插入 my_list.insert(0, x) # 使用 deque from collections import deque dq = deque() dq.appendleft(x) 

在高频操作中,这种结构替换能带来 2~5 倍差异。


四、循环优化与内置函数

Python 的 for 循环本身不快。

1️⃣ 使用内置函数代替循环

慢:

total = 0 for i in numbers: total += i 

快:

total = sum(numbers) 

原因:

sum() 在 C 层实现,比 Python 循环快。


2️⃣ 列表推导优于 append

慢:

result = [] for x in data: result.append(x * 2) 

快:

result = [x * 2 for x in data] 

通常快 20%~40%。


五、避免不必要的对象创建

Python 对象创建成本不低。

错误示例:

for i in range(1_000_000): s = str(i) 

优化方式:

是否必须转字符串?是否可以批量处理?对象越多,GC 压力越大。


六、字符串拼接优化

慢:

s = "" for i in range(10000): s += str(i) 

快:

s = "".join(str(i) for i in range(10000)) 

原因:

字符串不可变,+= 会不断创建新对象。


七、并发优化:什么时候有用?

这里要说清楚一个核心问题:

Python 有 GIL。

CPU 密集型任务:

  • 多线程 ≈ 无效

IO 密集型任务:

  • 多线程 / asyncio 有效

CPU 密集型 → 多进程

from multiprocessing import Pool def task(x): return x * x with Pool(4) as p: result = p.map(task, range(10_000)) 

IO 密集型 → asyncio

import asyncio async def task(): await asyncio.sleep(1) asyncio.run(task()) 

优化方向一定要匹配任务类型。


八、使用 C 加速工具

如果纯 Python 不够:

  • NumPy(向量化)
  • Cython
  • Numba
  • PyPy

示例:NumPy 向量化

慢:

result = [] for i in range(len(a)): result.append(a[i] + b[i]) 

快:

import numpy as np result = np.array(a) + np.array(b) 

向量化常常是 10 倍级提升。


九、缓存机制

重复计算是性能杀手。

from functools import lru_cache @lru_cache(maxsize=128) def fib(n): if n < 2: return n return fib(n-1) + fib(n-2) 

递归性能可提升数百倍。


十、内存优化

性能不只是 CPU。大对象及时释放;使用生成器代替大列表;

慢:

data = [i for i in range(10_000_000)] 

快:

data = (i for i in range(10_000_000)) 

生成器节省大量内存。


十一、优化优先级建议

按收益排序:

1️⃣ 算法优化
2️⃣ 数据结构选择
3️⃣ 内置函数替换
4️⃣ 向量化
5️⃣ 缓存
6️⃣ 并发
7️⃣ C 扩展

不要反过来。


十二、真实优化案例

某数据处理程序:

  • 原始版本:18 秒
  • 优化后:5.8 秒

优化步骤:

  1. 用 cProfile 找到 70% 时间在 list 查找
  2. 改为 set
  3. 替换字符串拼接为 join
  4. 使用 NumPy 向量化计算

没有改架构,没有加服务器。


十二、真实优化案例

真实优化案例:一次“看似普通”的数据清洗任务,是怎么从 47 秒优化到 6.3 秒的

这个案例发生在一个很普通的项目里,没有什么高并发,没有分布式,没有大模型。
就是一个典型的“数据清洗 + 规则匹配”的后台脚本。

但问题是——每天要跑几百次。

最开始没人觉得慢,直到它开始影响后续流程。


一、背景

业务场景很简单:

  • 读取一份约 30 万行的 CSV 文件
  • 对每一行做字段清洗
  • 根据规则表进行匹配
  • 生成一份结果文件

单次运行时间:47 秒

听起来不算灾难,但:

  • 每天执行 300+ 次
  • 串行流程,后面任务要等它

于是我接手做优化。


二、第一步:不要猜,先量化

我第一反应不是改代码,而是:

import cProfile cProfile.run("process_data()") 

结果非常清晰:

  • 71% 时间耗在一个函数 match_rules
  • 19% 时间耗在字符串拼接
  • 其他可以忽略

这已经很好了——
有明确突破口。


三、问题一:规则匹配的灾难性写法

原始代码大概是这样:

def match_rules(row, rules): for rule in rules: if rule["keyword"] in row["content"]: return rule["category"] return "unknown" 

问题在哪里?

  • rules 有 5000 条
  • 每一行都要循环 5000 次
  • 总行数 300,000

复杂度接近:

300,000 × 5,000 = 15 亿次字符串匹配 

这不是“慢”,是结构性错误。


四、第一次优化:构建索引结构

我做的第一件事:

把规则按关键词长度分组。

但效果不明显。

后来我意识到一个核心问题:

我不需要遍历规则,我需要“反向查找”。

于是我改成了:

# 预处理阶段 keyword_map = {} for rule in rules: keyword_map[rule["keyword"]] = rule["category"] 

但问题仍然存在:

in 匹配仍然需要遍历关键词。


五、真正有效的优化:使用 Aho-Corasick 算法

这个场景本质是“多关键词匹配”。

我引入了 pyahocorasick

import ahocorasick automaton = ahocorasick.Automaton() for rule in rules: automaton.add_word(rule["keyword"], rule["category"]) automaton.make_automaton() 

匹配变成:

def match_rules(content): for _, category in automaton.iter(content): return category return "unknown" 

复杂度从:

O(规则数 × 文本长度)

变成:

O(文本长度)

运行时间直接从 47 秒降到 14 秒

这一步是核心突破。


六、第二个问题:字符串拼接

原代码:

result = "" for row in data: result += process(row) + "\n" 

典型错误。

我改成:

lines = [] for row in data: lines.append(process(row)) result = "\n".join(lines) 

运行时间从 14 秒 → 9.8 秒


七、第三个问题:CSV 读取方式

原来使用的是:

import csv reader = csv.DictReader(open("data.csv")) 

我测试了 pandas

import pandas as pd df = pd.read_csv("data.csv") 

在这个数据规模下:

  • pandas 更快
  • 且字段操作更方便

最终整体时间 → 7.4 秒


八、最后一步:减少函数调用开销

在 profile 里我发现:

process(row) 被调用 30 万次。

内部大量:

  • .strip()
  • .lower()
  • 重复字段转换

我把清洗逻辑内联,并提前做一次性转换:

原来:

def clean(text): return text.strip().lower() 

后来:

content = row["content"].strip().lower() 

减少函数栈开销。

最终时间:

6.3 秒


九、优化过程总结

阶段时间
原始版本47s
Aho-Corasick14s
字符串 join9.8s
pandas 读取7.4s
减少函数调用6.3s

总提升:

7.5 倍


十、关键体会

这次优化让我重新确认几件事:

1️⃣ 性能瓶颈通常是结构问题

不是“Python 慢”,
而是你写成了 O(n²)。


2️⃣ 算法 > 语法优化

字符串拼接带来 4 秒提升
算法改造带来 33 秒提升

优先级非常明显。


3️⃣ profile 是必须的

如果没有 profile,我可能会:

  • 盲目加多线程
  • 改成 asyncio
  • 升级服务器

这些都是错误方向。


4️⃣ 小优化叠加才有大效果

单个改动不一定震撼。
但连续五个小改动,效果非常明显。


十一、如果再优化还能做什么?

我后来测试过:

  • 多进程拆分文件
  • Cython 重写核心匹配

还能再降 1~2 秒。

但业务已经满足要求,我就停了。

优化要有边界。

性能优化本质上是一种:

对程序运行路径的尊重。

Read more

LeetCode 25. K 个一组翻转链表 | 硬核拆解「reverse N」核心思路

LeetCode 25. K 个一组翻转链表 | 硬核拆解「reverse N」核心思路

✨ LeetCode 25. K 个一组翻转链表 | 硬核拆解「reverse N」核心思路 * 视频地址 * 🔍 一、问题核心含义:K 个一组翻转链表 * 1. 官方定义(严格版) * 2. 直观示例 * 3. 可视化流程图(Mermaid) * 📊 二、算法原理:基于「reverse N」的分层实现 * 1. 基础能力:reverse N 节点计数判断 * 2. 核心能力:__reverse N 前 N 个节点翻转 * 💡 三、整体实现框架:虚拟头节点 + 双指针循环 * 1. 核心技巧:虚拟头节点 * 2. 关键位置指针定义 * 3. 循环翻转逻辑

By Ne0inhk
C++链表详解:从零开始掌握链表结构,轻松应对算法面试

C++链表详解:从零开始掌握链表结构,轻松应对算法面试

作者:A小庞 发布平台:ZEEKLOG 阅读时长:15分钟 关键词:C++链表、数据结构、算法面试、高频考点、链表反转、内存管理 🌟 为什么链表是程序员的必修课? 在计算机科学中,链表(Linked List)是一种基础但至关重要的数据结构。它通过动态内存分配实现数据的非连续存储,解决了数组的固定长度和插入/删除低效的问题。无论是算法面试还是实际开发,链表都是高频考点和核心技能之一。 🧱 一、链表的核心概念 1.1 链表的物理结构 链表由一系列节点(Node)组成,每个节点包含两部分: * 数据域:存储实际数据(如 int value)。 * 指针域:存储下一个节点的地址(如 Node* next)。 示意图: 节点A → 节点B → 节点C → NULL 每个节点在内存中独立分布,

By Ne0inhk
《C++ 动态规划》第001-002题:第N个泰波拉契数,三步问题

《C++ 动态规划》第001-002题:第N个泰波拉契数,三步问题

🔥个人主页:Cx330🌸 ❄️个人专栏:《C语言》《LeetCode刷题集》《数据结构-初阶》《C++知识分享》 《优选算法指南-必刷经典100题》《Linux操作系统》:从入门到入魔 《Git深度解析》:版本管理实战全解 🌟心向往之行必能至 🎥Cx330🌸的简介: 目录 前言: 01.第N个泰波拉契数 算法原理(动态规划): 思路: 解法代码(C++): 博主手记(字体还请见谅哈): 02.三步问题 算法原理(动态规划): 思路: 解法代码(C++): 博主手记(字体还请见谅哈): 结尾: 前言: 聚焦算法题实战,系统讲解三大核心板块:“精准定位最优解”——优选算法,“简化逻辑表达,系统性探索与剪枝优化”——递归与回溯,“以局部最优换全局高效”——贪心算法,讲解思路与代码实现,帮助大家快速提升代码能力 01.

By Ne0inhk