☰
Vaex 基准测试实战:基于 ASV 的本地开发与自动化性能监控体系
2026/9/25 2:02:51 网站建设 项目流程
  • 数据分析
  • 数据可视化
  • 大数据
  • 数据科学

【免费下载链接】vaex

Out-of-Core hybrid Apache Arrow/NumPy DataFrame for Python, ML, visualization and exploration of big tabular data at a billion rows per second 🚀

项目地址:https://gitcode.com/gh_mirrors/va/vaex
点击查看免费下载

Vaex 作为一个面向十亿行级表格数据、以 Out-of-Core(内存外)计算为核心的 DataFrame 库,性能演进始终是其开发重心。为了让每次提交都能被量化评估,Vaex 将一套完整的基准测试(benchmark)体系集成进仓库:以 Airspeed Velocity(ASV)为运行引擎,在专用硬件上按既定调度自动运行,并把结果以 HTML 报告和历史 JSON 数据的形式持续沉淀下来。

本文以 benchmarks/README.rst 为主线,结合仓库内真实的基准测试源码(benchmarks/目录)与调度脚本 bin/benchmark_new_commits.sh,完整讲解如何在本地开发/验证基准测试、如何让基准在专用机器上自动化运行,以及如何从零搭建一台基准测试运行机。读完本文,你将掌握 Vaex 性能基准的编写规范、ASV 的两种本地运行模式,以及整套自动化基准管线的搭建方法。

技术选型:为什么是 Airspeed Velocity

Vaex 的基准测试统一由 ASV(Airspeed Velocity)驱动。ASV 是一个面向 Python 项目的性能基准框架,核心优势包括:

  • 环境隔离:可以在独立 Conda 环境中针对不同版本/提交运行测试,避免环境污染导致的结果失真;
  • 参数化矩阵:支持params/param_names声明参数化测试,自动展开成多维测试矩阵;
  • 结果历史:自动维护.asv/results/目录下的 JSON 结果历史,便于长期追踪性能回归;
  • 报告生成:通过asv publish生成静态 HTML 报告,方便发布到 Web 供团队与社区查看。

在 Vaex 仓库中,这一体系被划分为两个层面:

  1. 开发层面:开发者在本地编写、调试、试跑基准测试(asv dev/asv run --python=$(which python));
  2. 生产层面:专用硬件按 cron 调度自动运行(bin/benchmark_new_commits.sh),覆盖master分支的新提交与bench*分支的增量提交。

安装 ASV

按 benchmarks/README.rst 的说明,ASV 通过 Conda 安装(conda-forge 渠道):

conda install -c conda-forge asv

安装后,在仓库根目录下即可使用asv命令。注意:本地开发模式并不要求创建专用 Conda 环境,直接复用当前 Python 环境即可;而生产机器上 ASV 会为每次基准运行创建独立的新 Conda 环境(详见下文"生产环境"部分)。

认识 benchmarks 目录:基准测试的组织方式

仓库中的 benchmarks/ 目录集中存放所有基准测试,按功能域拆分为多个模块文件:

文件覆盖主题
aggregates.py聚合操作:count、mean、binby 一维/二维分箱、groupby
strings.py字符串操作:capitalize、contains、regex、split、replace 等
filter.py过滤后的 head / tail / count
groupby.pygroupby 聚合
groupbyh2o.pyH2O 风格的 groupby 基准
hashmap.py哈希表相关基准
isin.pyisin成员判断性能
sort.py排序性能
export.pyHDF5 导出性能
bookkeeping.py簿记类操作
fixtures.py基准数据集生成工具

所有这些模块通过 benchmarks/init.py 组织为 ASV 可识别的基准测试集合(ASV 默认递归发现bench_*目录/文件中满足命名约定的类与函数)。

编写基准测试:结构规范与源码解析

ASV 的基准测试通常以类(或带setup属性的函数)为单位,方法名以time_、peak、mem等前缀命名以区分计时/内存等指标。下面以仓库真实代码为例拆解其结构。

类级元数据

在 benchmarks/strings.py 中可以看到典型的类元数据声明:

class Strings: pretty_name = "Performance of string operations" version = "1" timeout = 3600 # in seconds param_names = [ 'N', # number of rows to use ] params = ([10**7])
  • pretty_name:基准的可读名称,用于在 HTML 报告中展示;
  • version:基准版本号,改动基准逻辑时递增,ASV 据此区分新旧结果;
  • timeout:单次运行的超时时间(秒),字符串基准因数据量大设为 3600 秒;
  • params/param_names:声明参数化矩阵。params为列表的列表,与param_names一一对应;ASV 会自动生成笛卡尔积。例如 aggregates.py 中params = ([10**7, 5*10**7, 10**8],),param_names = ['N'],表示在 1 千万、5 千万、1 亿行三种规模下各跑一遍;而 isin.py 则同时参数化行数 N 与候选值个数 M:
params = ([10**7, 5*10**7, 10**8], (1, 10, 100, 1_000, 1_000_000)) param_names = ['N', 'M']

数据准备:setup_cache 与 setup

ASV 基准类中两个特殊方法负责数据准备:

  • setup_cache(self):只运行一次,负责生成昂贵且可复用的数据,结果会被 ASV 缓存。Vaex 中典型用法是调用 fixtures.py 中的生成函数,确保测试数据集存在:
def setup_cache(self): # ensure the dataframe is generated generate_numerical()
  • setup(self, *params):每组参数组合运行前调用一次(每次计时前都会执行),负责加载 DataFrame、切片、预处理。例如 strings.py 中的 setup 将 1 亿行数据集切成前 N 行,并把执行器的 buffer 划分为 24 个分区以贴近多线程执行场景:
def setup(self, N): partitions = 24 df = vaex.open(generate_strings()) df_vaex = df[0:int(N)] df_vaex.executor.buffer_size = len(df_vaex) // partitions self.df = df_vaex

而 aggregates.py 的 setup 还会预先对多个整数列执行categorize(把连续整数映射为类别),用于模拟 groupby/binby 常见的类别列场景:

self.df.categorize(self.df.i8_10, min_value=5, max_value=15, inplace=True) self.df.categorize(self.df.i8_1K, min_value=5, max_value=1_000+5, inplace=True) self.df.categorize(self.df.i8_1M, min_value=5, max_value=1_000_000+5, inplace=True)

计时方法:以 time_ 为前缀

每个time_方法即一条独立的计时基准。以 strings.py 为例,它覆盖了 20 余种字符串操作的性能,例如:

def time_capitalize(self, N): self.df.s.str.capitalize().nop() def time_contains_regex(self, N): self.df.s.str.contains("9", regex=True).nop() def time_replace_regex(self, N): self.df.s.str.replace("1?[45]4", "1004", regex=True).nop() def time_split_and_join(self, N): self.df.s.str.split(".").str.join("-").nop()

注意这里的nop()是 Vaex 提供的"执行但不返回结果"的方法,用于强制触发完整的表达式求值(否则惰性求值下表达式可能不会真正执行),从而测得真实的端到端耗时。聚合与过滤类基准同理,例如 aggregates.py 中的一维/二维分箱计数:

def time_count_x_binby128(self, N): self.df.count('x', binby='x', limits=[-1, 1], shape=128) def time_count_star_x4(self, N): self.df.count(binby=[self.df.x4, self.df.y4], limits=[-1, -1], shape=128)

函数式基准:export.py 的写法

除了类,ASV 也支持函数式基准。在 export.py 中可以看到一种更轻量的写法:通过给函数设置.setup、.params、.param_names属性完成声明:

def time_export_plain(N, M): with tempfile.TemporaryDirectory() as tmpdir: df.export_hdf5(os.path.join(tmpdir, 'bench.hdf5')) time_export_plain.setup = setup_df time_export_plain.params = [[1024**2, 1024**2*16], [1, 4, 16]] time_export_plain.param_names = ['N', 'M']

setup_df负责构造 N 行、M 列的浮点 DataFrame,time_export_plain则把整个 DataFrame 导出为 HDF5,衡量 1 列 / 4 列 / 16 列场景下的序列化吞吐。函数式写法更适合单一、自包含的基准,类写法则适合共享数据准备的成组基准。

本地开发:快速验证基准测试

编写或修改基准后,先用开发模式快速试跑。按 benchmarks/README.rst,本地验证的命令为:

# 运行某个基准套件(例如 Strings),只重复一次,使用当前 Python 环境 asv dev --bench Strings

asv dev的特点:

  • 只运行一次:不进行多轮重复取中位数,速度极快,适合验证基准是否可运行、结果量级是否合理;
  • 复用当前环境:不会创建新 Conda 环境,避免每次验证都要重新解析依赖;
  • --bench参数支持子串匹配:例如--bench Strings只运行Strings类下的所有计时方法,也可用--bench Aggregate匹配聚合相关基准。

在跑asv dev之前,建议先确认基准数据存在。以字符串基准为例,首次运行会触发 fixtures.py 的generate_strings(),在~/.vaex/benchmarks/下生成包含 1 亿行(strings_8.hdf5)的 HDF5 数据集,包含顺序整数及其字符串表示两列。数据生成后会被复用,后续基准运行无需重建。

本地完整运行:全量基准

验证通过后,可在笔记本/工作站上跑一次完整基准。同样复用当前 Python 环境:

asv run --python=$(which python)

asv run是 ASV 的标准运行命令:

  • 未加参数时默认对当前检出代码运行全部基准;
  • --python=$(which python)明确指定使用当前 Python 解释器,而不是由 ASV 自动创建 Conda 环境——这保证了本地开发与生产环境行为一致但开销更低;
  • 默认会对每个基准进行多次重复(如--repeat控制重复次数)并汇总统计量(均值、标准差等)。

运行结果会写入.asv/results/目录下的 JSON 文件中,随后可通过asv publish生成 HTML 报告,用asv preview在本地浏览器查看趋势图。

提示:本地完整运行耗时较长(strings 基准单次超时就设定了 3600 秒)。建议开发阶段先用asv dev --bench <你的新基准名>验证,再在专用硬件上跑完整矩阵。

生产环境:自动化基准管线

Vaex 的基准并非只在开发机运行,而是有一套完整的自动化流程。按 benchmarks/README.rst 的描述,其工作流程为:

  1. cron 触发:一个 cron 任务定时调用 bin/benchmark_new_commits.sh;
  2. 切到 master:脚本强制将工作区重置为vaex仓库的master分支最新提交,基准测试代码一律取自master;
  3. 测 master 新提交:先对master上尚未基准过的新提交运行基准;
  4. 测 bench分支*:若存在bench*前缀的分支,则对origin/master..origin/<branch>之间的增量提交运行基准——注意基准测试代码仍取自master,因此这种方式适用于改进既有基准,而不适合开发新基准(新基准应合入master后再运行);
  5. 合并历史:将新结果与vaex-asv仓库中归档的.asv/results/**/*.json合并回该仓库并推送,从而保留完整历史;
  6. 发布 HTML:asv publish生成静态 HTML 并拷贝到基准机的 Web 目录,结果即可在 ASV 报告站点上公开访问。

调度脚本源码解读

bin/benchmark_new_commits.sh 用 Bash 实现了上述流程,几个关键函数值得逐一看:

结果恢复——每次运行前,从 vaex-asv 仓库拉取并恢复历史结果,保证结果连续可追溯:

function restore_results_from_git () { # copy results from vaex-asv/.asv/results/**.json to vaex/.asv/results/**.json (cd ../vaex-asv && git pull -f) (mkdir -p .asv) (mkdir -p ../vaex-asv/.asv/results/) echo Restoring the following results from vaex-asv repo: find ../vaex-asv/.asv/results/ cp -r ../vaex-asv/.asv/results/ .asv/ }

分支发现——找出所有以bench开头的远端分支作为候选测试目标:

function get_additional_branches_to_benchmark () { # find branches which start with "bench*" in the "origin" git branch -a | grep origin/bench | sed -e 's/.*origin\/\(.*\)/\1/' }

主流程——支持两种模式:带--only-last参数时只测master最新一个提交(用于机器初始化验证);否则测master全部新提交,再对每个 bench* 分支测其相对master的增量提交:

if [[ "$1" == "--only-last" ]] then echo Benchmarking last commit on master asv run else echo Benchmarking new commits on master (asv run NEW) || (echo Maybe no new commits to benchmark?) for branch in $(get_additional_branches_to_benchmark) do echo Benchmarking commits on "${branch}" asv run origin/master..origin/${branch} done fi # generate HTML asv publish store_and_push_results_in_git copy_results_to_www

其中:

  • asv run NEW中的NEW是 ASV 对"自上次运行以来新增提交"的引用名,配合--set-commit-hash可将结果挂到具体提交上;
  • asv run origin/master..origin/<branch>用 git 的 range 语法圈定要测的提交范围;
  • asv publish把 JSON 结果聚合为 HTML 报告;
  • store_and_push_results_in_git将结果拷贝回 vaex-asv 仓库并git commit+git push,完成历史归档;
  • copy_results_to_www把.asv/html/*拷贝到/var/www/asv.vaex.io/html/,即 Web 服务器根目录,供外部访问。

从脚本可以看到,整个管线刻意保持了"主仓库 + 独立结果仓库(vaex-asv)"的分离:vaex主仓库只包含基准代码,性能历史数据全部沉淀在 vaex-asv 仓库中,避免主仓库因频繁写入结果 JSON 而膨胀。

从零搭建基准运行机:初始设置 8 步

benchmarks/README.rst 给出了一套完整的新机器初始化清单,可用于搭建新的专用基准运行机:

1. 创建专用 Unix 用户(例如github),让基准运行与日常操作隔离。

2. 配置 SSH 部署密钥:生成 SSH key,并将其添加为 vaex-asv 仓库的 Deploy key,使该用户具备推送结果提交的权限。

3. 克隆两个仓库:

git clone git@github.com:vaexio/vaex.git git clone git@vaexio/vaex-asv.git # 结果归档仓库,实际地址以你的配置为准

4. 配置 git 提交身份,用于生成基准结果提交:

git config --global user.name "Vaex GitHub Integration" git config --global user.email github@vaex.io

5. 安装 Miniconda 并安装 ASV:

conda install asv -c conda-forge

6. 配置机器参数:运行asv machine,将 CPU 型号、核数、内存等硬件信息写入~/.asv-machine.json。这一步很关键——ASV 依赖该文件识别运行机,并据此在结果中标注机器标识。

7. 首次试跑:执行bin/benchmark_new_commits.sh --only-last,只对master最新提交跑一次基准,确认管线端到端可用并初始化结果历史。

8. 注册 cron 定时任务:例如每 8 小时运行一次。在crontab -e中写入:

PATH=/home/github/miniconda3/bin:/home/github/miniconda3/condabin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin 0 */8 * * * /home/github/vaex/bin/benchmark_new_commits.sh >/home/github/log/benchmark-run-$(date "+\%Y-\%m-\%d_\%H-\%M-\%S").log 2>&1

注意 cron 行中的两个要点:

  • PATH 必须显式写出:cron 环境 PATH 极简,不包含 Miniconda 的 bin 目录,若不配置则asv、conda等命令将无法执行;
  • 日志重定向:把 stdout/stderr 重定向到带时间戳的日志文件(date格式中的%需在 cron 中写成\%转义),方便事后排查某轮运行是否失败。

另外建议先mkdir /home/github/log/创建日志目录。生产环境下,ASV 每次运行都会创建新的 Conda 环境(基于asv.conf.json或仓库声明的依赖矩阵),保证不同运行之间的依赖环境可复现、可比对。

基准数据集:fixtures 的生成逻辑

基准的可复现性依赖固定的测试数据。benchmarks/fixtures.py 提供了两个生成函数,数据文件统一落在~/.vaex/benchmarks/私有目录下,已存在则跳过生成:

generate_strings()生成 1 亿行字符串数据(strings_8.hdf5):一列为顺序整数,一列为整数的字符串表示。生成后还会shuffle()打乱顺序再导出,模拟真实数据的随机分布:

nmax = 8 filename = f'strings_{nmax}.hdf5' path = os.path.join(vaex.utils.get_private_dir('benchmarks'), filename) if not os.path.exists(path): x = np.arange(0, int(10 ** nmax)) s = x.astype(str) df_vaex = vaex.from_arrays(x=s, s=s) df_vaex.shuffle().export(path, progress=False)

generate_numerical()生成数值基准数据(numerical_8-b.hdf5),设计了一套分层测试矩阵:

  • x/y:float64,标准正态分布;x4/y4为其 float32 版本,用于对比精度对性能的影响;
  • iB_10:10 个在 5~15 之间的随机整数,B ∈ {1,2,4,8},对应 int8 到 int64,用于测试小基数分箱/分组;
  • iB_100:100 个 5~105 的随机整数;
  • iB_1K:1000 个 5~1005 的随机整数(int16 起);
  • iB_1M:100 万个 5~1,000,005 的随机整数(int32 起),用于测试高基数分组时的哈希表性能。
i10 = np.random.randint(5, 10 + 5, N, dtype=np.int64) i100 = np.random.randint(5, 100 + 5, N, dtype=np.int64) i1K = np.random.randint(5, 1000 + 5, N, dtype=np.int64) i1M = np.random.randint(5, 1_000_000 + 5, N, dtype=np.int64)

从源码可以推断,这套数据刻意覆盖"基数从 10 到 100 万跨五个数量级 + 整数位宽从 8 到 64 位跨四个档位"的组合,从而系统性地检验 Vaex 在不同哈希密度下的分组、分箱、isin、去重等核心算子性能。例如 aggregates.py 的BinByCat1M类就只对 4 字节与 8 字节的 100 万基数列做分箱测试(params = Aggregates.params + ([4, 8],)),isin.py 则将候选集合大小从 1 一直参数化到 100 万,观察isin在哈希表扩展过程中的行为。

常见基准场景一览

结合源码,可以梳理出 Vaex 基准覆盖的核心性能面,便于你定位与扩展:

  • 字符串处理(strings.py):大小写转换、trim、补齐、正则匹配/替换、split/join、子串定位等 20+ 个计时方法,全部基于 1 亿行字符串列;
  • 聚合与统计(aggregates.py):count()、均值、一维/二维 binby(shape=128)、按不同基数类别列分箱;
  • GroupBy(groupby.py、groupbyh2o.py):按 100 基数、100 万基数及 2 的幂次列分组计数;
  • 过滤后访问(filter.py):构造布尔过滤后的 DataFrame,测head()、tail()、count()的缓存命中性能;
  • 成员判断(isin.py):数值与字符串列的isin,参数化候选集大小;
  • 导出(export.py):不同行列规模的 HDF5 导出吞吐,含相关列(sum派生列)导出场景。

结语

Vaex 的基准测试体系是一个"本地可迭代、线上可监控"的完整闭环:本地用asv dev与asv run --python=$(which python)快速开发和验证基准,生产环境则由 cron 驱动 bin/benchmark_new_commits.sh,对master与bench*分支的新提交自动执行基准、把结果归档到独立结果仓库、并发布为静态 HTML 报告。

如果你正在为 Vaex 贡献性能优化或新增基准,推荐的实操路径是:阅读 benchmarks/fixtures.py 理解现有数据集设计 → 参照 benchmarks/strings.py 或 benchmarks/aggregates.py 的类结构编写新基准 → 用asv dev --bench <你的基准名>快速验证 → 合入master后由专用硬件接管长期监控。这样既能保证每次提交的性能变化被及时发现,也能让社区的每一项性能工作都有数据可依。

  • 数据分析
  • 数据可视化
  • 大数据
  • 数据科学

【免费下载链接】vaex

Out-of-Core hybrid Apache Arrow/NumPy DataFrame for Python, ML, visualization and exploration of big tabular data at a billion rows per second 🚀

项目地址:https://gitcode.com/gh_mirrors/va/vaex
点击查看免费下载

相关推荐

上一篇:RT-DETR 2025终极指南:动态卷积技术深度解析与实战应用
下一篇:如何解决Universal_Robots_ROS2_Driver常见问题?开发者必看的调试指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询