
编程语言AI Agent编译器CLI人工智能【免费下载链接】bamlThe programming language for agents项目地址https://gitcode.com/gh_mirrors/ba/baml点击查看免费下载导读本文围绕 BAML 开源仓库 speedtest 基准套件中的string::substring slice long 100k工作负载展开先完整拆解该工作负载的构造方式长字符串数据、100,000 次循环、slice(100, 400)切片累加再对照 Python 与 TypeScript 的同构实现最后深入 BAML 虚拟机源码讲清slice在 codepoint 索引与零拷贝BexStr之下的真实执行路径并给出在本仓库中运行与验证该基准的具体方法。读完本文你将能读懂任意一个.md工作负载文件理解 speedtest 的解析—打包—计时—交叉验证流水线并掌握 BAML 字符串切片与 Python/JS 切片的语义差异。一、工作负载文件全景一份文件、三种语言、一个基准工作负载文件位于 substring-slice-long-100k.md与同目录下其他split-*、trim-*、contains-*工作负载一样采用统一的四段式 Markdown 结构## eval-setup一段 Python 代码用于构造测试数据并生成三种语言各自可用的字符串字面量## BAMLBAML 源码片段即被测对象## Python语义等价的 Python 实现## Typescript语义等价的 TypeScript 实现。文件名中的100k对应主循环的100,000 次迭代与split-long-literal-1k的 1000 次、split-medium-literal-10k的 10,000 次形成递进对照long则点明被测源字符串是长字符串。整个 workload 目录按类别组织在 baml_language/tools/speedtest/workloads 下分为classes、compute、concurrency、interfaces、string五类本文所述的切片基准即属于string类。1.1 eval-setup构造长源字符串原文档的 eval-setup 如下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) baml_src json.dumps(src) py_src repr(src) js_src json.dumps(src)它的作用可以拆成三步理解定义段落chunk一段约 340 字节的英文长文本全部为 ASCII 字符拼接源串src用###作为分隔符把 10 段chunk连接起来###.join([chunk] * 10)得到约 3.4KB 的长字符串。分隔符让源串在切片位置100~400 字符区间跨越多个段落确保切片操作不是在单个连续文本块内走捷径生成三种语言的字符串字面量json.dumpsBAML 与 TypeScript 使用 JSON 转义与reprPython 使用其原生字符串表示保证三份源码在语义上装载的是同一份数据。这里有个关键机制$$baml_src、$$py_src、$$js_src是模板占位符会在解析阶段被替换为 eval-setup 实际计算出的字符串字面量详见下文解析器如何展开模板。1.2 三端基准代码同一逻辑、三种写法BAML 端被测对象function main() - int { let s $$baml_src; let total 0; for (let i 0; i 100000; i 1) { let sub s.slice(100, 400); total sub.length(); }; return total; }Python 端对照基准s $$py_src t 0 for _ in range(100000): t len(s[100:400]) print(t)TypeScript 端对照基准const s $$js_src; let t 0; for(let i0;i100000;i) t s.substring(100, 400).length; console.log(t);三端逻辑完全同构对源串取[100, 400)闭开区间的子串把子串长度累加 100,000 次。由于源串是纯 ASCIIBAML 的 codepoint 索引与字节索引等价因此每次切片都恰好是 300 个字符三端最终输出一致30,000,000——这正是 speedtest 交叉验证正确性的基础见下文交叉验证一节。值得注意的细节差异维度BAMLPythonTypeScript切片 APIs.slice(100, 400)s[100:400]s.substring(100, 400)索引语义codepoint 索引支持负索引字符索引支持负索引与步长UTF-16 code unit 索引支持负索引越界行为自动 clamp 到边界自动 clamp 到边界自动 clamp 到边界输出方式函数返回值intprintconsole.log三端在主循环内都不做分配/打印把压力集中在切片 取长度这个最小热点上是一个刻意设计的微基准。二、解析器如何展开模板从 .md 到可执行源码工作负载文件里的$$baml_src并不是直接交给编译器的——它先由 loader.py 解析展开。核心逻辑集中在parse_workload_mdloader.py读取标题正则匹配文件首行# string::substring slice long 100k作为工作负载名称切分代码段用^##\s([\w-])\s*\n\w*\n(.*?) 正则把四个代码块按小写段名存入sections执行 eval-setupexec(setup, {__builtins__: __builtins__}, namespace)在受控命名空间中运行 eval-setup产出baml_src、py_src、js_src等变量模板替换对## BAML、## Python、## Typescript三段代码做安全替换。注意这里用的是自定义模板类_DDTemplateloader.py其delimiter $$——即只有$$var形式的占位符才会被替换单个$保持字面原样避免与 Python 字符串中的$冲突完整性校验BAML、Python、TypeScript 三段任一缺失即返回None由load_workloads打印WARN: skipping ... (parse failed)后跳过。展开后的完整 BAML 源码大致形如function main() - int { let s the quick brown fox ... matters now###the quick brown fox ... matters now###...; let total 0; for (let i 0; i 100000; i 1) { let sub s.slice(100, 400); total sub.length(); }; return total; }2.1 一份源码、两套消费方展开后的源码有两条去向Python 基准运行器由 runner.py 写入临时目录、分别用python3 -S、node、bun执行Python 加-S跳过 site 初始化以减少启动噪声Rust 基准套件由 export_baml.py 把所有工作负载的展开后BAML 源码以 JSON 数组[{name, category, baml}]输出到 stdout供crates/baml_tests复用为 CodSpeed 基准避免在 Rust 侧重复实现.md解析与$$模板逻辑。也就是说这个.md文件是单一事实来源Python 运行器和 Rust 基准套件共享同一份展开逻辑保证两端测的是完全相同的代码。三、BAML slice 的底层实现codepoint 索引与零拷贝要理解这个基准真正在测什么需要进入 BAML 字符串的运行时实现。切片入口在 bex_vm/src/package_baml/string.rsfn slice(string: BexStr, start: i64, end: i64) - BexStr { // Codepoint-indexed, not byte-indexed; a negative index counts from the end. let len string.char_count(); let start resolve_slice_bound(start, len); // An end resolving before start yields an empty string. let end resolve_slice_bound(end, len).max(start); string.substring_by_char(start, end) }源码注释直接揭示了三个语义要点按 Unicode codepoint 索引而非字节索引slice(100, 400)中的 100 与 400 是第 100 个字符到第 400 个字符不含与 Python 的字符索引对齐这与 TypeScript 的substringUTF-16 code unit 索引在含 emoji 等多字节字符时会产生差异负索引从末尾计数slice(-10, -1)取倒数第 10 到倒数第 1 个字符由resolve_slice_bound统一处理end早于start时返回空串end.max(start)保证结果不为负区间。3.1 BexStrV8 风格的 56 字节不可变字符串切片真正发生的地方在 bex_str/src/bex_str.rs。BexStr是 BAML 的运行时字符串类型注释明确写着 V8-inspired immutable string. O(1) clone. 56 bytes且带有一个编译期断言size_of::BexStr() 56bex_str.rs。它有四种形态变体存储方式典型用途Inline栈上[u8; 54]零堆分配≤54 字节的短字符串Flat堆上ArcFlatStr引用计数不可变缓冲区长字符串主体Slice指向某个Flat的(offset, len, char_count)视图零拷贝子串Concat延迟拼接树首次字节级访问时才扁平化字符串拼接3.2 substring 的三级策略短串内联、长串零拷贝、重切片折叠substring(start, end)bex_str.rs按结果长度分级处理空结果直接返回BexStr::empty()Inline len0短结果≤54 字节把字节复制进 Inline 缓冲区零堆分配。这正是本基准的关键——slice(100, 400)产生 300 字节结果超过 54 字节阈值走第三条路径长结果54 字节构造BexStr::Slice { parent, offset, len, char_count, hash }不复制任何字符串字节只是对原Flat缓冲区的一个视图。更重要的是Slice的depth-1 不变式depth-1 invariant当对一个已经是Slice的字符串再次切片时BexStr::Slice { parent, offset, .. }分支bex_str.rs不会产生Slice套Slice的嵌套结构而是直接指向同一个Flatparent 并累加 offset。这意味着在 100,000 次循环里每次s.slice(100, 400)都只是一次 O(1) 的视图构造若干次原子操作与指针运算没有任何字符串内容的拷贝或分配。3.3 codepoint 到字节的换算slice入口拿到的是 codepoint 索引而底层substring需要字节区间因此先经substring_by_charbex_str.rs换算pub fn substring_by_char(self, start: usize, end: usize) - BexStr { let char_len self.char_count(); let start start.min(char_len); let end end.min(char_len).max(start); if start end { return BexStr::empty(); } let bytes self.as_bytes(); let byte_start byte_offset_of_nth_codepoint(bytes, start); let byte_end byte_offset_of_nth_codepoint(bytes, end); self.substring(byte_start, byte_end) }char_count对Flat/Slice是 O(1)构造时已缓存bex_str.rs 附近边界自动 clamp 到[0, char_len]永不 panic。byte_offset_of_nth_codepoint负责把第 N 个 codepoint 换算成字节偏移——在本基准的纯 ASCII 源串上退化为恒等映射因此换算开销极小。3.4 基准的真正测点综合以上实现substring slice long 100k实际压测的是slice入口的 codepoint 换算与边界解析resolve_slice_boundsubstring_by_charBexStr::Slice视图的构造路径Inline 检查、Slice 折叠、bytecount::num_chars统计子串字符数对Slice视图调用length()的 O(1) 取长度100,000 次循环本身的迭代与累加开销。它并不测字符串内容的拷贝因为根本没有拷贝这是理解该基准结果曲线的关键前提。四、基准运行器从打包到计时的完整流水线工作负载文件本身不含任何计时逻辑全部由 runner.py 的cmd_run驱动其流程如下构建--build时执行cargo build --release -p baml_pack_host -p baml_clirunner.py探测运行器检测python3、bun、node是否可用--only-baml可跳过装载工作负载load_workloads()递归扫描workloads/**/*.md--filter按名称子串过滤打包调用baml-cli pack main --file workload.baml -o out把展开后的 BAML 源码打成独立可执行文件runner.py交叉验证先运行打包产物取末行输出作为expected再分别运行python3 -S、node、bun版本输出不一致则标记MISMATCHrunner.py计时默认自适应模式criterion 风格3 次预热丢弃 → 估算单次耗时 → 在--measurement-time默认 5 秒预算内取样本clamp 到[5, 100]次runner.py--runs N可切换固定次数模式输出与存档按类别分组打印| Benchmark | baml | python3 | baml/py | node | baml/node | bun | baml/bun |表格含中位数、标准差、比值并把结果保存到 baselines 目录供后续对比runner.py。4.1 CLI 入口与常用命令CLI 定义在 cli.py通过pyproject.toml的[project.scripts]pyproject.toml暴露speedtest命令支持run默认、compare、open、list、baselines五个子命令。只跑本文工作负载的示例# 先构建 release 版 baml-cli一次即可 cargo build --release -p baml_pack_host -p baml_cli # 只跑 substring slice long 100k跳过 python/node/bun固定 10 次采样 speedtest run --filter substring-slice-long-100k --only-baml --runs 10 # 与其他语言对照计时 speedtest run --filter substring-slice-long-100k # 与之前的基线对比 speedtest compare branch-a branch-b # 打开结果浏览器 UI speedtest open注意两点前提打包依赖仓库内的baml-cli--baml可指定路径自适应计时依赖python3/node/bun至少其一可用--profile还需要samply在 PATH 上并配合 profiling 构建cargo build --profile profiling -p baml_pack_host -p baml_cli使用runner.py。五、语义对照三种切片 API 的边界差异虽然三端代码表面等价但在一般输入下语义并不完全一致理解这些差异有助于正确解读基准结果BAMLslice(start, end)codepoint 索引、闭开区间、负索引从末尾计数、越界 clamp、end start得空串Pythons[start:end]同样闭开区间、负索引、越界 clamp但支持步长参数且索引针对 Unicode 码位TypeScriptsubstring(start, end)闭开区间、负索引会被当作 0substring(-3, 5)等价于substring(0, 5)、参数会自动交换substring(400, 100)与substring(100, 400)结果相同——这与 BAML/Python 的end start得空串截然不同且其索引是 UTF-16 code unit含 emoji代理对时位置会偏移。本工作负载刻意选用纯 ASCII 源串与start end的正常区间使三端语义完全重合从而让性能对比不受语义差异干扰这正是该工作负载设计上干净的地方。六、测试佐证与延伸阅读BexStr的字符计数语义有独立单元测试覆盖见 bex_str/src/tests.rs 中的char_count_ascii、char_count_multibyte、char_count_emoji、char_count_slice、char_count_concat等用例验证了 ASCII、多字节、emoji、Slice 视图与 Concat 拼接下字符数的正确性。若想继续研究字符串类基准的横向对比同目录下还有split-long-literal-1k.md同一长源串的split性能1,000 次迭代split-short-literal-100k.md 与 split-medium-literal-10k.md不同长度源串的split递进对照contains-medium-literal-100k.md 与 trim-padded-literal-100k.mdincludes与trim的 100k 迭代基准。从源码结构可以推断string类别整体采用同一段chunk文本、不同操作slice/split/trim/contains、不同迭代规模1k/10k/100k的矩阵式设计便于在同一数据形态下横向对比各字符串原语的运行时开销。七、小结string::substring slice long 100k是一个精心设计的字符串切片微基准它以约 3.4KB 的###拼接长串为数据源在 100,000 次循环中对[100, 400)区间反复切片并累加长度并用 Python 与 TypeScript 的同构实现做对照。透过 string.rs 与 bex_str.rs 的源码可以看到BAML 的slice按 codepoint 索引、支持负索引与边界 clamp并在底层通过 56 字节BexStr的Slice零拷贝视图实现——每次切片都是一次 O(1) 视图构造而非内容复制。理解这份工作负载等于同时掌握了 speedtest 基准框架的解析—打包—交叉验证—计时流水线以及 BAML 字符串类型设计的核心思想。赞分享编程语言AI Agent编译器CLI人工智能【免费下载链接】bamlThe programming language for agents项目地址https://gitcode.com/gh_mirrors/ba/baml点击查看免费下载相关推荐JavaScript字符串方法wtfjs中的slice与substringJavaScript字符串方法wtfjs中的slice与substring 在JavaScript开发中字符串处理是日常任务的重要组成部分。然而看似简单的文档教程BAML 字符串 split 长字面量基准从 speedtest Workload 定义到零拷贝底层实现BAML 字符串 split 长字面量基准从 speedtest Workload 定义到零拷贝底层实现 本指南以仓库中 split long literal编程语言AI Agent编译器CLI人工智能yq 切片操作符Slice完全指南数组与字符串的 .[start:end] 区间截取yq 切片操作符Slice完全指南数组与字符串的 . start:end 区间截取 导读 yq 是一款可移植的命令行 YAML/JSON/XML/CSV/开发工具CLI上一篇Bindu gRPC 适配器限制与取舍全解析流式、TLS、重连与连接池的现状与路线图下一篇ONNX GraphSurgeon 图 Layer API 实战用 Graph.layer() 与 Graph.register() 高效构建复杂 ONNX 模型创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考