性能基准测试方法归纳总结

做性能优化,第一件事不是改代码,而是先把“现在跑多快”搞清楚。我见过太多人一上来就调参数、换算法,结果优化完发现还不如原来——因为没有基准线。

说白了,基准测试就是给性能“拍一张X光片”。你总得知道病在哪,才能开药方。本章我把常用的方法、工具、以及我在项目中踩过的坑,一次性揉碎了讲清楚。

测试前的三件事

先别急着跑脚本。我个人习惯,动手前先问自己三个问题:

  1. 测什么?——CPU? 内存? 还是两者都要?
  2. 什么场景?——SDK初始化? 每次调用? 还是长时间运行?
  3. 怎么算准?——单次还是多次? 均值还是中位数?

嗯,这三个问题答不上来,测出来的数据就是废纸。我曾经有个项目,团队测了三天发现SDK内存泄漏,结果只是忘了清缓存,白白浪费一周。

我的小习惯:每次测试前,都写一个测试计划文档。哪怕只有三行,也要写清楚“测什么、怎么测、算什么”。这样即使隔一个月回头再看,也知道当时在干嘛。

四大类测试方法

实际工作中,我常用下面这四类方法。它们各有各的适用场景,别搞混了。

方法 适用场景 优点 缺点
微基准测试 单函数、单接口 结果精细,容易定位 脱离真实上下文
宏基准测试 完整业务链路 贴近真实用户 结果受干扰多
压力测试 极限负载 暴露瓶颈 环境要求高
A/B对比测试 方案选型 结论直接 需要对照组

你想想看,如果只做微基准,调优一个函数快了10倍,结果整个链路卡在别的地方——那不就是白干吗?所以我建议至少跑一轮宏基准,确认真实效果。

CPU与内存的关键指标

我们做SDK优化,最关心的指标就三个方向:

  • CPU耗时——用户会感觉“卡不卡”。建议用 clock_gettime 或 perf 采用户态时间。
  • 峰值内存——Android里叫PSS,iOS里叫resident_size。我习惯在测试前后各打一次 getrusage()。
  • 内存分配次数——很多优化其实是在减少malloc调用,比单纯降峰值更实用。
重要的事情说三遍:冷启动数据不可靠,必须抛弃前3~5次的结果!(这是因为JIT预热、缓存未命中、页面调度等因素会严重影响前几次数值。)

一个最简单的基准测试框架

下面是我自己常用的一个Python脚本骨架,测一个函数的CPU耗时和内存。你可以直接套用。

import time
import tracemalloc
import statistics

def benchmark_cpu_memory(func, iterations=10, warmup=3):
    # 热身
    for _ in range(warmup):
        func()
    
    times = []
    mems = []
    
    for _ in range(iterations):
        # 记录内存起始
        tracemalloc.start()
        start = time.perf_counter()
        
        func()
        
        end = time.perf_counter()
        current, peak = tracemalloc.get_traced_memory()
        tracemalloc.stop()
        
        times.append(end - start)
        mems.append(peak)
    
    return {
        'cpu_ms': statistics.median([t*1000 for t in times]),
        'mem_kb': statistics.median([m/1024 for m in mems])
    }

# 举个栗子
def test_sdk_init():
    # 这里替换成你的SDK初始化代码
    pass

result = benchmark_cpu_memory(test_sdk_init, iterations=7)
print(f"中位数耗时: {result['cpu_ms']:.2f} ms")
print(f"中位数峰值内存: {result['mem_kb']:.2f} KB")
曾经踩过的坑:千万别用 time.time() 做高精度计时!它受系统时钟调整影响。我一直用 time.perf_counter(),它能保证单调递增,精度也够。另外 tracemalloc 在Python 3.4+才有,老版本得用 memory_profiler 替代。

统计分析:为什么中位数比平均值靠谱

你可能觉得“取平均不就完了吗”?我刚开始也这么想,直到有一次做压力测试,结果里有一个异常值直接把平均拉高了5倍。排查半天发现是GC线程引起的。

我个人习惯:最少跑10轮,去掉前3轮热身,然后取中位数。中位数不受两端异常值影响,更能代表“典型性能”。如果要看波动范围,再加上P90和P10。

表格对比一下:

统计量 用处 注意点
中位数(P50) 典型性能 适用于大多数场景
平均值 总体趋势 容易被极值污染
P90/P99 尾部延迟 用户感知的长尾问题
标准差 稳定性 值小说明结果一致

性能基准测试的完整流程

下面这张图是我自己画的标准流程,每次做测试就按这个走,基本不会漏东西。

性能基准测试流程 1. 定义指标 2. 设计场景 3. 环境准备 4. 执行测试 5. 数据采集 6. 统计分析 7. 报告输出 💡 常见指标: • CPU耗时(ms) • 峰值内存(KB) • 分配次数 ⚠️ 环境控制要点: • 关闭后台应用/服务 • 固定CPU频率(不降频) • 同等温度/电量条件 • 使用同一台设备

环境控制:最容易忽略的变量

说白了,基准测试最难的不是代码,而是让环境“不变”。手机电量从80%降到20%,CPU频率就可能降一半。我有个血的教训:拿自己电脑测了一整晚,第二天发现笔记本在插电和电池模式下性能差30%——白测了。

所以我建议:

  • 测试机和被测App,保证每次都是同一个版本
  • 如果有Android设备,用 adb shell setprop persist.sys.cpufreq.max_freq 锁频
  • iOS需要在Xcode里关掉App Thinning和Bitcode
  • 每次跑测试前重启手机,消除后台进程干扰
终极绝招:用一台未Root/越狱的真机,把手机调到飞行模式,插上充电器,放在恒温环境里跑。虽然看起来很蠢,但结果稳定得可怕。

好啦,这一章的内容就是这样。记住:没有基准的优化都是瞎蒙。你按上面的流程走一遍,至少能保证数据是可信的,后面优化起来心里才有底。