- 数据工程
- 大数据
- 序列化
- 数据分析
【免费下载链接】arrow
Apache Arrow is a multi-language toolbox for accelerated data interchange and in-memory processing
本文是一篇面向 Apache Arrow 开发者的基准测试(Benchmark)实战指南。文章以仓库文档 docs/source/developers/benchmarks.rst 为主体脉络,结合dev/archery下的源码实现(命令入口、Benchmark 数据模型、比较器逻辑)与cpp/src下的 C++ 基准测试用例,系统讲解如何用 Archery 工具运行、对比基准测试,如何在开发迭代中高效复用构建与结果缓存,以及编写可被自动回归检测的基准测试时需要遵守的规范。读完本文,你将掌握archery benchmark run/archery benchmark diff/archery benchmark list的完整用法,并能独立完成一次性能回归分析。
一、前置准备:安装 Archery
运行基准测试套件的第一步是安装 Archery 工具。Archery 是 Apache Arrow 的开发者工具集,以 Python 库加命令行前端(CLI)的形式组织,其 benchmark 相关代码位于 dev/archery/archery/benchmark/ 目录。
Archery 的详细安装与使用说明参见仓库文档 docs/source/developers/continuous_integration/archery.rst。其依赖与打包配置见 dev/archery/setup.py 与 dev/archery/requirements.txt,通常通过pip install -e dev/archery的方式以可编辑模式安装。
安装完成后,可通过archery --help查看全部子命令。基准测试功能由benchmark命令组提供(源码见 dev/archery/archery/cli.py#L346-L353),包含三个子命令:
| 子命令 | 作用 |
|---|---|
archery benchmark list | 列出某个目标(revision 或构建目录)下的全部基准测试名称 |
archery benchmark run | 针对单个目标运行基准测试套件并输出结果 |
archery benchmark diff | 像git diff一样对比两组基准测试结果,检测回归 |
二、运行基准测试套件
基准测试套件通过benchmark run子命令运行。最基本的用法是在当前 git 工作区(workspace)中直接运行:
# 在当前 git 工作区运行基准测试 archery benchmark run # 将结果保存到文件 archery benchmark run --output=run.json这里WORKSPACE是一个保留的特殊标记,代表当前 git 工作区,意味着不会执行额外的 clone 操作(见 dev/archery/archery/cli.py#L472-L473)。除工作区外,目标参数还可以是 git 修订版本(commit、tag、HEAD、HEAD~1等)或一个已有的 CMake 构建目录。
从源码看,CppBenchmarkRunner.default_configuration()(见 dev/archery/archery/benchmark/runner.py#L117-L134)为基准测试构建提供了默认配置:build_type="release"、with_tests=False、with_benchmarks=True,并默认启用 compute、csv、dataset、json、parquet 以及 brotli/bz2/lz4/snappy/zlib/zstd 等压缩算法模块,关闭 Python 绑定。也就是说,基准测试默认在 Release 模式下构建,并覆盖 Arrow 的核心计算与 IO 能力。
2.1 自定义 CMake 参数
有时需要传入自定义的 CMake 编译参数,例如指定编译器或关闭 SIMD 指令集:
export CC=clang-8 CXX=clang++8 archery benchmark run --cmake-extras="-DARROW_SIMD_LEVEL=NONE"--cmake-extras是benchmark命令组的公共选项之一,可多次堆叠使用(multiple=True),会被透传给 CMake 配置阶段(见 dev/archery/archery/cli.py#L381-L383)。这类场景常用于:验证不同编译器(GCC vs Clang)、不同优化级别、不同指令集(如 SSE4.2 / AVX2 / AVX512)对性能的影响。
benchmark命令组的公共选项(源码见 dev/archery/archery/cli.py#L356-L391)还包括:
--src <arrow_src>:指定 Arrow 源码目录(默认取当前环境推断的源码根);--preserve:保留临时工作区(下文详述);--output <file>:将输出结果写入文件;--language {cpp,java}:指定目标语言,目前仅支持cpp与java,默认cpp;--cmake-extras:透传给 CMake 的额外参数(cpp);--cpp-benchmark-extras:透传给 C++ benchmark 可执行文件的额外参数;--build-extras/--benchmark-extras:透传给 Maven build / benchmark 的额外参数(java)。
2.2 指定已有构建目录
如果已经有一个完整的 CMake 构建目录,可以跳过构建阶段直接运行基准测试:
archery benchmark run $HOME/arrow/cpp/release-build从CppBenchmarkRunner.from_rev_or_path(见 dev/archery/archery/benchmark/runner.py#L195-L231)的实现可以看到目标参数的三种解析方式:
- 若目标是 JSON 字符串或 JSON 文件,则反序列化为静态结果集(
StaticBenchmarkRunner); - 若目标是合法的 CMake 构建目录,则直接复用该构建(
CMakeBuild.is_build_dir判断); - 否则将其视为 git 修订版本,在临时目录中 clone 并 checkout 对应代码后创建全新构建。
此外,benchmark run还支持--repetitions <N>与--repetition-min-time <seconds>参数,用于控制每个基准的重复次数与单次最短运行时长,提高统计精度(cpp 默认 1 次、java 默认 5 次,见 dev/archery/archery/cli.py#L447-L454)。
2.3 了解可运行的基准测试
在运行之前,可以用benchmark list先查看某个目标下有哪些基准测试:
# 列出当前工作区的全部基准测试 archery benchmark list # 列出某个构建目录中的基准测试 archery benchmark list /build/cpp # 列出某个历史提交的基准测试(会临时 clone 该修订版本) archery benchmark list HEAD~1list与run复用相同的 runner 解析逻辑,只是输出内容不同:前者逐个输出套件名.基准名,后者真正执行并序列化结果。以 C++ 为例,runner 会扫描构建目录中所有匹配*-benchmark的可执行文件作为"套件"(见 dev/archery/archery/benchmark/runner.py#L136-L143),仓库中真实的基准套件分布在 cpp/src/arrow/io/memory_benchmark.cc、cpp/src/arrow/io/file_benchmark.cc、cpp/src/arrow/compute/kernels/scalar_arithmetic_benchmark.cc 等数十个文件中。
三、结果对比:检测性能回归
基准测试的核心目标之一是检测性能回归(performance regression)。为此,archery通过benchmark diff子命令实现了基准对比功能——它的定位就像"针对基准测试结果的 git diff"(见 dev/archery/archery/cli.py#L555-L557)。
默认调用下,它会将当前源码(即 git 中的当前工作区,作为 contender)与本地主干分支(默认基线 baseline 为origin/HEAD)进行对比:
archery --quiet benchmark diff --benchmark-filter=FloatParsing ----------------------------------------------------------------------------------- Non-regressions: (1) ----------------------------------------------------------------------------------- benchmark baseline contender change % counters FloatParsing<FloatType> 105.983M items/sec 105.983M items/sec 0.0 {} ------------------------------------------------------------------------------------ Regressions: (1) ------------------------------------------------------------------------------------ benchmark baseline contender change % counters FloatParsing<DoubleType> 209.941M items/sec 109.941M items/sec -47.632 {}输出被划分为Non-regressions(无回归)与Regressions(回归)两个分组,字段包括基准名、基线值(baseline)、对比值(contender)、变化百分比(change %)与 counters。上述示例中,FloatParsing<DoubleType>从 209.941M items/sec 跌到 109.941M items/sec,变化达 -47.632%,被判定为回归。
3.1 对比判定逻辑的源码实现
回归判定逻辑位于 dev/archery/archery/benchmark/compare.py:
- 全局回归阈值
DEFAULT_THRESHOLD = 0.05(5%),注释明确说明这是一个"主观且并不完美"的全局默认值,不追踪累积回归(见 dev/archery/archery/benchmark/compare.py#L19-L21); BenchmarkComparator.change计算变化率:(new - old) / abs(old);regression属性结合less_is_better(单位是否是per_second类型决定"越小越好"还是"越大越好")与阈值判定回归,即调整后的变化超过 5% 即视为回归(见 dev/archery/archery/benchmark/compare.py#L104-L108);RunnerComparator.comparisons对两套 runner 的套件与基准按名称做两两对比(pairwise_compare),生成BenchmarkComparator序列;- 输出格式化在
_format_comparisons_with_pandas(见 dev/archery/archery/cli.py#L687-L713):使用 pandas 将结果解析为 DataFrame,按change %降序排列,再切分为 Non-regressions / Regressions 两组打印。
--threshold选项可覆盖默认的 5% 阈值:
# 将回归阈值放宽到 10% archery benchmark diff --threshold=0.13.2 对比任意目标组合
benchmark diff的两个位置参数分别是contender与baseline,二者均可为 git 修订版本或 CMake 构建目录,因此可以灵活组合出多种对比场景,例如:对比不同编译器、不同编译选项构建的结果:
# 用 gcc-7 构建 gcc7-build,用 clang-8 构建 clang8-build archery build --with-benchmarks=true \ --cxx-flags=-ftree-vectorize \ --cc=gcc-7 --cxx=g++-7 gcc7-build archery build --with-benchmarks=true \ --cxx-flags=-flax-vector-conversions \ --cc=clang-8 --cxx=clang++-8 clang8-build # 对比两个构建目录 archery benchmark diff gcc7-build clang8-build对比主干分支与最新发布 tag:
export LAST=$(git tag -l "apache-arrow-[0-9]*" | sort -rV | head -1) archery benchmark diff <default-branch> "$LAST"更多调用示例可通过archery benchmark diff --help查看。
四、高效迭代:三招降低基准测试开发开销
基准测试开发往往因漫长的构建时间与运行时间而显得繁琐。archery benchmark diff提供了多种技巧来显著降低这一开销。
4.1 复用已有构建目录(--preserve)
基准测试命令支持直接对比已有构建目录。配合--preserve标志,可以避免从零开始重复构建源码:
# 第一次调用:在临时目录中 clone 并 checkout,--preserve 保留该目录 archery benchmark diff --preserve # 修改 C++ 源码 ... # 在先前创建的构建目录中重新运行基准测试 archery benchmark diff /tmp/arrow-bench*/{WORKSPACE,master}/build当目标为 git 修订版本时,archery 默认在临时目录中完成 clone/checkout 与构建,临时目录随命令结束被清理;--preserve会保留该目录(目录名形如/tmp/arrow-bench*/{WORKSPACE,master}/build),供后续迭代直接复用,仅增量重建修改过的部分(对应 dev/archery/archery/cli.py#L366-L368 中--preserve选项,以及tmpdir(preserve=preserve)的上下文管理)。
4.2 用 JSON 结果文件做"穷人缓存"
benchmark run的结果可以保存为 JSON 文件。这既能避免重新构建源码,也能避免重复执行(有时代价很高的)基准测试——相当于一种轻量级缓存机制:
# 在某个 commit 上运行基准测试并保存结果 archery benchmark run --output=run-head-1.json HEAD~1 # 将此前捕获的结果与 HEAD 对比 archery benchmark diff HEAD run-head-1.json运行结果文件同样可用于 diff 的 contender 与 baseline 两个位置:
archery benchmark run --output=baseline.json $HOME/arrow/cpp/release-build git checkout some-feature archery benchmark run --output=contender.json $HOME/arrow/cpp/release-build archery benchmark diff contender.json baseline.json从from_rev_or_path的第一条分支可见(dev/archery/archery/benchmark/runner.py#L211-L214),只要目标是可反序列化为 runner 的 JSON(文件或字符串),就会走StaticBenchmarkRunner.from_json直接加载静态结果,不再触发任何构建与执行。JSON 的编解码格式定义在 dev/archery/archery/benchmark/codec.py,每个 Benchmark 序列化为name / unit / less_is_better / values / time_unit / times / counters字段,套件与 runner 逐层嵌套,最终可整体存入一个文件。
4.3 正则过滤套件与基准
benchmark命令支持用--suite-filter过滤套件、用--benchmark-filter过滤基准,两者均接受正则表达式:
# 从一次既有运行中接手,只保留匹配 Kernel 的基准、匹配 compute-aggregate 的套件 archery benchmark diff \ --suite-filter=compute-aggregate --benchmark-filter=Kernel \ /tmp/arrow-bench*/{WORKSPACE,master}/build过滤器的实现位于 dev/archery/archery/benchmark/runner.py#L33-L37:regex_filter将正则编译后对套件名 / 基准名执行search,None表示不过滤。过滤在suites属性中逐级应用(先过滤套件、再过滤套件内基准,见 dev/archery/archery/benchmark/runner.py#L168-L193)。另外,若过滤条件匹配不到任何基准,runner 会抛出ValueError("No benchmark matches the suite/benchmark filter")用于及早发现拼写错误。
--benchmark-filter同样适用于run/list(见benchmark_filter_options,dev/archery/archery/cli.py#L394-L403),例如按套件前缀精确收窄对比范围:
archery benchmark diff --suite-filter="^arrow-compute-aggregate" \ --benchmark-filter="(Sum|Mean)Kernel"五、回归检测:如何编写合格的基准测试
要让基准测试被自动纳入回归检测,编写时需要遵守以下规范(规则详见 docs/source/developers/benchmarks.rst 的 "Regression detection" 一节)。
5.1 四条硬性规则
规则 1:基准名必须以^Regression开头。benchmark命令默认用正则^Regression过滤基准测试,因此并非所有基准都会默认运行。如果希望自己的基准被自动执行回归校验,命名必须匹配该前缀。例如仓库中的RegressionSumKernel、RegressionMeanKernel一类命名(在 dev/archery/archery/tests/test_benchmarks.py 的测试与 fixtures 中均有体现)。
规则 2:不要覆盖 C++ 基准参数定义中的 repetitions。命令会以--benchmark_repetitions=K运行以获得统计显著性(对应 dev/archery/archery/benchmark/google.py#L53-L68 中GoogleBenchmarkCommand.results组装--benchmark_repetitions、--benchmark_out、--benchmark_out_format=json等参数)。因此,基准自身不应在参数定义中硬编码重复次数,否则会与命令行重复设置冲突。
规则 3:基准应运行得足够快。受规则 2 影响,基准会被重复执行多次。当输入数据放不进内存(L2/L3 缓存)时,基准往往会变成内存带宽受限而非 CPU 受限,此时应缩小输入规模,保证测量的是 CPU 计算路径。这一原则在仓库的基准实现中有大量体现,例如ParallelMemoryCopy基准(cpp/src/arrow/io/memory_benchmark.cc#L311-L315)通过RangeMultiplier(2)、Range(1, kNumCores)控制线程数范围;BufferOutputStream系列基准固定写入约 32 MB 数据((1 << 25) / raw_nbytes次迭代)以维持可控的运行时长(cpp/src/arrow/io/memory_benchmark.cc#L317-L329)。
规则 4:正确选择时间度量(cputime vs realtime)。Google benchmark 库默认使用cputime度量——即进程所有线程在 CPU 上花费的运行时间之和;与之相对的realtime是墙钟时间(end_time - start_time)。两者的取舍如下:
- 单线程模型下,cputime 受上下文切换影响更小,是更优选择;
- 多线程场景下,cputime 会被线程数放大、严重偏离 realtime,此时应改用
SetRealtime()(C++ 中对应UseRealTime())。
仓库中多线程基准ParallelMemoryCopy与多个 IO 流基准都显式调用了UseRealTime()(见 cpp/src/arrow/io/memory_benchmark.cc#L311-L315 与 cpp/src/arrow/io/file_benchmark.cc#L290-L296)。Archery 在解析 GoogleBenchmark 结果时也会识别名称含/real_time的观测并优先采用 realtime 值(见 dev/archery/archery/benchmark/google.py#L108-L120 的is_realtime与time属性)。
5.2 结果聚合:中位数与统计信息
archery 对单次运行的多次重复观测(runs)做聚合处理:GoogleBenchmark会剔除 Google benchmark 生成的_mean、_median、_stddev等聚合观测(run_type == "aggregate"),仅保留真实 runs,并排序后取中位数作为基准的代表值(dev/archery/archery/benchmark/google.py#L155-L166)。Benchmark.median的计算见 dev/archery/archery/benchmark/core.py#L19-L26。单位方面,bytes_per_second与items_per_second型指标"越大越好"(less_is_better=False),纯时间型指标则相反;对比输出时,数值会由items_per_seconds_fmt/bytes_per_seconds_fmt自动格式化为 K/M/G 单位(见 dev/archery/archery/benchmark/compare.py#L24-L58),因此文档示例中会出现105.983M items/sec这类读数。
六、脚本化:把 benchmark 能力集成进自动化流程
archery以 Python 库 + 命令行前端的形态编写,其库可以导入用于自动化任务。benchmark run与benchmark diff在内部均以 Python 对象表达:BenchmarkRunner(runner)、BenchmarkSuite/Benchmark(数据模型)、RunnerComparator/BenchmarkComparator(比较器),均可直接 import 复用。
由于 CLI 的构建输出可能相当冗长,有两种方式控制输出:
- 使用
--quiet选项抑制日志输出; - 使用
--output=<file>将结果写入文件。
# 将 diff 结果写入文件(等价于 --output) archery benchmark diff --benchmark-filter=Kernel --output=compare.json ... # 用 --quiet 避免 stdout 被日志干扰,再重定向到文件 archery --quiet benchmark diff > result.json--output写入的内容实际是 JSON Lines 格式(每个 comparator 序列化为一行 JSON,见_get_comparisons_as_json,dev/archery/archery/cli.py#L678-L684),便于下游程序逐行解析;而终端直接展示的表格则依赖 pandas 格式化。结合"JSON 结果文件可被 diff 直接当作 contender/baseline 使用"的特性,可以实现"先采集、后分析"的解耦流水线:
archery benchmark run --output=run.json HEAD~1 archery --quiet benchmark diff WORKSPACE run.json > result.json第一条命令在历史提交上采集基线结果并缓存,第二条命令将当前工作区与缓存结果对比——由于run.json是静态结果,diff 不会重新构建或运行任何基准(见 dev/archery/archery/benchmark/runner.py#L196-L231 的三种目标解析优先级)。仓库自身的测试 dev/archery/archery/tests/test_benchmarks.py 及其 fixtures(如 dev/archery/archery/tests/fixtures/archery-benchmark-diff.jsonl)覆盖了 diff 输出解析等关键路径,可作为二次开发时理解数据格式的参考。
七、小结
围绕基准测试这一主题,Archery 提供了"运行(run)— 对比(diff)— 回归检测(Regression 前缀 + 阈值判定)— 脚本化"的完整闭环:
- 用
archery benchmark run [rev_or_path]采集结果,支持 git 修订版本、WORKSPACE 与已有 CMake 构建目录三种目标,必要时以--cmake-extras注入自定义编译参数; - 用
archery benchmark diff [contender] [baseline]完成"基准测试版的 git diff",默认以 5% 阈值划分 Regressions 与 Non-regressions,--suite-filter/--benchmark-filter用正则收窄范围; - 用
--preserve、JSON 结果缓存、正则过滤三种手段大幅压缩迭代成本; - 编写回归基准时遵守命名前缀
^Regression、不覆盖 repetitions、控制运行时长、多线程场景使用UseRealTime()四条规则,即可无缝接入自动回归检测。
对于深度使用场景,建议直接阅读 dev/archery/archery/benchmark/ 下的 runner、compare、google、codec 四个模块,理解 runner 的目标解析优先级、比较器的阈值判定与 JSON 编解码格式,即可将基准能力进一步集成到自定义的性能监控与 CI 流程中。
- 数据工程
- 大数据
- 序列化
- 数据分析
【免费下载链接】arrow
Apache Arrow is a multi-language toolbox for accelerated data interchange and in-memory processing
相关推荐
使用 Archery 运行 Apache Arrow 基准测试:套件执行、结果对比与回归检测实战指南
使用 Archery 运行 Apache Arrow 基准测试:套件执行、结果对比与回归检测实战指南 本文面向 Apache Arrow 的贡献者与性能敏感型使
大数据数据分析数据工程序列化Apache Arrow 性能基准测试实践:用 Archery 运行、对比与回归检测
Apache Arrow 性能基准测试实践:用 Archery 运行、对比与回归检测 本文是 Apache Arrow 开发者基准测试(Benchmark)指南
数据工程数据分析大数据如何用 archery 运行 Arrow C++ 基准测试并与 main 分支比较检测性能回退
如何用 archery 运行 Arrow C++ 基准测试并与 main 分支比较检测性能回退 如果你在 Arrow 仓库中修改了 C++ 代码,想确认这次改动
大数据数据分析数据工程序列化
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考