☰
BAML 字符串 split 长字面量基准:从 speedtest Workload 定义到零拷贝底层实现
2026/9/25 3:55:14 网站建设 项目流程
  • 编程语言
  • AI Agent
  • 编译器
  • CLI
  • 人工智能

【免费下载链接】baml

The programming language for agents

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

本指南以仓库中 split-long-literal-1k.md 为解剖对象,讲解 BAML 性能基准工具speedtest如何用一份 Markdown 同时定义 BAML / Python / TypeScript 三语言等价基准,验证string.split在长字符串、多分隔符场景下的行为与性能,并结合 bex_vm 的源码剖析 split 的零拷贝实现原理。读完本文,你将理解该 Workload 的模板变量机制、三端代码的等价关系、可预期的输出结果,以及如何用speedtestCLI 单独筛选、运行并对比这一基准。

一、这个 Workload 在测什么:基准设计的核心意图

string::split long literal 1k属于speedtest基准套件中string分类下的一个用例,其命名可以拆解为三部分:

  • string::split:被测对象是字符串内建方法split(delim);
  • long literal:输入是一段接近 200 字符的自然语言长字面量,而不是"hello world"这类短串;
  • 1k:基准循环 1000 次,强调"长输入 × 高频调用"的组合。

与同目录下的姊妹基准对比,能更清楚它的定位:

Workload输入规模迭代次数侧重
string-split-100k.md短字面量"hello world foo bar baz qux"100000短串高频 split 开销
split-short-literal-100k.md短字面量100000短字面量场景
split-medium-literal-10k.md约 200 字符、8 段、\|分隔10000中等字面量
split-long-literal-1k.md(本文)约 200 字符 × 10 段、###分隔1000长字面量、多段分隔

该 Workload 的核心假设是:BAML 的split在长字符串上不应产生明显的分配开销——因为底层实现是零拷贝的。下一节先还原它的完整定义,再在第四节用源码验证这个假设。

二、文档结构剖析:一份 Markdown 如何驱动三语言基准

该 Workload 文件遵循speedtest套件的统一格式:一个#标题、一个可选的## eval-setup段、以及## BAML/## Python/## Typescript三个代码段。解析逻辑位于 loader.py:

  • 正则^#\s+(.+)提取标题作为 workload 名称(string::split long literal 1k);
  • 正则 `^##\s+([\w-]+)\s*\n```\w*\n(.*?)```` 切出各代码段;
  • 若存在eval-setup,通过exec在受限命名空间中执行,产出模板变量;
  • 各语言代码段经模板替换后得到可执行源码。

eval-setup:用 Python 生成三端共享的输入

eval-setup是一段在基准加载时执行的 Python 代码,负责构造输入数据并导出三种语言各自的字面量转义形式:

import json chunk = ( "the quick brown fox jumps over the lazy dog and runs through the meadow " "while the sun sets behind the distant mountains casting long shadows " "across the rolling hills and the gentle breeze carries the scent of " "wildflowers and freshly cut grass through the warm summer air as birds " "sing in the trees and the world feels at peace with itself in this " "perfect moment of stillness and beauty that seems to stretch on forever " "without end or care for the troubles of yesterday or tomorrow only the " "present moment matters now" ) src = "###".join([chunk] * 10) delim = "###" baml_src = json.dumps(src) baml_delim = json.dumps(delim) py_src = repr(src) py_delim = repr(delim) js_src = json.dumps(src) js_delim = json.dumps(delim)

这里的关键设计是一份数据、三份转义:

  • src:把一段约 200 字符的自然语言chunk用###连接 10 次,得到约 2000 字符、含 9 个分隔符的长串;
  • baml_src/baml_delim:JSON 转义(双引号字符串),供 BAML 代码段使用;
  • py_src/py_delim:Pythonrepr转义,保证换行、引号等字符被安全编码;
  • js_src/js_delim:JSON 转义,供 TypeScript/Node 使用。

模板变量机制:$$前缀的巧思

模板替换由 loader 内部的_DDTemplate完成,它继承自 Python 标准库string.Template并将定界符改为$$:

class _DDTemplate(Template): """Template using $$var instead of $var, so single $ is literal.""" delimiter = '$$'

选择$$var而不是$var是有意为之:eval-setup 生成的字符串本身包含大量$(例如 JSON 转义内容),若沿用默认$定界符会产生误替换;改用$$后,只有$$baml_src、$$baml_delim这类显式占位符会被替换,其余$全部按字面保留。替换采用safe_substitute,未定义的$$xxx会原样保留而非抛错。

三、三语言代码对照:语义等价的可执行基准

模板替换完成后,三份代码在语义上完全等价,这是"跨语言对比"成立的前提。

BAML

function main() -> int { let s = $$baml_src; let delim = $$baml_delim; let count = 0; for (let i = 0; i < 1000; i += 1) { let parts = s.split(delim); count += parts.length(); }; return count; }

要点:s.split(delim)返回一个数组,parts.length()取段数,累加 1000 次后作为int返回。src由 10 段连接而成,含 9 个###分隔符,因此每次 split 得到10 个部分,最终返回值是1000 × 10 = 10000。

Python

s = $$py_src delim = $$py_delim c = 0 for _ in range(1000): c += len(s.split(delim)) print(c)

Python 的str.split(delim)同样把10 段 / 9 分隔符切成 10 个元素,print(c)输出 10000。

TypeScript

const s = $$js_src; const delim = $$js_delim; let c = 0; for(let i=0;i<1000;i++) c += s.split(delim).length; console.log(c);

String.prototype.split对非空定界符同样返回 10 个元素,console.log(c)输出 10000。

三端在语义上等价(非空定界符、无尾随空段),因此基准框架可以在计时之前先做跨语言输出校验——见第五节。

四、底层实现验证:split 的零拷贝语义

基准之所以把"长字面量 + 高频调用"作为考察点,与底层实现方式直接相关。BAML 运行时中字符串方法由 package_baml/string.rs 提供,split的实现如下:

// Zero-copy: each segment is a zero-copy substring Slice into the original. fn split(string: &BexStr, delimiter: &BexStr) -> Vec<BexStr> { let s = string.as_str(); let d = delimiter.as_str(); if d.is_empty() { return char_substrings(string); } let base = s.as_ptr() as usize; s.split(d) .map(|part| { let start = part.as_ptr() as usize - base; string.substring(start, start + part.len()) }) .collect() }

实现要点:

  1. 零拷贝切片:用 Rust 标准库str::split(d)遍历分隔位置,对每个子串计算它在原字符串中的字节偏移start,再调用string.substring(start, start + len)生成切片。每个段都是指向原字符串内存的BexStr切片,不复制字符数据——这正是注释Zero-copy的含义;
  2. 空定界符特判:d.is_empty()时退化为char_substrings,即按 Unicode 码点逐字符切分,这与 JavaScript"abc".split("")返回单个字符数组的行为一致;
  3. 共享 substr 基建:lines(按行切分)等相邻方法也复用相同的指针偏移 +substring模式,说明该零拷贝思路在字符串族方法中是统一约定。

由此可以推断:本 Workload 中 1000 次对约 2000 字符长串的 split 主要开销来自分割遍历与结果数组构建,而非字符数据拷贝,这也是它与短串高频场景(如string-split-100k)在测量维度上的本质区别。

五、如何运行与复用这个 Workload

speedtest是仓库baml_language/tools/speedtest下的 Python 工具包(入口配置见 pyproject.toml,脚本入口为speedtest = "speedtest.cli:main"),提供run/compare/open/list/baselines五个子命令,参数定义见 cli.py。

单独跑长字面量 split 基准

# 构建 release 版 baml-cli 后,仅运行名称匹配 "split-long-literal" 的 workload speedtest run --build --filter split-long-literal

常用参数:

参数作用
--filter STR只跑名称包含该子串的 workload,可重复指定多个
--only-baml跳过 Python / Node / Bun,只测 BAML
--runs N固定跑 N 次(默认走自适应计时)
--measurement-time SECS自适应模式下每个 workload 的目标总时长,默认 5 秒
--tag NAME为本次运行打标签,便于后续对比
--profile用samply record采集 CPU profile
--results-dir DIR结果保存目录(默认~/.speedtest/)

run的执行流程(见 runner.py)分五步:

  1. 打包:调用baml-cli pack main --file <w.baml> -o <w.packed>把该 Workload 的 BAML 源码编译为独立可执行文件;
  2. 取基准输出:运行打包产物,取最后一行 stdout 作为期望结果(本 Workload 应为10000);
  3. 跨语言校验:依次运行python3 -S、node、bun(可用时),若输出与期望不一致则标记mismatch——这正是三端语义等价性被自动验证的环节;
  4. 计时:先做 3 次预热(丢弃、暖 OS 缓存),再按目标时长自适应采样(样本数限制在[5, 100]),统计中位数、标准差、最小/最大值;
  5. 落盘:结果保存到results/与baselines/<branch>/latest,支持后续对比。

对比与查看

speedtest compare base new # 对比两个基线 speedtest compare base new --threshold 5 # 忽略变化幅度小于 5% 的项 speedtest list # 列出全部 workload 名称 speedtest open # 浏览器打开结果 UI

复用为 Rust CodSpeed 基准

仓库还提供了 export_baml.py 作为桥接脚本:它复用loader.load_workloads,把每个 Workload 的展开后BAML 源码导出为 JSON 数组({"name", "category", "baml"}),供crates/baml_tests作为 CodSpeed 基准直接复用,避免在 Rust 侧重复实现.md解析、eval-setup 执行与$$模板替换逻辑。

六、围绕该 Workload 的扩展阅读路径

若想深入该基准所依赖的运行时能力,可按以下路径继续探索:

  • split 运行时实现:bex_vm/src/package_baml/string.rs 中split及lines、chars、slice等字符串族方法的零拷贝实现;
  • Workload 解析与模板:speedtest/loader.py 中parse_workload_md、_DDTemplate与load_workloads;
  • 计时与打包:speedtest/runner.py 中自适应计时time_command_adaptive与打包pack_baml;
  • 同类基准对照:string分类下其余 7 个 Workload(workloads/string/),可用于横向对比不同输入规模下 split、contains、concat、substring、trim 的性能特征。

七、小结:本 Workload 验证了什么

string::split long literal 1k用一份 Markdown 同时刻画了三件事:

  1. 测试方法:通过eval-setup+$$模板变量,在加载期为 BAML / Python / TypeScript 生成字节级一致的长输入,保证三端基准的公平性;
  2. 语义契约:三端代码对同一输入必须产出同一结果(10000),并由 runner 在计时前自动校验,防止"测了不同的事";
  3. 实现特征:长字面量高频 split 是零拷贝切片路径的代表性负载,其性能特征与短串场景存在本质差异。

对于想要为 BAML 新增或修改字符串内建方法的开发者,这份 Workload 既是一份可复用的性能回归用例,也是一份可运行的语义验证样例。

  • 编程语言
  • AI Agent
  • 编译器
  • CLI
  • 人工智能

【免费下载链接】baml

The programming language for agents

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

相关推荐

上一篇:Twire高级技巧:画质设置与横屏聊天窗口的隐藏功能
下一篇:在Docker中畅玩Windows:1GB内存也能轻松驾驭

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

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

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

立即咨询