codecov
Swift address sanitizer Swift thread sanitizer Swift Linux build Swift macOS build

基准测试

Benchmark 包使您能够轻松地为各种指标创建复杂的 Swift 性能基准测试。

本 README 提供了 Benchmark 包的快速概览。有关更详细的信息,请参阅文档

概述

性能是许多应用程序和框架的关键特性。Benchmark 帮助您轻松地测量和跟踪许多影响性能的不同指标,例如 CPU 使用率、ARC 流量、内存/malloc 使用率以及操作系统资源(如线程和系统调用)的使用,以及完全自定义的指标计数器。

Benchmark 在 macOS 和 Linux 上均可运行,并支持多种用于性能测量的关键工作流程

Benchmark 提供了一种快速测量和验证性能指标的方法,而其他更专业的工具(如 Instruments、DTrace、Heaptrack、Leaks、Sample 等)可用于归因性能问题或查找任何已发现偏差的根本原因。

Benchmark 既适用于关注执行时间的较小临时基准测试,也适用于关注多个附加指标(如内存分配、系统调用、线程使用、上下文切换、ARC 流量等)的更广泛的基准测试。使用 Histogram,它尤其适用于捕获大量样本的延迟统计信息。

文档

有关如何在 Swift 包中使用 Benchmark 的文档可以在在线查看,也可以在 Xcode 中使用“Build Documentation”查看。

此外,如果您从命令行运行 swift package benchmark help,命令插件会提供帮助信息。

添加依赖项并开始使用

只需几个步骤即可开始基准测试

  1. 添加对 Benchmark 项目的依赖
  2. 使用 swift package benchmark init 添加基准测试可执行目标
  3. 添加您要进行基准测试的代码片段或代码
  4. 运行 swift package benchmark

步骤详解

步骤 1:将包依赖项添加到 Package.swift

要添加对 Benchmark 的依赖,请将依赖项添加到您的包中

.package(url: "https://github.com/ordo-one/package-benchmark", .upToNextMajor(from: "1.4.0")),

步骤 2:使用 benchmark init 添加基准测试可执行目标

向您的项目添加新的基准测试可执行目标的最简单方法是使用

swift package --allow-writing-to-package-directory benchmark init MyNewBenchmarkTarget

这将为您执行以下步骤

init 命令验证您指定的名称是否未被任何现有目标使用,并且不会覆盖 Benchmarks/ 位置中具有该名称的任何现有文件。

创建新目标后,您可以直接运行它,例如

swift package benchmark --target MyNewBenchmarkTarget

步骤 2(可选方法):手动添加基准测试可执行目标

或者,如果您不希望插件修改您的项目目录,您可以手动执行相同的步骤:在 Package.swift 中为您要测量的每个基准测试套件创建一个可执行目标。所有基准测试的源文件必须位于 swift 包根目录中名为 Benchmarks 的目录中。基准测试插件使用此目录以及可执行目标信息来自动发现和运行您的基准测试。对于每个可执行目标,包括对 package-benchmark 中的 Benchmark(支持框架)和 BenchmarkPlugin(样板文件生成器)的依赖。以下示例显示了一个名为 My-Benchmark 的基准测试套件,它具有对 Benchmark 的必需依赖,以及位于 Benchmarks/My-Benchmark 目录中的基准测试源文件

.executableTarget(
      name: "My-Benchmark",
      dependencies: [
          .product(name: "Benchmark", package: "package-benchmark"),
      ],
      path: "Benchmarks/My-Benchmark",
      plugins: [
          .plugin(name: "BenchmarkPlugin", package: "package-benchmark"),
      ]
),

步骤 3:编写基准测试

文档可用,以及一个示例项目在实践中使用此包的各个方面。

示例基准测试代码

import Benchmark

let benchmarks : @Sendable () -> Void = {
    Benchmark("Minimal benchmark") { benchmark in
      // measure something here
    }

    func defaultCounter() -> Int {
        10
    }
    
    func dummyCounter(_ count: Int) {
        for index in 0 ..< count {
            blackHole(index)
        }
    }
    
    Benchmark("All metrics, full concurrency, async",
              configuration: .init(metrics: BenchmarkMetric.all,
                                   maxDuration: .seconds(10))) { benchmark in
        let _ = await withTaskGroup(of: Void.self, returning: Void.self, body: { taskGroup in
            for _ in 0..<80  {
                taskGroup.addTask {
                    dummyCounter(defaultCounter()*1000)
                }
            }
            for await _ in taskGroup {
            }
        })
    }     
}

步骤 4:运行基准测试

要执行所有定义的基准测试,只需运行

swift package benchmark

有关所有选项的更多详细信息,请参阅文档

示例输出基准测试运行

image

示例输出按指标分组的基准测试

image

示例输出增量比较

image

示例输出阈值偏差检查

image

YouPlot 的示例用法

安装 YouPlot

swift package benchmark run --filter InternalUTCClock-now --metric wallClock --format histogramPercentiles --path stdout --no-progress | uplot lineplot -H

image

JMH 可视化

使用 jmh.morethan.io

image

image

Swift 6 支持

该软件包支持 Swift 6.0 基准测试目标以及 Swift 5.10 目标(对于 Swift 5.9 支持,需要精确使用 1.28.0 版本)。

对于 Swift 6 目标,请为基准测试闭包使用以下签名

let benchmarks : @Sendable () -> Void = {

输出

Benchmark 的默认文本输出围绕五数概括百分位数,加上最后一个十分位数 (p90) 和最后一个百分位数 (p99) 展开 - 因此它是七数概括的变体,重点关注结果的“坏”端(因为这些是我们通常关心解决的问题)。我们发现,与平均值或标准差相比,关注百分位数对于更广泛的基准测试测量更有用,并且可以更深入地了解结果。百分位数允许以一致的方式表达吞吐量和延迟测量的基准测试结果(它们通常不具有标准化分布,几乎总是多模态的)。延迟测量的这种多模态性质导致平均值和标准差的常用统计度量可能具有误导性。

有关百分位数的更多详细信息,请参阅文档

API 和文件格式稳定性

API 将在 1.0.0 版本后被视为稳定,并在未来的版本中遵循语义版本控制。

外部定义的导出文件格式(例如 JMH 或 HDR Histogram 格式)如果发生更改,将遵循上游定义,但多年来一直相当稳定。

Histogram 可编码表示形式不稳定,如果 Histogram 实现发生更改,则可能会更改。

基准测试内部基线表示形式(存储在 .benchmarkBaselines 中)不稳定,不被视为公共 API,并且可能会随着时间推移而损坏。

对于那些想要长期保存基准测试数据的人,建议以例如 HDR Histogram 表示形式(百分位数、平均值、标准差等)导出数据,或者只是对 histogramSamples 格式(它是原始数据)进行后处理以获得您想要的表示形式。

欢迎提交针对其他标准化格式的 PR,因为导出格式是保存此类数据的预期稳定接口。