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 オラクルで検査し、Mare Mark で計測します。これは十進ライブラリではなくベンチマーク用のハーネスです。無効なフィクスチャはハーネス自身のバグであるため、どの関数も契約外の入力に対しては Result を返さずに中断します。
チュートリアルでは各項目の使い方を示し、設計ページでは精度契約とオラクルを導出します。ソース:src/dzmingli_vs_floating/。
このページの例はブラックボックステストで、パッケージを @dzmingli_vs_floating として、また moonbitlang/core/bigint をインポートします。
中立な十進モデル
DecimalValue
DecimalValue は実装に依存しない十進数で、すべてのフィクスチャ、オラクルの結果、観測値はこれに変換されます。
pub(all) struct DecimalValue {
coefficient : @bigint.BigInt
scale : Int
}
値 は を表します。符号は係数が持ちます。このパッケージが生成する値は を満たし、負の指数は parse_decimal_value が係数に掛け込みます。 と のように多くの組が同じ数を表すため、normalize がその中の一つを選びます。
normalize
normalize は、スケールが非負である範囲で係数の末尾の 0 を取り除きます。
pub fn normalize(DecimalValue) -> DecimalValue
0 は になります。それ以外の値では、結果は であるか、係数が で割り切れません。この形は数ごとに一意なので、二つの値が等しいのは正規化形が等しいときに限ります(証明は設計ページ)。コスト:取り除く 0 一つにつき BigInt の除算 1 回。
canonical_string
canonical_string は正規化した値を指数なしの通常の位取り記法で表します。
pub fn canonical_string(DecimalValue) -> String
この文字列は負の値では先頭に - を持ち、 のときは 0. の接頭辞と小数部の先頭の 0 を持ち、末尾の 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 算術だけで厳密に計算します。その意味論は十分な精度と 0 方向への丸めを持つ GDA コンテキストのものです。各規則は設計ページにあります。
oracle_multiply
oracle_multiply は厳密な積を返します。オラクル用の名前を付けた multiply です。
pub fn oracle_multiply(DecimalValue, DecimalValue) -> DecimalValue
oracle_divide
oracle_divide は商が有限の十進展開を持つとき、厳密な商を返します。
pub fn oracle_divide(DecimalValue, DecimalValue) -> DecimalValue
除数が 0 のとき、また約分後の分母が と 以外の素因数を持つとき(“repeating decimal in exact decimal oracle”)に中断します。コスト:BigInt の GCD 1 回と、結果の小数桁ごとに 倍 1 回。
oracle_operation
oracle_operation は二項または単項の Operation を評価し、正規化された結果を返します。
pub fn oracle_operation(Operation, DecimalValue, DecimalValue) -> OracleResult
第三オペランドを 0 とした oracle_operation3 と同じなので、加数が 0 でない限り Fma に対しては誤った答えを返します。
oracle_operation3
oracle_operation3 は任意の Operation を評価します。第三オペランドは Fma の加数で、それ以外では無視されます。
pub fn oracle_operation3(Operation, DecimalValue, DecimalValue, DecimalValue) -> OracleResult
| 演算 | 期待値 |
|---|---|
Add, Subtract, Multiply | 厳密な結果 |
Divide | 厳密な有限の商。そうでなければ中断 |
DivideInteger | 0 方向に切り捨てた商 |
Remainder | 、符号は と同じ |
Power | を繰り返し乗算で計算。 は非負整数でなければならない |
Fma | 厳密な |
SquareRoot | スケールが偶数の完全平方数の厳密な平方根。そうでなければ中断 |
Plus, Minus, Abs | , , |
Quantize | を のスケールまで 0 方向に切り捨て |
Rescale | をスケール まで 0 方向に切り捨て。 は整数でなければならない |
ScaleB | 。 は整数でなければならない |
Reduce | 正規化した |
ToIntegralExact, ToIntegralValue | を 0 方向に整数へ切り捨て |
Compare | 十進数としての -1、0、1 |
Parse, Format | をそのまま |
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
桁目の数字は なので、すべての桁は に入ります。係数はちょうど digits 桁で末尾に 0 を持ちません。scale >= digits のとき文字列は 0. と先頭の 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]
ケース の両オペランドは 桁、スケール です。除数は選別されていないので 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
を係数の桁数、 をスケールとすると:
| 演算 | 結果 |
|---|---|
Add, Subtract | |
Multiply, Fma, Power, Divide, DivideInteger, Remainder | |
SquareRoot と単項演算 | |
Compare, Parse, Format |
prepare_fixture3 はガード桁を 2 桁加え、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) は精度 を選び、精度 と 0 方向への丸めで両方のコンテキストを作り、三つの正準文字列を両ライブラリに解析させます。
pub fn prepare_fixture3(Operation, DecimalValue, DecimalValue, DecimalValue) -> DecimalFixture
は Power では 、Fma では 、それ以外では working_precision(...) + 2 です。DzmingLi が変換構文エラーを報告すると中断します。
prepare_fixture
prepare_fixture(operation, left, right) は第三オペランドを 0 とした 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) は、指定精度と 0 方向への丸めを持つコンテキストで正準文字列を 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 を通ります。指数、末尾の 0、ステータスフラグは捨てられます。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 の中央値で割った値( を超えれば 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 は Mare Mark の成果物バージョン mmka_1 を付けた "comparison" JSONL レコードを出力します。
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")
}