介绍 Benchmark Package:使用性能检查补充单元测试

在软件开发领域,古老的格言“先使其工作,再使其正确,最后使其快速”是创建健壮、高效应用程序的指导原则。这个旅程始于确保我们的代码按预期运行,单元测试和集成测试已证明在这项任务中不可或缺。然而,确保功能性只是等式的一部分。衡量应用程序卓越性的真正标准延伸到其性能——在各种条件下运行的速度和效率。这其中就蕴含着关键但经常被忽视的第三步:使其快速

在专业交易软件领域,与持续集成 (CI) 集成的全面基准测试框架的作用,与单元测试和集成测试的重要性不相上下。正如单元测试和集成测试对于确保软件的功能正确性至关重要一样,CI 管道中的基准测试对于持续验证非功能性方面(例如高吞吐量、低延迟、可预测的性能和一致的资源使用)至关重要。这对于在快节奏的金融环境中保持竞争优势至关重要,在这样的环境中,极端的市场数据速率和性能要求意味着,即使是微秒级的响应时间的微小变化,也可能对交易结果产生重大影响。

无论应用程序领域如何,性能都是整体产品的重要组成部分,没有最终用户愿意等待计算机或其他电子设备,对用户操作的即时响应确实有助于提供令人愉悦的最终用户体验。

在检查了 Swift 生态系统内的现有基础设施后,我们得出结论,没有现有的解决方案能够满足我们对多平台和丰富指标支持、CI 集成以及开发者友好的需求。因此,我们决定开发一个 Benchmark package 并开源它,相信它可以帮助提升 Swift 社区的性能,并使我们所有人受益。

基准测试的作用

您是否曾经遇到过性能问题,该问题溜到了最终用户手中,并导致了错误报告?当您更改 Swift 软件包时,您是否系统地测量和验证性能指标?

Swift 的目标是达到媲美 C 语言的性能,强调可预测和一致的执行。实现这一目标涉及优化受限资源(如 CPU、内存和网络带宽)的使用,这些资源会显着影响服务器端、桌面和移动环境中的应用程序工作负载。关键性能指标包括 CPU 使用率、内存分配和管理、网络 I/O 和系统调用等。这些指标对于基础软件至关重要,在基础软件中,控制资源使用和最小化占用空间与保持运行时性能同样重要。Benchmark package 随时支持这些指标,以及 Linux 和 macOS 的特定于操作系统的指标,为 Swift 开发者提供了一个全面的工具包,以监控和增强其应用程序的效率。

构建一组基准测试并持续运行它们,可以指示何时出现性能未达到预期的情况,就像单元测试标记某些功能预期被破坏一样。然后,使用补充工具(例如 Instruments、DTrace、Heaptrack、Leaks、Sample 等)进行根本原因分析,以分析和修复根本问题。

这类似于单元测试,其中失败的测试表明存在问题,而其他更专业的工具用于修复问题(例如,调试器、TSAN/ASAN、添加断言、调试打印输出等)。

基准测试基础设施

开源的 Benchmark package 帮助您自动化性能测试,并使个人开发者在推送更改之前轻松在本地运行快速性能验证。

Benchmark package 以 SwiftPM 命令插件的形式实现,并添加了一个专用命令来与基准测试进行交互

swift package benchmark

入门介绍信息可在 package GitHub 页面 以及 Swift Package Index DocC 文档 中找到。

一个测量 Date 性能的极简基准测试可能如下所示

import Benchmark
import Foundation

let benchmarks = {
    Benchmark("Foundation-Date") { benchmark in
        for _ in benchmark.scaledIterations {
            blackHole(Foundation.Date())
        }
    } 
}

它既适用于主要关注 CPU 使用率的微基准测试,也适用于更复杂的长时间运行的基准测试,并且由于使用了 HDR Histogram package,因此支持在很长一段时间内测量各种样本。

Benchmark 提供了对 广泛的内置指标集 的支持

自定义指标也受支持,用于特定于应用程序的测量(例如,缓存命中/未命中统计信息)。

编写基准测试

编写基准测试的介绍 以及一个 示例仓库

一个稍微复杂的基准测试,用于测量 Histogram package 的一部分

import Benchmark
import Foundation
import Histogram

let benchmarks = {
    // Minimal benchmark with default settings
    Benchmark("Foundation-Date") { benchmark in
        for _ in benchmark.scaledIterations {
            blackHole(Foundation.Date())
        }
    } 

    // Slightly more complex with some customization
    let customBenchmarkConfiguration: Benchmark.Configuration = .init(
        metrics: [
            .wallClock,
            .throughput,
            .syscalls,
            .threads,
            .peakMemoryResident
        ],
        scalingFactor: .kilo
    )

    Benchmark("ValueAtPercentile", configuration: customBenchmarkConfiguration) { benchmark in
        let maxValue: UInt64 = 1_000_000

        var histogram = Histogram<UInt64>(highestTrackableValue: maxValue, 
                                          numberOfSignificantValueDigits: .three)

        for _ in 0 ..< 10_000 {
            blackHole(histogram.record(UInt64.random(in: 10 ... 1_000)))
        }

        let percentiles = [0.0, 25.0, 50.0, 75.0, 80.0, 90.0, 99.0, 100.0]

        benchmark.startMeasurement() // don't measure the setup cost above

        for i in benchmark.scaledIterations {
            blackHole(histogram.valueAtPercentile(percentiles[i % percentiles.count]))
        }

        benchmark.stopMeasurement()
    }
}

基准测试输出和分析

默认输出为表格格式,以便于人类阅读,但该 package 支持一系列不同的输出格式,其输出适用于使用其他可视化工具进行分析。

运行基准测试时的示例默认输出: 基准测试的示例文本输出

支持关键基准测试工作流程

结束语

包括 Swift FoundationSwiftPMSwiftNIOGoogle Flatbuffers 等主要公共项目在内的 Swift 社区最近已开始使用 Benchmark package,以专注于性能优化。

通过浏览详尽的文档,并加入 Swift 论坛上的对话,分享见解并获得您的问题的答案,来了解如何将此工具用于您自己的 Swift 应用程序。或者为什么不为您最喜欢的缺少性能测试的开源 package 提供 PR 呢?

立即采取第一步来改进您的软件,添加其第一个基准测试以检查性能!