basic arithmetic benchmarks added
.NET Test / .NET tests (push) Successful in 1m9s

This commit is contained in:
2026-09-14 00:04:39 +04:00
parent e05e1e902a
commit cb2524980d
6 changed files with 186 additions and 9 deletions
+5
View File
@@ -49,6 +49,11 @@ jobs:
--report-xunit-trx --coverlet --coverlet-output-format cobertura --report-xunit-trx --coverlet --coverlet-output-format cobertura
--coverlet-include "[Just.PreciseMath]*" --coverlet-include "[Just.PreciseMath]*"
- name: Benchmark smoke test
run: >-
dotnet run --project 2-benchmarks/Just.PreciseMath.Benchmarks
--configuration Release --no-build -- --job Dry --filter '*'
- name: Upload test and coverage results - name: Upload test and coverage results
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v7
if: ${{ always() }} if: ${{ always() }}
@@ -0,0 +1,107 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
namespace Just.PreciseMath.Benchmarks;
/// <summary>Compares same-type scalar arithmetic on ordinary finite operands.</summary>
[MemoryDiagnoser]
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
[CategoriesColumn]
public class ArithmeticBenchmarks
{
private double _doubleLeft;
private double _doubleRight;
private decimal _decimalLeft;
private decimal _decimalRight;
private DoubleDouble _doubleDoubleLeft;
private DoubleDouble _doubleDoubleRight;
/// <summary>One binary-exact input and one decimal input with a binary residual.</summary>
[Params(1.25, 1.1)]
public double Left { get; set; }
[GlobalSetup]
public void Setup()
{
// Conversion and construction are deliberately outside the timed methods.
// Decimal and DoubleDouble represent the same decimal input; double rounds it.
_doubleLeft = Left;
_doubleRight = 0.75;
_decimalLeft = (decimal)Left;
_decimalRight = 0.75m;
_doubleDoubleLeft = (DoubleDouble)_decimalLeft;
_doubleDoubleRight = (DoubleDouble)_decimalRight;
}
[Benchmark(Baseline = true), BenchmarkCategory("Addition")]
public double DoubleAdd()
{
return _doubleLeft + _doubleRight;
}
[Benchmark, BenchmarkCategory("Addition")]
public decimal DecimalAdd()
{
return _decimalLeft + _decimalRight;
}
[Benchmark, BenchmarkCategory("Addition")]
public DoubleDouble DoubleDoubleAdd()
{
return _doubleDoubleLeft + _doubleDoubleRight;
}
[Benchmark(Baseline = true), BenchmarkCategory("Subtraction")]
public double DoubleSubtract()
{
return _doubleLeft - _doubleRight;
}
[Benchmark, BenchmarkCategory("Subtraction")]
public decimal DecimalSubtract()
{
return _decimalLeft - _decimalRight;
}
[Benchmark, BenchmarkCategory("Subtraction")]
public DoubleDouble DoubleDoubleSubtract()
{
return _doubleDoubleLeft - _doubleDoubleRight;
}
[Benchmark(Baseline = true), BenchmarkCategory("Multiplication")]
public double DoubleMultiply()
{
return _doubleLeft * _doubleRight;
}
[Benchmark, BenchmarkCategory("Multiplication")]
public decimal DecimalMultiply()
{
return _decimalLeft * _decimalRight;
}
[Benchmark, BenchmarkCategory("Multiplication")]
public DoubleDouble DoubleDoubleMultiply()
{
return _doubleDoubleLeft * _doubleDoubleRight;
}
[Benchmark(Baseline = true), BenchmarkCategory("Division")]
public double DoubleDivide()
{
return _doubleLeft / _doubleRight;
}
[Benchmark, BenchmarkCategory("Division")]
public decimal DecimalDivide()
{
return _decimalLeft / _decimalRight;
}
[Benchmark, BenchmarkCategory("Division")]
public DoubleDouble DoubleDoubleDivide()
{
return _doubleDoubleLeft / _doubleDoubleRight;
}
}
@@ -2,9 +2,8 @@
<PropertyGroup> <PropertyGroup>
<AssemblyName>Just.PreciseMath.Benchmarks</AssemblyName> <AssemblyName>Just.PreciseMath.Benchmarks</AssemblyName>
<RootNamespace>Just.PreciseMath.Benchmarks</RootNamespace> <RootNamespace>Just.PreciseMath.Benchmarks</RootNamespace>
<Description>BenchmarkDotNet scaffold for measuring Just.PreciseMath arithmetic and mathematical functions.</Description> <Description>BenchmarkDotNet comparisons of DoubleDouble, decimal, and double arithmetic.</Description>
<!-- Source-free scaffold: change to Exe when adding the benchmark entry point. --> <OutputType>Exe</OutputType>
<OutputType>Library</OutputType>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@@ -0,0 +1,31 @@
using BenchmarkDotNet.Reports;
using BenchmarkDotNet.Running;
namespace Just.PreciseMath.Benchmarks;
internal static class Program
{
private static int Main(string[] args)
{
BenchmarkSwitcher switcher = BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly);
// Only these standalone discovery/help forms intentionally produce no reports.
if (args is ["--list", "flat" or "tree"] or ["--help"] or ["-h"])
{
switcher.Run(args);
return 0;
}
Summary[] summaries = switcher.Run(args.Length == 0 ? ["--filter", "*"] : args).ToArray();
// BenchmarkDotNet can return normally after validation, build, or execution
// failures. Do not let CI mistake an empty or failed run for a passing smoke test.
if (summaries.Length == 0 || summaries.Any(summary =>
summary.HasCriticalValidationErrors || summary.Reports.Length == 0 ||
summary.Reports.Any(report => !report.Success || report.ResultStatistics is null)))
{
Console.Error.WriteLine("Benchmark run failed or produced no measurements.");
return 1;
}
return 0;
}
}
+2 -2
View File
@@ -12,8 +12,8 @@ Do not claim API completeness or accuracy beyond tested contracts.
- `0-source/Just.PreciseMath/`: library implementation and package metadata. - `0-source/Just.PreciseMath/`: library implementation and package metadata.
- `1-tests/Just.PreciseMath.Tests/`: xUnit v3 tests, Shouldly assertions, and - `1-tests/Just.PreciseMath.Tests/`: xUnit v3 tests, Shouldly assertions, and
Microsoft.Testing.Platform (MTP) with `coverlet.MTP` coverage. Microsoft.Testing.Platform (MTP) with `coverlet.MTP` coverage.
- `2-benchmarks/Just.PreciseMath.Benchmarks/`: source-free BenchmarkDotNet library - `2-benchmarks/Just.PreciseMath.Benchmarks/`: executable BenchmarkDotNet arithmetic
scaffold, not yet a runnable benchmark suite. comparisons; use the Dry job for smoke validation, not performance conclusions.
- Root `Directory.Build.props` holds shared settings. Each numbered directory's - Root `Directory.Build.props` holds shared settings. Each numbered directory's
props explicitly imports it; preserve this import chain. props explicitly imports it; preserve this import chain.
- `review-legacy/`: optional local reference material, excluded by `.gitignore`. - `review-legacy/`: optional local reference material, excluded by `.gitignore`.
+39 -4
View File
@@ -42,7 +42,7 @@ rounded 106-bit results**. The deterministic rational-oracle tests check a
conservative error bound of `2^-100` relative plus one minimum binary64 subnormal, conservative error bound of `2^-100` relative plus one minimum binary64 subnormal,
with exact component checks for selected representable cases. Near underflow, with exact component checks for selected representable cases. Near underflow,
extended precision necessarily decreases; overflow produces infinity. Performance extended precision necessarily decreases; overflow produces infinity. Performance
has not been benchmarked, including the allocating exponent-boundary path. of the allocating exponent-boundary path is not covered by the basic benchmarks.
## Conversions and formatting ## Conversions and formatting
@@ -133,8 +133,8 @@ bool success = DoubleDouble.TryParse("1.25e-2".AsSpan(), CultureInfo.InvariantCu
The planned `PreciseMath` static class and its `Abs`, `Sqrt`, `Pow`, `Exp`, and The planned `PreciseMath` static class and its `Abs`, `Sqrt`, `Pow`, `Exp`, and
`Log` functions are not implemented. Generic-math interfaces beyond `ISignedNumber`, `Log` functions are not implemented. Generic-math interfaces beyond `ISignedNumber`,
additional text formats/general round-trip formatting, and performance benchmarks additional text formats/general round-trip formatting, and broader performance
remain deferred. benchmarks remain deferred.
Replacing allocating arithmetic boundary fallbacks is also deferred; the current Replacing allocating arithmetic boundary fallbacks is also deferred; the current
`BigInteger` paths remain in place. That optimization does not require removing `BigInteger` paths remain in place. That optimization does not require removing
`BigInteger` from conversions, parsing, formatting, or independent test oracles. `BigInteger` from conversions, parsing, formatting, or independent test oracles.
@@ -154,11 +154,46 @@ dotnet format Just.PreciseMath.slnx --verify-no-changes --no-restore
Test results and coverage reports are available in the `test-results` artifact Test results and coverage reports are available in the `test-results` artifact
on CI workflow runs. on CI workflow runs.
## Benchmarks
The BenchmarkDotNet suite compares same-type `+`, `-`, `*`, and `/` operations for
`DoubleDouble`, `decimal`, and `double`: 12 methods with two input cases each.
Each operation/input group uses `double` as its baseline and reports allocations.
Operands are stored in fields and results are returned to the harness; conversions
and construction happen in setup, outside the timed methods.
The left inputs are `1.25` (binary-exact) and `1.1` (a nonzero low component in
`DoubleDouble`); the right input is `0.75`. Decimal and double-double inputs originate
from the same decimal values, while `double` rounds to binary64. These types have
different precision and range contracts: this is a cost comparison, not an accuracy
test. Exceptional values, exponent-boundary fallbacks, mixed-type operators,
conversions, parsing, and formatting are not benchmarked yet.
After the Release build above, run from the repository root:
```sh
# Discover benchmark methods without running them.
dotnet run --project 2-benchmarks/Just.PreciseMath.Benchmarks -c Release --no-build -- --list flat
# Quick execution check (also run in CI); not useful for timing comparisons.
dotnet run --project 2-benchmarks/Just.PreciseMath.Benchmarks -c Release --no-build -- --job Dry --filter '*'
# Full measurement run; optionally select an operation with --anyCategories Addition.
dotnet run --project 2-benchmarks/Just.PreciseMath.Benchmarks -c Release --no-build -- --filter '*'
```
Reports are written under the ignored `BenchmarkDotNet.Artifacts/` directory.
Empty selections and failed benchmark runs return a nonzero exit code. CI only
checks execution, with no performance gate. Use full runs on an idle, controlled
machine for comparisons; a scalar `double` operation may approach harness overhead,
so inspect BenchmarkDotNet warnings before interpreting ratios. Do not compare
coverage-instrumented runs or treat Dry-job timings as performance measurements.
## Project structure ## Project structure
- `0-source/Just.PreciseMath/`: library implementation. - `0-source/Just.PreciseMath/`: library implementation.
- `1-tests/Just.PreciseMath.Tests/`: unit tests. - `1-tests/Just.PreciseMath.Tests/`: unit tests.
- `2-benchmarks/Just.PreciseMath.Benchmarks/`: reserved for performance benchmarks. - `2-benchmarks/Just.PreciseMath.Benchmarks/`: arithmetic benchmarks against `decimal` and `double`.
## Contributing ## Contributing