4、执行时间优化:减少函数运行时间的实用技巧与代码模式

函数执行时间,说白了就是你的钱。在无服务器架构里,计费单位是毫秒。你跑得越久,账单就越厚。我见过不少团队,代码逻辑没问题,但就是跑得慢,一个月多花几千块冤枉钱。

为什么会这样?因为很多人把本地开发的习惯直接搬到了云上。本地你不在乎那几百毫秒,但在 Lambda 或函数计算里,每一毫秒都在燃烧预算。今天我就聊聊怎么把这些时间抠出来。

4.1 冷启动优化:别让初始化拖后腿

冷启动是个老生常谈的话题,但我还是要强调。我个人习惯在函数初始化阶段做三件事:

  • 懒加载:不是所有依赖都需要在启动时加载。比如某个函数只有 10% 的请求会用到 PDF 生成库,那就别在全局初始化它。
  • 连接复用:数据库连接、HTTP 客户端这些,一定要放在全局作用域。每次请求都新建连接,那时间成本就太高了。
  • 减少依赖体积:你想想看,一个 50MB 的部署包和 5MB 的,冷启动时间能差多少?我建议用工具分析一下依赖树,把没用的库砍掉。

核心原则:把初始化工作从请求路径上移走。能提前做的,就别等到用户请求来了再做。

4.2 代码层面的执行时间优化

这部分我踩过不少坑。先说一个最常见的:循环里的重复计算

// 不推荐
for (let i = 0; i < items.length; i++) {
  const config = getConfig(); // 每次循环都调用
  process(items[i], config);
}

// 推荐
const config = getConfig(); // 只调用一次
for (let i = 0; i < items.length; i++) {
  process(items[i], config);
}

嗯,这个例子很简单,但我在项目中遇到过更隐蔽的。有一次排查一个函数为什么跑 3 秒,最后发现是循环里反复读取了同一个 S3 对象。改成一次性读取后,时间直接降到 200 毫秒。

4.2.1 异步与并发

无服务器环境天然适合异步处理。但很多人用错了。我建议:

  • 能用 Promise.all 就别用串行 await
  • 多个独立的 I/O 操作,并发执行
  • 注意并发数控制,别把下游服务打爆了

小技巧:如果下游 API 有速率限制,可以用 p-limit 这样的库来控制并发数。我曾经因为并发太高,把第三方 API 打挂了,结果被对方拉黑了 IP。

4.2.2 数据结构与算法选择

这个听起来基础,但真有人在这上面翻车。我记得有一次帮客户优化,他们的函数要对一个 10 万条记录的数组做查找,用的是 Array.find。改成 Map 后,时间从 800ms 降到了 5ms。

说白了,就是选对数据结构。在无服务器环境里,内存和时间都是钱,别偷懒。

4.3 外部依赖调用优化

函数执行时间的大头,往往不在代码本身,而在等待外部服务响应。我总结了几条经验:

优化点 做法 预期效果
HTTP 连接复用 使用 keep-alive,复用 TCP 连接 减少 30%-50% 的请求时间
设置超时 给每个外部调用设置合理的超时时间 避免长时间等待,快速失败
缓存结果 对不频繁变化的数据做本地或 Redis 缓存 减少重复调用,时间降为 0
批量操作 合并多个小请求为一个大请求 减少网络往返次数

避坑指南:我曾经在项目中没设置 HTTP 超时,结果下游服务挂了,函数一直等到 Lambda 的 15 分钟超时上限才结束。那一次账单,嗯,不提了。所以一定要给每个外部调用设置超时,建议 3-5 秒。

4.4 使用临时存储加速

Lambda 的 /tmp 目录有 512MB 到 10GB 的临时存储空间。很多人忽略了这一点。我习惯用它来做三件事:

  • 缓存计算结果:如果同一个函数实例处理多个请求,可以把中间结果写到 /tmp
  • 存储临时文件:处理图片、视频时,避免每次都从 S3 读取
  • 保存配置数据:从数据库拉取的配置,可以缓存到本地文件

你想想看,从 /tmp 读文件比从 S3 或数据库快了多少?至少快两个数量级。而且这是免费的存储空间,不用白不用。

4.5 函数超时与重试策略

这个点容易被忽视。函数超时设置不合理,会导致大量时间浪费。我建议:

  • 设置合理的超时时间:不是越长越好。根据函数实际执行时间,留 20% 的余量就够了
  • 使用异步调用:如果任务本身就需要长时间运行,考虑用 Step Functions 或异步调用模式
  • 重试要有退避:指数退避 + 随机抖动,避免重试风暴

我的经验:把超时时间从 30 秒改成 5 秒后,虽然失败率稍微上升了一点,但整体成本下降了 40%。因为那些卡住的请求不再白白消耗资源了。快速失败,有时候比慢速成功更划算。

4.6 监控与持续优化

优化不是一次性的。我建议在函数里加上性能埋点,把执行时间、冷启动时间、外部调用时间都记录下来。然后定期分析:

  • 哪些函数的 P99 延迟在上升?
  • 哪些外部调用的耗时异常?
  • 冷启动率有没有变化?

有了数据,你才能做有针对性的优化。我习惯用 X-Ray 或者 OpenTelemetry 来做分布式追踪。嗯,这里要注意,埋点本身也会消耗一点时间,但相比它能带来的优化收益,这点开销完全可以忽略。

最后说一句:执行时间优化是个持续的过程。每次代码变更,都可能引入新的性能问题。养成看性能指标的习惯,你的账单会感谢你的。