作者 |韩钦亭,Freewheel lead engineer
引 言
在软件开发中,性能优化一直是开发者面临的核心挑战之一。尽管传统的 Profiling 工具如火焰图、调用栈分析等能帮助开发者定位性能瓶颈,但如何快速理解报告并制定优化策略仍高度依赖个人经验。
为此,我们在已有 Profiling 平台的基础上,引入了 AI 智能辅助模块,帮助开发者快速分析结果并获取针对性的性能优化建议。
本文将分享该功能的设计思路、工程实现与应用成效,并探讨 AI 在性能优化场景下的未来潜力。
背景与挑战
2.1 既有 Profiling 平台回顾
在上篇文章《快速定位线上性能问题:Profiling 在微服务应用下的落地实践》中,我们已经构建了完整的性能分析体系:
该平台在高并发保障、发布回归验证等场景中发挥了重要作用,并积累了大量实践经验。
2.2 面临的主要痛点
尽管已有完善的 Profiling 基础设施,但在实际使用过程中,我们观察到以下共性问题:
2.3 新需求的提出
随着大语言模型(LLM)在软件工程、代码生成等领域的持续深入,我们也在积极探索:是否可以借助 LLM 降低性能调优的门槛?尤其针对上述性能优化过程中的共性痛点,LLM 正好具备天然优势 —— 它擅长理解复杂上下文、归纳经验模式、生成结构化建议,这与我们在优化过程中所面临的瓶颈高度契合。
因此,我们提出了一个新的功能设想:
在现有 Profiling 平台中新增 AI 辅助模块,允许开发者提交代码片段,并结合 Profiling 报告,一键发送给 AI 模型,由 AI 自动生成可执行的性能优化建议,极大缩短分析与决策路径。
架构设计与实现
为了将「基于 AI 的智能性能优化建议」这一设想落地,我们在现有的 Profiling 平台基础上进行了功能扩展,围绕两个核心目标展开设计:
最终,我们形成了一套简洁直观的交互方式和稳定可扩展的技术实现。
3.1 架构设计: 从 Profiling 到 AI 建议 的闭环
为了最大程度上贴合开发者的使用习惯,我们设计了最小可闭环的性能分析流程,涵盖从数据采集到优化建议生成的全链路能力,具体包括以下几个阶段:
整体架构图如下:
3.2 交互流程: 开发者视角的优化之旅
具体的交互流程如下
下图展示了该自研平台的上传与分析界面:
3.3 关键技术实现
3.3.1 数据适配层
传统的 profiling 数据(如 .pb.gz
的 pprof 文件)是二进制结构,AI 模型(尤其是大型语言模型)无法直接解析这些格式。为支持 AI 分析,我们需要对其转换为更适配的文本格式。
下表对常见 profiling 数据格式的可读性和 AI 适配性进行了对比:
我们最终选择.txt 格式,在可读性、解析效率之间取得平衡。可以直接通过 go 语言提供的命令完成转换
go tool pprof -text <profile_data.pb.gz> > profiling.txt
.txt 文件中包含以下关键信息:
示例片段如下所示:
flat flat% sum% cum cum%
50ms 6.41% 6.41% 50ms 6.41% internal/runtime/syscall.Syscall6
30ms 3.85% 10.26% 130ms 16.67% runtime.scanobject
20ms 2.56% 30.77% 100ms 12.82% runtime.mallocgc
转换后的文本数据将通过以下 AI 交互流程生成优化建议
3.3.2 AI 交互层
在完成 profiling 数据的标准化转化后,我们进一步构建了 AI 交互层,将性能数据与代码上下文结合,通过 Prompt 工程驱动大语言模型,从而实现
Prompt 结构设计
为了引导模型完成上述任务,我们设计了一套结构化的 Prompt 模版,确保输入上下文和输出结果的准确性、完整性与可控性。
为确保分析结果具备清晰的结构和可执行性,模型输出应满足以下四点规范:
以下为实际构造的 Prompt 示例,用于提交至 AI 模型进行分析:
def build_prompt(pprof_content, code_snippet):
messages = [
{
"role": "system",
"content": "You are a professional Go performance optimization expert skilled in analyzing pprof data to identify bottlenecks and provide actionable solutions."
},
{
"role": "user",
"content": f"""
Please help me analyze the following Go program's CPU profiling report and identify the performance bottlenecks.
Below is the CPU profiling data generated by the go tool pprof:
{pprof_content}
"""
}
]
if code_snippet:
messages.append({
"role": "user",
"content": f"""
**Here is the Go program code.**
Please do the following:
- Identify the problematic code segments based on the profiling report and code snippet.
- Clearly correlate each performance issue with the profiling result (e.g., function name, time percentage).
- Provide optimization suggestions with explanations.
- If possible, show an optimized code snippet with a brief explanation of why it improves performance.
**Code:**
```go
{code_snippet}
"""
})
return messages
3.3.3 安全性与部署策略
为了确保数据安全与平台合规,我们采用以下架构:
3.4 模型选型与评估
3.4.1 模型选择决策
在针对大规模性能诊断与代码优化场景选型时,我们重点参考了以下几个维度来评估大语言模型的实际能力与适用性:
3.4.2 模型能力对比分析
我们使用一段真实的 Go 代码及其对应的性能数据,选取 OpenAI-O1,DeepSeek R1、Claude 3.7 Sonnet 对数据样本进行评估。
性能数据摘要
flat flat% sum% cum cum%
1.50s 13.33% 13.33% 1.50s 13.33% runtime/internal/syscall.Syscall6
0.44s 3.91% 17.24% 1.54s 13.69% runtime.mallocgc
0.41s 3.64% 24.71% 1.01s 8.98% runtime.scanobject
0.18s 1.60% 29.87% 0.43s 3.82% runtime.mapassign_faststr
0.07s 0.62% 49.16% 1.38s 12.27% runtime.gcDrain
0.02s 0.18% 60.98% 1.10s 9.78% example.com/bio.(*SubItemBIO).setSubItemBasicInfo
0.01s 0.089% 63.11% 1.59s 14.13% database/sql.(*DB).queryDC
0.01s 0.089% 63.56% 1.07s 9.51% database/sql.withLock
0 0% 67.47% 1.62s 14.40% database/sql.(*DB).QueryContext
0 0% 67.47% 1.62s 14.40% database/sql.(*DB).query
0 0% 67.47% 0.69s 6.13% example.com/bio.(*SubItemBIO).getSubItemAlertInfos
0 0% 67.47% 0.65s 5.78% example.com/bio.(*SubItemBIO).getSubItemAlertInfos.func1
相关代码节选
func (o *SubItemBIO) setSubItemBasicInfo(records ...)
{
subItems := make([]*SubItem, 0)
for _, subItem := range records {
alertInfos, err := o.getSubItemAlertInfos(subItem)
if err != nil {
return nil, err
}
subItem.AlertInfos = alertInfos
subItems = append(subItems, modelToSubItem(subItem))
}
...
}
func (o *SubItemBIO) getSubItemAlertInfos(subItem) {
result := make(map[string]string)
getAlertInfos := func(subItemID, subItem.ProductID, subItem.VariantID int64, alertType string) error {
luAlertInfoQuery := NewQueryBuilder().Build()
// fetch lu alert info
luAlertInfos, err := o.LuAlertInfoApi.FindAll(ctx, luAlertInfoQuery)
luAlertInfoIds := getLuAlertInfoIDs(luAlertInfos)
entityID := getEntityID(subItem.ProductID, subItem.VariantID, alertType)
alertInfoQuery := NewQueryBuilder(subItemID, luAlertInfoIds,entityID).Build()
// fetch alerts
alerts, err := o.AlertInfoApi.FindAll(ctx, alertInfoQuery)
luAlertIDToName := getLuAlertIDToName(luAlertInfos)
for _, alert := range alerts {
alertName := luAlertIDToName[alert.LuAlertID]
result[alertName] = alert
}
}
// Prduct alert infos
if err := getAlertInfos(subItemID, subItem.ProductID, subItem.VariantID, "Product"); err != nil {
...
}
// Variant alert infos
if err := getAlertInfos(subItemID, subItem.ProductID, subItem.VariantID, "Variant"); err != nil {
...
}
}
3.4.2.1 OpenAI O1 示例输出
3.4.2.2 DeepSeek R1 示例输出
3.4.2.3 Claude 3.7 Sonnet 示例输出
3.4.3 模型对比总结 ( 以 ~20K token 输入为例 )
总体来看,三款模型在性能报告解读以及代码诊断方面均具备较高准确性,建议可执行,但通常需运行 2-3 次才能获得完整分析。其中 DeepSeek R1 的建议更精炼,适合有工程经验的开发者快速吸收。
使用场景推荐
此次模型评估基于代码分析与性能诊断场景展开,不同模型在输出丰富度、成本控制与建议质量上各有侧重。最终我们选择 DeepSeek-R1,主要出于在该场景下的性价比优势。但对于更需要上下文完整性的场景,OpenAI O1 与 Claude 3.7 依然具备优势,值得在多模型系统中灵活搭配使用。
实际案例:基于 AI 的
Service A/B 性能瓶颈定位与优化
4.1 场景描述
服务架构
Service A 的一个核心接口近期被发现存在性能瓶颈:
该接口在日常小数据量访问场景中表现正常,P95 响应时间较低,未出现显著性能问题。但在处理个别大体量请求(如一次查询数千条记录)时,用户明显感受到响应延迟,P99 延迟指标显著偏高。因此,我们决定将其作为一次典型的优化实战案例。
为了定位问题,分别对上述两个服务进行了 CPU profiling,以下是 Service A 的 profiling 结果(节选):
flat flat% sum% cum cum%
50ms 6.41% 6.41% 50ms 6.41% internal/runtime/syscall.Syscall6
30ms 3.85% 10.26% 130ms 16.67% runtime.scanobject
20ms 2.56% 30.77% 100ms 12.82% runtime.mallocgc
10ms 1.28% 53.85% 300ms 38.46% example.com/internal/proto/client.(*subItemClient).ListByItemID
10ms 1.28% 79.49% 140ms 17.95% runtime.gcDrain
0 0% 100% 110ms 14.10% runtime.schedule
对应的代码片段
// FetchOrderData 加载主订单数据并处理其下的任务结构
func FetchOrderData(ctx context.Context, req RequestInput) error {
data, _ := OrderService.GetByExternalID(ctx, req)
return loadTasksConcurrently(ctx, data.ID)
}
// loadTasksConcurrently 并发加载所有任务及其下属条目
func loadTasksConcurrently(ctx context.Context, orderID int64) error {
tasks, _ := TaskService.ListByOrderID(ctx, orderID)
for _, task := range tasks {
go func(t Task) {
loadItemsConcurrently(ctx, t.ID)
}(task)
}
return nil
}
// loadItemsConcurrently 并发加载条目并处理其子项信息
func loadItemsConcurrently(ctx context.Context, taskID int64) {
items, _ := ItemService.ListByTaskID(ctx, taskID)
for _, item := range items {
go func(it Item) {
_ = SubItemService.ListByItemID(ctx, it.ID)
}(item)
}
}
4.2 AI 视角下的性能问题剖析与建议
基于 Service A 的 profiling 数据与代码分析,DeepSeek 提出了如下优化方向:
对 Service B 的性能诊断与优化建议,详见第 3.4 节中的实测案例。
4.3 代码优化方案
下图汇总了 DeepSeek 对 Service A 和 Service B 的性能瓶颈与对应优化建议:
在实际落地过程中,我们并未机械套用 DeepSeek 的优化建议,而是结合自身业务场景和系统架构,进行了有针对性的系统性重构。我们借鉴其“批量查询与降低并发深度”的核心思想,在 Service B 中引入 Batch Query 机制,对存在层级结构的数据进行统一查询,从源头上避免了 Service A 针对每个子元素重复发起请求的结构性开销。
重构后的查询链路不仅显著减少了 goroutine 数量,还有效降低了 GC 与网络调度开销,整体响应性能得到大幅提升。虽然最终方案未完全沿用 AI 提供的代码路径,但其提出的方向性建议对优化策略的形成具有重要参考价值。这也印证了两个结论:
4.4 优化效果
接口的响应时间大幅下降
同时 CPU 的使用率大幅降低
未来展望:
从辅助分析到自主优化
在本文撰写过程中,OpenAI 发布了 O3 模型,Anthropic 发布了 Claude 4,DeepSeek 也推出了 R1 v0528 升级版本。由于时间关系,我们暂未对这些新模型进行评估。但读者可以借鉴本文思路进行后续尝试和评估,并选择最契合自身需求的模型。
在实践中我们发现,AI 模型不仅可以解析复杂的 profiling 报告,还能结合代码上下文给出具体、可落地的优化建议。基于这一能力,我们正在推动 AI 从辅助分析走向自主优化,构建更智能的性能工程体系,未来重点将聚焦于以下几个方向:
当前的分析能力主要集中在 Go 场景,下一步我们将扩展至 Java、C++ 等主流后端语言,逐步构建跨语言的性能分析和优化能力,以适配更广泛的后端系统环境。
性能瓶颈的本质往往与业务场景和数据结构紧密相关。我们将支持用户输入业务背景信息与数据库结构,结合 LLM 的上下文理解能力,构建更具语义相关性的 Prompt,从而生成更贴近实际的优化建议。
借助代码补全与重构类 API,我们正在构建自动化修复能力,使模型不仅能指出性能瓶颈,还能“一键生成”优化后的代码片段,甚至直接发起 Pull Request,实现从“识别问题”到“解决问题”的闭环。