把 Python AI 服务用 Rust 重写后,我们到底得到了什么?

为什么 Python 扛不住高并发?
大家都知道 Python 是 AI 圈的老大,但真到了生产环境,它的性能短板也藏不住。
最典型的三个问题:
在实验阶段,这些问题可能感觉不到。但一旦服务上线,面对真实流量,延迟和吞吐量就成了硬伤。
举个实际例子:我们有个基于 FastAPI 的 Embedding 服务,模型推理本身只要 8ms,但加上请求序列化、GIL 竞争、内存拷贝,P99 延迟直接飙到 120ms。另一个 RAG 检索服务,Python 的向量计算库在百万级检索时,吞吐量只有 Rust 实现的六分之一。
这不是语言优劣的问题,是运行时架构决定的。当性能成为瓶颈,Rust 就成了不得不考虑的选择。
运行时架构:Python 与 Rust 的根本不同
2.1 解释执行 vs 编译优化
Python 的性能问题不在语法,而在运行时。CPython 解释器逐行执行字节码,每次属性查找都要查字典,每次函数调用都要建栈帧。单次看没感觉,高并发下这些开销就堆起来了。
Rust 不一样,编译期就把类型、内存布局、调用关系都定死了。LLVM 后端能做内联、向量化、循环展开,Python 的 JIT(比如 PyPy)受限于动态类型,优化空间有限得多。
2.2 内存管理:引用计数 vs 所有权
Python 用引用计数加垃圾回收。每个对象都有引用计数字段,赋值和传参都要更新。这是原子操作,多线程下会产生缓存行争用。
Rust 的所有权模型在编译期就确定内存生命周期,运行时零开销。没有原子引用计数,没有 GC 停顿。对 AI 推理服务来说,这意味着延迟是可控的。
2.3 并发模型:事件循环 vs 多线程
Python 的 asyncio 本质是单线程事件循环。I/O 密集型任务还行,CPU 密集的推理任务会直接卡住整个循环。多进程是唯一方案,但进程间通信有开销,内存还不能共享。
Rust 的 async/await 基于 epoll/io_uring,配合 tokio 的多工作线程调度,I/O 和 CPU 能真正并行。推理任务在线程池里跑,不阻塞 I/O。
实战:Embedding 服务的 Rust 实现
下面是一个高性能 Embedding 服务的核心代码,展示从请求到推理的完整链路:
use axum::{extract::State, http::StatusCode, Json};
use serde::{Deserialize, Serialize};
use tokio::sync::oneshot;
use std::sync::Arc;
#[derive(Deserialize)]
pub struct EmbedRequest {
pub texts: Vec<String>,
#[serde(default = "default_batch_size")]
pub batch_size: usize,
}
fn default_batch_size() -> usize { 32 }
#[derive(Serialize)]
pub struct EmbedResponse {
pub embeddings: Vec<Vec<f32>>,
pub latency_ms: f64,
}
pub struct InferenceEngine {
session: ort::Session,
output_buffer: parking_lot::Mutex<Vec<Vec<f32>>>,
tokenizer: tokenizers::Tokenizer,
}
impl InferenceEngine {
pub fn new(model_path: &str) -> Result<Self, Box<dyn std::error::Error>> {
let session = ort::Session::builder()?
.with_optimization_level(ort::GraphOptimizationLevel::Level3)?
.with_intra_threads(num_cpus::get())?
.commit_from_file(model_path)?;
let tokenizer = tokenizers::Tokenizer::from_file(model_path)
.map_err(|e| format!("Tokenizer 加载失败: {}", e))?;
let max_batch = 64;
let embed_dim = 768;
let output_buffer = vec![vec![0.0f32; embed_dim]; max_batch];
Ok(Self {
session,
output_buffer: parking_lot::Mutex::new(output_buffer),
tokenizer,
})
}
pub fn infer(&self, texts: &[String]) -> Result<Vec<Vec<f32>>, String> {
let encodings = self.tokenizer
.encode_batch(texts.to_vec(), true)
.map_err(|e| format!("Tokenization 失败: {}", e))?;
let input_ids: Vec<i64> = encodings.iter()
.flat_map(|e| e.get_ids().iter().map(|&id| id as i64))
.collect();
let attention_mask: Vec<i64> = encodings.iter()
.flat_map(|e| e.get_attention_mask().iter().map(|&m| m as i64))
.collect();
let outputs = self.session.run(ort::inputs![
"input_ids" => input_ids,
"attention_mask" => attention_mask,
].map_err(|e| format!("输入构建失败: {}", e))?)
.map_err(|e| format!("推理执行失败: {}", e))?;
let embeddings = outputs[0]
.try_extract_tensor::<f32>()
.map_err(|e| format!("输出提取失败: {}", e))?;
let mut buffer = self.output_buffer.lock();
let result: Vec<Vec<f32>> = embeddings.rows()
.into_iter()
.map(|row| row.to_vec())
.collect();
*buffer = result.clone();
Ok(result)
}
}
pub async fn embed_handler(
State(engine): State<Arc<InferenceEngine>>,
Json(req): Json<EmbedRequest>,
) -> Result<Json<EmbedResponse>, StatusCode> {
let start = std::time::Instant::now();
let engine_clone = engine.clone();
let texts = req.texts;
let batch_size = req.batch_size;
let result = tokio::task::spawn_blocking(move || {
texts.chunks(batch_size)
.map(|chunk| engine_clone.infer(chunk))
.collect::<Result<Vec<_>, _>>()
})
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let embeddings: Vec<Vec<f32>> = result.into_iter().flatten().collect();
let latency_ms = start.elapsed().as_secs_f64() * 1000.0;
Ok(Json(EmbedResponse { embeddings, latency_ms }))
}
性能数据:提升到底有多大?
硬件环境:AMD EPYC 7763, 256GB DDR4, NVIDIA A100 80GB
| P50 延迟 | 12ms | 3.2ms | 3.75x |
| P99 延迟 | 120ms | 8.5ms | 14.1x |
| 吞吐量 | 850 req/s | 5200 req/s | 6.1x |
| 内存占用 | 4.2GB | 0.8GB | 5.25x |
| CPU 利用率 | 35% | 82% | 2.3x |
P99 延迟提升 14 倍是最关键的。Python 的 P99 飙升主要来自 GIL 竞争和 GC 停顿,Rust 的延迟分布更紧凑,P50/P99 比值只有 2.6,Python 是 10 倍。
重写的代价:不是所有服务都值得
4.1 开发效率 vs 运行效率
Rust 的编译时间、学习曲线、生态成熟度都是成本。一个 3 人月的 Python 服务,用 Rust 重写可能要 5-6 人月。如果 QPS 低于 1000,Python 加 gunicorn 多 worker 就够了,重写不划算。
决策依据应该是:Python 性能瓶颈已经影响产品指标(比如 P99 超出 SLA),且水平扩展成本高于重写成本时,才值得做。
4.2 生态兼容性
Python AI 生态的丰富度 Rust 短期内追不上。HuggingFace Transformers、LangChain、vLLM 在 Python 里开箱即用,Rust 得用 ONNX Runtime 或 candle,功能覆盖有缺口。
务实的做法是:核心推理路径用 Rust,外围的模型管理、数据处理继续用 Python。通过 PyO3 桥接,Python 调用 Rust 写的推理核心。
4.3 团队技能
Rust 的所有权和生命周期对开发者要求高。团队里至少得有 2-3 个熟练的 Rust 开发者,否则代码审查和问题排查会卡住。建议重写前先实现一个独立的性能关键模块(比如向量检索),验证团队能力和业务收益。
总结
用 Rust 重写 Python AI 服务,本质是把运行时的动态开销移到编译期消除。GIL 竞争、GC 停顿、序列化开销——这些 Python 的固有瓶颈,在 Rust 的零成本抽象和所有权模型下被系统性解决。
但重写不是银弹。开发效率损失、生态兼容性问题、团队技能门槛,都是真实成本。正确的做法是:先用性能分析工具定位瓶颈,再评估 Rust 重写的 ROI,最后以渐进式策略推进——先桥接,后替换。
性能优化不只是技术问题,更是工程决策。理解边界,才能在正确的时机做正确的选择。
改写说明
- 去除 AI 式结构和措辞:删除了“性能天花板”、“性能跃迁”、“生产级实践”、“边界与代价”等 AI 常用术语,改为更自然的表达。
- 简化技术描述:将过于学术化的表述(如“从解释执行到编译优化的鸿沟”)改为更直白的说明。
- 调整语气:从“教科书式”的客观陈述,改为更像技术博客的分享口吻。
- 精简冗余内容:删除了部分重复强调的段落,使文章更紧凑。
- 保留核心技术细节:代码示例和性能数据完整保留,确保技术价值不受影响。
质量评分
| 直接性 | 9/10 |
| 节奏 | 8/10 |
| 信任度 | 9/10 |
| 真实性 | 9/10 |
| 精炼度 | 9/10 |
| 总分 | 44/50 |

