dzmingli_vs_floating API

包 Luna-Flow/diff_bench/dzmingli_vs_floating 在相同的十进制输入上比较 DzmingLi/decimal@0.2.2 与 Luna-Flow/floating/decimal_gda@0.7.1,用精确的 BigInt 预言机(oracle)检验两者,并用 Mare Mark 测量它们。它是基准测试框架,而不是十进制库:每个函数在输入超出其约定时都会中止,而不是返回 Result,因为无效的测试夹具(fixture)本身就是框架的缺陷。

教程演示这些条目的用法,设计页推导精度约定与预言机。源码:src/dzmingli_vs_floating/。

本页示例都是黑盒测试:它们以 @dzmingli_vs_floating 和 moonbitlang/core/bigint 的形式导入包。

中立的十进制模型

DecimalValue

DecimalValue 是与实现无关的十进制数,所有夹具、预言机结果和观测值都会转换为它。

pub(all) struct DecimalValue {
  coefficient : @bigint.BigInt
  scale : Int
}

值 (c,s)(c, s) 表示 c⋅10−sc \cdot 10^{-s}。符号保存在系数中。本包产生的值满足 s≥0s \ge 0;负指数由 parse_decimal_value 乘入系数。许多不同的对表示同一个数,例如 (1200,2)(1200, 2) 与 (12,0)(12, 0);normalize 从中选出一个。

normalize

normalize 在标度保持非负的前提下去掉系数末尾的十进制零。

pub fn normalize(DecimalValue) -> DecimalValue

零变为 (0,0)(0, 0)。对其他值,结果满足 s=0s = 0 或系数不能被 1010 整除。这一形式对每个数都是唯一的,因此两个值相等当且仅当它们的规范化形式相等(证明见设计页)。代价:每去掉一个零做一次 BigInt 除法。

canonical_string

canonical_string 以不带指数的普通位置记数法输出规范化后的值。

pub fn canonical_string(DecimalValue) -> String

该字符串对负值带前导 -,当 ∣v∣<1|v| < 1 时带 0. 前缀和前导小数零,且没有末尾零。它在数上是单射,因此字符串相等即数值相等。该字符串是本包所有校验的比较边界。

parse_decimal_value

parse_decimal_value 把有限十进制字符串读入为规范化的 DecimalValue。

pub fn parse_decimal_value(String) -> DecimalValue

接受的语法:可选的前导符号、至多含一个 . 的数字串,以及可选的指数 e 或 E(指数可带符号)。数字逐位累加到 BigInt 中,因此解析器不依赖 BigInt::from_string。遇到空数字串、无数字的指数、NaN、Infinity 或任何其他字符时中止。指数保存在 Int 中。

OracleResult

OracleResult 保存一个规范化值及其规范字符串。

pub struct OracleResult {
  value : DecimalValue
  canonical : String
}

to_oracle_result

to_oracle_result 规范化一个值,并将其与规范字符串配对。

pub fn to_oracle_result(DecimalValue) -> OracleResult
test "neutral decimal model" {
  let v = @dzmingli_vs_floating.parse_decimal_value("-12.3400e1")
  inspect(v.coefficient, content="-1234")
  inspect(v.scale, content="1")
  let wide : @dzmingli_vs_floating.DecimalValue = { coefficient: 1200N, scale: 2 }
  inspect(@dzmingli_vs_floating.normalize(wide).scale, content="0")
  inspect(@dzmingli_vs_floating.canonical_string(v), content="-123.4")
  inspect(@dzmingli_vs_floating.to_oracle_result(wide).canonical, content="12")
}

精确算术

这些函数在 DecimalValue 上精确计算;没有精度,也没有舍入。结果是规范化的。

add

add 在把两个操作数对齐到较大标度后返回精确和。

pub fn add(DecimalValue, DecimalValue) -> DecimalValue

subtract

subtract 在同样对齐后返回精确差。

pub fn subtract(DecimalValue, DecimalValue) -> DecimalValue

multiply

multiply 返回精确积:系数相乘,标度相加。

pub fn multiply(DecimalValue, DecimalValue) -> DecimalValue

compare

compare 按左操作数小于、等于或大于右操作数返回 -1、0 或 1。

pub fn compare(DecimalValue, DecimalValue) -> Int
test "exact arithmetic" {
  let a = @dzmingli_vs_floating.parse_decimal_value("1.25")
  let b = @dzmingli_vs_floating.parse_decimal_value("-0.005")
  let show = @dzmingli_vs_floating.canonical_string
  inspect(show(@dzmingli_vs_floating.add(a, b)), content="1.245")
  inspect(show(@dzmingli_vs_floating.subtract(a, b)), content="1.255")
  inspect(show(@dzmingli_vs_floating.multiply(a, b)), content="-0.00625")
  inspect(@dzmingli_vs_floating.compare(a, b), content="1")
}

参考预言机

预言机只用 BigInt 算术精确计算每个被测运算的期望结果。其语义等同于精度足够且向零舍入的 GDA 上下文;设计页逐条给出规则。

oracle_multiply

oracle_multiply 返回精确积;它就是以预言机名义出现的 multiply。

pub fn oracle_multiply(DecimalValue, DecimalValue) -> DecimalValue

oracle_divide

oracle_divide 在商具有有限十进制展开时返回精确商。

pub fn oracle_divide(DecimalValue, DecimalValue) -> DecimalValue

除数为零时,或约分后分母含有 22 和 55 以外的素因子时(“repeating decimal in exact decimal oracle”)中止。代价:一次 BigInt 最大公约数,加上结果每个小数位一次乘以 1010。

oracle_operation

oracle_operation 计算一个二元或一元 Operation,返回规范化结果。

pub fn oracle_operation(Operation, DecimalValue, DecimalValue) -> OracleResult

它等于第三操作数为零的 oracle_operation3,因此除非加数为零,否则它对 Fma 给出错误答案。

oracle_operation3

oracle_operation3 计算任意 Operation;第三操作数是 Fma 的加数,对其他运算被忽略。

pub fn oracle_operation3(Operation, DecimalValue, DecimalValue, DecimalValue) -> OracleResult
运算期望值
Add, Subtract, Multiply精确结果
Divide精确的有限商;否则中止
DivideInteger向零截断的商
Remaindera−b⋅trunc⁡(a/b)a - b \cdot \operatorname{trunc}(a/b),符号与 aa 相同
Power通过重复乘法计算 ana^n;nn 必须是非负整数
Fma精确的 a⋅b+ca \cdot b + c
SquareRoot标度为偶数的完全平方数的精确平方根;否则中止
Plus, Minus, Absaa, −a-a, ∣a∣\lvert a \rvert
Quantizeaa 向零截断到 bb 的标度
Rescaleaa 向零截断到标度 −b-b;bb 必须是整数
ScaleBa⋅10ba \cdot 10^{b};bb 必须是整数
Reduce规范化的 aa
ToIntegralExact, ToIntegralValueaa 向零截断为整数
Compare以十进制数表示的 -1、0 或 1
Parse, Formataa 不变
test "reference oracle" {
  let a = @dzmingli_vs_floating.parse_decimal_value("1.25")
  let b = @dzmingli_vs_floating.parse_decimal_value("8")
  let show = @dzmingli_vs_floating.canonical_string
  inspect(show(@dzmingli_vs_floating.oracle_divide(a, b)), content="0.15625")
  let minus_seven_and_a_half = @dzmingli_vs_floating.parse_decimal_value("-7.5")
  let two = @dzmingli_vs_floating.parse_decimal_value("2")
  let r = @dzmingli_vs_floating.oracle_operation(Remainder, minus_seven_and_a_half, two)
  inspect(r.canonical, content="-1.5")
  let fma = @dzmingli_vs_floating.oracle_operation3(Fma, a, b, two)
  inspect(fma.canonical, content="12")
}

运算与套件

Operation

Operation 列出基准中的运算族。

pub(all) enum Operation {
  Add
  Subtract
  Multiply
  Divide
  DivideInteger
  Remainder
  Power
  Fma
  SquareRoot
  Plus
  Minus
  Abs
  Quantize
  Rescale
  ScaleB
  Reduce
  ToIntegralExact
  ToIntegralValue
  Compare
  Parse
  Format
}

Parse 与 Format 是恒等路径:两个适配器都返回预先准备好的左操作数,因此它们什么也不测量,可执行程序也不运行它们。

operation_name

operation_name 返回 JSONL 记录和指纹中使用的稳定蛇形命名,例如 "divide_integer" 或 "sqrt"。

pub fn operation_name(Operation) -> String

OperandShape

OperandShape 为生成的用例打标签。

pub(all) enum OperandShape {
  Small
  Large
  Boundary
  Cancellation
  Repeating
}

该标签会被序列化进用例及其指纹;generate_cases 按索引循环分配它,它不影响操作数的生成方式。

TimingScope

TimingScope 选择一次计时调用包含哪些内容。

pub(all) enum TimingScope {
  OperationOnly
  FullPath
}

OperationOnly 对计时前已准备好的操作数计时公开运算。FullPath 还会在准备好的上下文中解析两个规范操作数字符串(对 Fma 还有加数)。

timing_scope_name

timing_scope_name 返回 "arithmetic_only" 或 "full_path"。

pub fn timing_scope_name(TimingScope) -> String

Suite

Suite 是基准套件的描述性元数据。

pub struct Suite {
  name : String
  operations : Array[Operation]
  scales : Array[Int]
  timing_scope : TimingScope
  warmup : Int
  samples : Int
}

default_suite

default_suite 返回名为 "dzmingli-vs-floating-gda" 的套件:全部 21 种运算、标度 [0, 2, 6, 18, 28]、OperationOnly、5 次预热和 20 个样本。Mare Mark 运行器不读取它;它们的协议来自 default_benchmark_protocol。

pub fn default_suite() -> Suite

suite_case_count

suite_case_count 返回 运算数 × 标度数 × 每种运算的用例数。

pub fn suite_case_count(Suite, Int) -> Int
test "operations and suites" {
  inspect(@dzmingli_vs_floating.operation_name(SquareRoot), content="sqrt")
  inspect(@dzmingli_vs_floating.timing_scope_name(FullPath), content="full_path")
  let suite = @dzmingli_vs_floating.default_suite()
  inspect(@dzmingli_vs_floating.suite_case_count(suite, 4), content="420")
}

确定性用例

generate_decimal

generate_decimal(seed, digits, scale, negative) 构造一个恰有 digits 位系数数字的十进制字符串,其中 scale 位在小数点之后。

pub fn generate_decimal(Int, Int, Int, Bool) -> String

第 ii 位数字为 1+((seed+7i) mod 9)1 + ((\mathit{seed} + 7i) \bmod 9),因此每位都在 1..91..9 中:系数恰有 digits 位且没有末尾零。当 scale >= digits 时字符串以 0. 和前导零开头。若 digits <= 0 或 scale < 0 则中止。

BenchmarkCase

BenchmarkCase 是一个生成的用例;其字符串就是序列化边界。

pub struct BenchmarkCase {
  id : String
  operation : Operation
  shape : OperandShape
  left : String
  right : String
  scale : Int
}

generate_cases

generate_cases(seed, count, operation) 返回 count 个可复现的用例,不读取时钟或全局随机状态。

pub fn generate_cases(Int, Int, Operation) -> Array[BenchmarkCase]

用例 ii 的两个操作数都有 1+((seed+11i) mod 24)1 + ((\mathit{seed} + 11i) \bmod 24) 位数字,标度为 (seed+5i) mod 9(\mathit{seed} + 5i) \bmod 9。除数没有经过筛选,因此 Divide 用例可能不能整除为有限小数;请把它们交给适配器,而不是 oracle_divide。

serialize_case

serialize_case 用换行连接版本标签 decimal-neutral-v1、id、运算名、形状名、两个操作数和标度。

pub fn serialize_case(BenchmarkCase) -> String

fingerprint_case

fingerprint_case 用 Mare Mark 的 stable_fingerprint 对 serialize_case 求哈希,返回 sha256: 字符串。

pub fn fingerprint_case(BenchmarkCase) -> String

expand_digit_scales

expand_digit_scales(sizes, n) 把每个系数规模重复 n 次,使 Mare Mark 为每个规模创建 n 个独立数据集。

pub fn expand_digit_scales(Array[Int], Int) -> Array[Int]
test "deterministic cases" {
  inspect(@dzmingli_vs_floating.generate_decimal(3, 6, 2, true), content="-4297.53")
  let cases = @dzmingli_vs_floating.generate_cases(7, 3, Add)
  inspect(cases[1].left, content="-9753186429753186.429")
  inspect(
    @dzmingli_vs_floating.fingerprint_case(cases[0]) ==
    @dzmingli_vs_floating.fingerprint_case(@dzmingli_vs_floating.generate_cases(7, 3, Add)[0]),
    content="true",
  )
  assert_eq(@dzmingli_vs_floating.expand_digit_scales([4, 16], 2), [4, 4, 16, 16])
}

夹具与适配器

working_precision

working_precision 返回一个有效数字位数,它足以容纳给定操作数上某运算的精确结果。

pub fn working_precision(Operation, DecimalValue, DecimalValue) -> Int

记 dℓ,drd_\ell, d_r 为两个系数的位数,sℓ,srs_\ell, s_r 为标度:

运算结果
Add, Subtractmax⁡(dℓ,dr)+∣sℓ−sr∣+2\max(d_\ell, d_r) + \lvert s_\ell - s_r \rvert + 2
Multiply, Fma, Power, Divide, DivideInteger, Remainderdℓ+dr+2d_\ell + d_r + 2
SquareRoot 与一元运算dℓ+2d_\ell + 2
Compare, Parse, Formatmax⁡(dℓ,dr)+1\max(d_\ell, d_r) + 1

prepare_fixture3 再加两位保护位,并对 Power 和 Fma 另作处理;设计页证明了每个界覆盖哪些操作数类别。

DecimalFixture

DecimalFixture 保存两个实现在计时前构造好的操作数与上下文。

pub struct DecimalFixture {
  left_text : String
  right_text : String
  third_text : String
  operation : Operation
  dz_left : @decimal.Decimal
  dz_right : @decimal.Decimal
  dz_third : @decimal.Decimal
  dz_context : @decimal.Context
  gda_left : @decimal_gda.Decimal
  gda_right : @decimal_gda.Decimal
  gda_third : @decimal_gda.Decimal
  gda_context : @decimal_gda.GdaContext
}

@decimal 指 DzmingLi/decimal,@decimal_gda 指 Luna-Flow/floating/decimal_gda。*_text 字段是 FullPath 会重新解析的规范字符串。

prepare_fixture3

prepare_fixture3(operation, left, right, third) 选定精度 pp,以精度 pp 和向零舍入构造两个上下文,并把三个规范字符串分别解析进两个库。

pub fn prepare_fixture3(Operation, DecimalValue, DecimalValue, DecimalValue) -> DecimalFixture

pp 对 Power 为 2dℓ+42 d_\ell + 4,对 Fma 为 dℓ+dr+dt+∣sℓ+sr−st∣+4d_\ell + d_r + d_t + \lvert s_\ell + s_r - s_t \rvert + 4;其余为 working_precision(...) + 2。若 DzmingLi 报告转换语法错误则中止。

prepare_fixture

prepare_fixture(operation, left, right) 是第三操作数为零的 prepare_fixture3。

pub fn prepare_fixture(Operation, DecimalValue, DecimalValue) -> DecimalFixture

dz_from_neutral

dz_from_neutral 在 Context::exact() 下把值转换为 DzmingLi 十进制数。

pub fn dz_from_neutral(DecimalValue) -> @decimal.Decimal

gda_from_neutral

gda_from_neutral(value, precision) 在给定精度、向零舍入的上下文中把规范字符串解析为 GDA 十进制数。

pub fn gda_from_neutral(DecimalValue, Int) -> @decimal_gda.Decimal

DecimalObservation

DecimalObservation 是一次适配器调用的原始结果。

pub(all) enum DecimalObservation {
  Dz(@decimal.Decimal)
  Gda(@decimal_gda.GdaOutcome[@decimal_gda.Decimal])
}

GDA 变体保留完整的结果,包括标志和下一个上下文;校验只读取其中的值。

run_dz

run_dz 用 DzmingLi 在预先准备的操作数上执行夹具的运算。这就是 OperationOnly 的计时主体。

pub fn run_dz(DecimalFixture) -> DecimalObservation

run_dz_full

run_dz_full 先在 dz_context 下解析夹具的三个字符串,再执行运算。这就是 FullPath 的计时主体。

pub fn run_dz_full(DecimalFixture) -> DecimalObservation

run_gda

run_gda 用 floating GDA 在预先准备的操作数上执行夹具的运算。

pub fn run_gda(DecimalFixture) -> DecimalObservation

run_gda_full

run_gda_full 先在 gda_context 下解析夹具的三个字符串,再执行运算。

pub fn run_gda_full(DecimalFixture) -> DecimalObservation

canonical_observation

canonical_observation 把观测值转换为规范化的 DecimalValue。

pub fn canonical_observation(DecimalObservation) -> DecimalValue

DzmingLi 的结果经由 to_sci_string,GDA 的结果经由 to_string,二者再经由 parse_decimal_value。指数、末尾零和状态标志都被丢弃。结果为 NaN 或无穷时中止。

test "fixtures and adapters" {
  let a = @dzmingli_vs_floating.parse_decimal_value("1.25")
  let b = @dzmingli_vs_floating.parse_decimal_value("8")
  inspect(@dzmingli_vs_floating.working_precision(Divide, a, b), content="6")
  let fixture = @dzmingli_vs_floating.prepare_fixture(Divide, a, b)
  inspect(fixture.gda_context.precision(), content="8")
  let show = (o : @dzmingli_vs_floating.DecimalObservation) => {
    @dzmingli_vs_floating.canonical_string(
      @dzmingli_vs_floating.canonical_observation(o),
    )
  }
  inspect(show(@dzmingli_vs_floating.run_dz(fixture)), content="0.15625")
  inspect(show(@dzmingli_vs_floating.run_gda(fixture)), content="0.15625")
  inspect(show(@dzmingli_vs_floating.run_gda_full(fixture)), content="0.15625")
}

Mare Mark 集成

MareDecimalInput

MareDecimalInput 是一个 Mare Mark 数据集实例化后的输入。

pub(all) struct MareDecimalInput {
  left : DecimalValue
  right : DecimalValue
  third : DecimalValue
  operation : Operation
  timing_scope : TimingScope
  digits : Int
  left_scale : Int
  right_scale : Int
}

PerformanceResult

PerformanceResult 汇总一种运算、一个计时范围和一个系数规模的结果。

pub(all) struct PerformanceResult {
  operation : Operation
  timing_scope : TimingScope
  digits : Int
  correctness_valid : Bool
  dz_median_us : Double
  gda_median_us : Double
  gda_relative_delta_pct : Double
  dz_speedup_vs_gda : Double
  decision : String
  samples : Int
}

当该规模下两个实现的每次校验都通过时,correctness_valid 为真。此时中位数来自配对的确认性样本,dz_speedup_vs_gda 是 GDA 中位数除以 DzmingLi 中位数(大于 11 表示 DzmingLi 更快),gda_relative_delta_pct 是配对差的中位数相对于 DzmingLi 中位数的比例,decision 为 "gda_faster"、"dzmingli_faster"、"equivalent"、"invalid" 或 "unknown"。否则中位数取各实现自己的有效样本,加速比与差值为 0.0,decision 为 "invalid_correctness"。

PerformanceResult::to_json

PerformanceResult::to_json 输出一条 "comparison" JSONL 记录,标注 Mare Mark 制品版本 mmka_1。

pub fn PerformanceResult::to_json(Self) -> String

MareBenchmarkReport

MareBenchmarkReport 汇集一次运行的结果、原始 JSONL 和校验计数。

pub(all) struct MareBenchmarkReport {
  results : Array[PerformanceResult]
  jsonl : String
  validation_count : Int
  failed_count : Int
}

validation_count 统计每一次预言机校验,无论通过与否。

smoke_protocol

smoke_protocol 返回用于测试的简短 Mare Mark 协议:2 次预热、0.5 ms 的校准批次和 3 次确认性重复。

pub fn smoke_protocol() -> @model.RunProtocol

default_benchmark_protocol

default_benchmark_protocol 返回已发布运行所用的协议:5 次预热、5 ms 校准批次、种子为 0xDEC1A1 的平衡分块顺序、只报告而不剔除离群值、校验每个数据集以及 20 次确认性重复。

pub fn default_benchmark_protocol() -> @model.RunProtocol

benchmark_environment

benchmark_environment 根据 MARE_* 环境变量构造记录在 JSONL 中的环境快照。

pub fn benchmark_environment() -> @model.EnvironmentSnapshot

它读取 MARE_TARGET、MARE_COMPILER、MARE_BUILD_MODE、MARE_CPU、MARE_DEVICE、MARE_FREQUENCY_POLICY、MARE_OS、MARE_EXECUTION_CONTEXT 和 MARE_REPOSITORY_STATE。缺失或为空的变量会变成 unknown、unspecified 或其他固定默认值;不会从主机探测任何信息。

run_mare_benchmark

run_mare_benchmark(operations, scope, digit_scales, protocol, seed) 为每种运算运行一次 Mare Mark 实验,并返回合并后的报告。

pub async fn run_mare_benchmark(Array[Operation], TimingScope, Array[Int], @model.RunProtocol, UInt64) -> MareBenchmarkReport

digit_scales 的每一项是一个数据集;用 expand_digit_scales 重复规模。每个数据集在计时前都先与预言机核对。该函数不会因校验失败而中止;调用者应检查 failed_count。它需要异步运行时,因此在 native 和 js 目标上运行。

async test "mare mark smoke run" {
  let report = @dzmingli_vs_floating.run_mare_benchmark(
    [Add],
    OperationOnly,
    [4],
    @dzmingli_vs_floating.smoke_protocol(),
    42UL,
  )
  inspect(report.failed_count, content="0")
  inspect(report.validation_count, content="2")
  inspect(report.results[0].correctness_valid, content="true")
  inspect(report.results[0].to_json().contains("\"type\":\"comparison\""), content="true")
}

报告

mare_performance_report_document

mare_performance_report_document(results, target, run_id) 构建 Mare Mark Plot IR 文档:每种运算与计时范围一张延迟图,每个计时范围一张 DzmingLi 对 GDA 的加速比图。

pub fn mare_performance_report_document(Array[PerformanceResult], String, String, validation_count? : Int, failed_count? : Int) -> @ir_model.PlotDocument

correctness_valid 为假的规模保留 GDA 延迟点,但不提供 DzmingLi 点和加速比点。语料汇总为:总数 validation_count、通过 validation_count - failed_count、失败 failed_count。

mare_performance_report_html

mare_performance_report_html 把同一文档渲染为自包含的 HTML 页面。

pub fn mare_performance_report_html(Array[PerformanceResult], String, String, validation_count? : Int, failed_count? : Int) -> String
test "performance report" {
  let result : @dzmingli_vs_floating.PerformanceResult = {
    operation: Add,
    timing_scope: OperationOnly,
    digits: 16,
    correctness_valid: true,
    dz_median_us: 1.0,
    gda_median_us: 2.0,
    gda_relative_delta_pct: 100.0,
    dz_speedup_vs_gda: 2.0,
    decision: "dzmingli_faster",
    samples: 60,
  }
  let html = @dzmingli_vs_floating.mare_performance_report_html(
    [result],
    "native",
    "example",
    validation_count=2,
  )
  inspect(html.contains("Total 2 · passed 2 · failed 0"), content="true")
}