Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions .github/workflows/benchmarks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
name: Cross-platform benchmarks

on:
workflow_dispatch:
inputs:
rows:
description: Workbook row count
required: true
default: 100000
type: number
columns:
description: Workbook column count
required: true
default: 10
type: number
iterations:
description: Fresh processes per runtime and scenario
required: true
default: 5
type: number
mini_excel_version:
description: MiniExcel NuGet baseline version
required: true
default: 2.0.0-preview.4
type: string
schedule:
- cron: '17 3 * * 1'

permissions:
contents: read

jobs:
benchmark:
name: Benchmark ${{ matrix.rid }}
strategy:
fail-fast: false
matrix:
include:
- runner: windows-latest
rid: win-x64
- runner: windows-11-arm
rid: win-arm64
- runner: ubuntu-latest
rid: linux-x64
- runner: ubuntu-24.04-arm
rid: linux-arm64
- runner: macos-15-intel
rid: osx-x64
- runner: macos-latest
rid: osx-arm64
runs-on: ${{ matrix.runner }}
env:
BENCHMARK_ROWS: ${{ inputs.rows || '100000' }}
BENCHMARK_COLUMNS: ${{ inputs.columns || '10' }}
BENCHMARK_ITERATIONS: ${{ inputs.iterations || '5' }}
MINIEXCEL_VERSION: ${{ inputs.mini_excel_version || '2.0.0-preview.4' }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@1.85.0
- uses: actions/setup-dotnet@v4
with:
dotnet-version: |
8.0.x
10.0.x
- uses: Swatinem/rust-cache@v2
with:
key: benchmark-${{ matrix.rid }}
- name: Run equivalent-query benchmark
shell: pwsh
run: |
./build/Benchmark-Package.ps1 `
-Rid '${{ matrix.rid }}' `
-Rows $env:BENCHMARK_ROWS `
-Columns $env:BENCHMARK_COLUMNS `
-Iterations $env:BENCHMARK_ITERATIONS `
-MiniExcelVersion $env:MINIEXCEL_VERSION
- uses: actions/upload-artifact@v4
with:
name: benchmark-${{ matrix.rid }}
path: |
artifacts/benchmarks/benchmark-${{ matrix.rid }}.json
artifacts/benchmarks/benchmark-${{ matrix.rid }}.md
if-no-files-found: error

publish-results:
name: Open benchmark results PR
needs: benchmark
if: github.ref_name == github.event.repository.default_branch
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
pattern: benchmark-*
path: artifacts/benchmarks
merge-multiple: true
- name: Publish benchmark reports
shell: pwsh
run: ./build/Publish-Benchmark-Results.ps1
- name: Create or update results pull request
uses: peter-evans/create-pull-request@v7
with:
branch: automation/benchmark-results
base: ${{ github.event.repository.default_branch }}
delete-branch: true
commit-message: 'docs: update cross-platform benchmark results'
title: 'docs: update cross-platform benchmark results'
body: |
Automated benchmark refresh from `${{ github.workflow }}`.

Source run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
add-paths: docs/benchmarks
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ jobs:
key: ${{ matrix.rid }}
- name: Build, pack, and test
shell: pwsh
run: ./build/Test-Package.ps1 -Rid '${{ matrix.rid }}'
run: ./build/Test-Package.ps1 -Rid '${{ matrix.rid }}' -LifecycleIterations 5000
- uses: actions/upload-artifact@v4
with:
name: ${{ matrix.rid }}
Expand Down Expand Up @@ -106,7 +106,7 @@ jobs:
-v "$GITHUB_WORKSPACE:/work" \
-w /work \
mcr.microsoft.com/dotnet/sdk:8.0-alpine \
sh -lc 'dotnet restore tests/MiniExcelRust.PackageTests/MiniExcelRust.PackageTests.csproj --source artifacts/packages && dotnet run --project tests/MiniExcelRust.PackageTests/MiniExcelRust.PackageTests.csproj -c Release --no-restore'
sh -lc 'dotnet restore tests/MiniExcelRust.PackageTests/MiniExcelRust.PackageTests.csproj --source artifacts/packages && dotnet run --project tests/MiniExcelRust.PackageTests/MiniExcelRust.PackageTests.csproj -c Release --no-restore -- suite 5000 32'
- uses: actions/upload-artifact@v4
with:
name: ${{ matrix.rid }}
Expand All @@ -130,9 +130,9 @@ jobs:
run: dotnet pack src/MiniExcelRust/MiniExcelRust.csproj -c Release -o artifacts/packages
- name: Verify package contents
shell: pwsh
run: ./build/Verify-Package.ps1 -PackagePath artifacts/packages/MiniExcelRust.0.1.0-preview.1.nupkg
run: ./build/Verify-Package.ps1 -PackagePath artifacts/packages/MiniExcelRust.0.1.0-preview.2.nupkg
- uses: actions/upload-artifact@v4
with:
name: nuget-preview
path: artifacts/packages/MiniExcelRust.0.1.0-preview.1.*nupkg
path: artifacts/packages/MiniExcelRust.0.1.0-preview.2.*nupkg
if-no-files-found: error
8 changes: 4 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ on:
workflow_dispatch:
inputs:
version:
description: NuGet version, for example 0.1.0-preview.1
description: NuGet version, for example 0.1.0-preview.2
required: true
default: 0.1.0-preview.1
default: 0.1.0-preview.2
type: string
publish:
description: Publish to NuGet.org after all tests pass
Expand Down Expand Up @@ -188,7 +188,7 @@ jobs:
shell: pwsh
run: |
dotnet restore tests/MiniExcelRust.PackageTests/MiniExcelRust.PackageTests.csproj --source artifacts/packages -p:MiniExcelRustPackageVersion=${{ needs.prepare.outputs.version }}
dotnet run --project tests/MiniExcelRust.PackageTests/MiniExcelRust.PackageTests.csproj -c Release --no-restore -p:MiniExcelRustPackageVersion=${{ needs.prepare.outputs.version }}
dotnet run --project tests/MiniExcelRust.PackageTests/MiniExcelRust.PackageTests.csproj -c Release --no-restore -p:MiniExcelRustPackageVersion=${{ needs.prepare.outputs.version }} -- suite 5000 32
- name: Consume package in Alpine
if: matrix.musl
shell: bash
Expand All @@ -197,7 +197,7 @@ jobs:
-v "$GITHUB_WORKSPACE:/work" \
-w /work \
mcr.microsoft.com/dotnet/sdk:8.0-alpine \
sh -lc 'dotnet restore tests/MiniExcelRust.PackageTests/MiniExcelRust.PackageTests.csproj --source artifacts/packages -p:MiniExcelRustPackageVersion=${{ needs.prepare.outputs.version }} && dotnet run --project tests/MiniExcelRust.PackageTests/MiniExcelRust.PackageTests.csproj -c Release --no-restore -p:MiniExcelRustPackageVersion=${{ needs.prepare.outputs.version }}'
sh -lc 'dotnet restore tests/MiniExcelRust.PackageTests/MiniExcelRust.PackageTests.csproj --source artifacts/packages -p:MiniExcelRustPackageVersion=${{ needs.prepare.outputs.version }} && dotnet run --project tests/MiniExcelRust.PackageTests/MiniExcelRust.PackageTests.csproj -c Release --no-restore -p:MiniExcelRustPackageVersion=${{ needs.prepare.outputs.version }} -- suite 5000 32'

publish:
needs: [prepare, test-package]
Expand Down
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ version = "0.1.0"
edition = "2024"
rust-version = "1.85"
license = "Apache-2.0"
repository = "https://github.com/mini-software/MiniExcelRust"
homepage = "https://github.com/mini-software/MiniExcelRust"
repository = "https://github.com/mini-software/MiniExcelRustNuGetTest"
homepage = "https://github.com/mini-software/MiniExcelRustNuGetTest"
64 changes: 61 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,19 @@ versioned C ABI.
dotnet add package MiniExcelRust --prerelease
```

The package is currently prerelease. Pin an exact version in production builds:

```shell
dotnet add package MiniExcelRust --version 0.1.0-preview.2
```

To check for and install a newer preview:

```shell
dotnet list package --outdated --include-prerelease
dotnet add package MiniExcelRust --prerelease
```

## Usage

```csharp
Expand All @@ -24,8 +37,21 @@ foreach (var row in MiniExcelRust.Query("input.xlsx", useHeaderRow: true))
}
```

`Query` accepts `path`, `useHeaderRow`, `sheetName`, and `startCell`. Each streamed row is
an `IDictionary<string, object?>`; cells are returned as strings, doubles, booleans, or nulls.
For example, a query against another sheet starting at `C2` is:

```csharp
var rows = MiniExcelRust.Query(
"input.xlsx",
useHeaderRow: true,
sheetName: "Data",
startCell: "C2");
```

Rows are streamed in bounded batches across the native boundary. Disposing the enumerator
early closes the native query handle.
early closes the native query handle. Normal `foreach` enumeration disposes it automatically;
code that manually obtains an enumerator should wrap it in `using`.

## Supported Platforms

Expand Down Expand Up @@ -58,11 +84,43 @@ dotnet build ./src/MiniExcelRust/MiniExcelRust.csproj -c Release
```

`Test-Package.ps1` builds the native library, packs `MiniExcelRust`, restores a separate
consumer from the local package feed, and executes an XLSX query smoke test.
consumer from the local package feed, and verifies equivalent queries against MiniExcel.

GitHub CI runs those header, headerless, sheet, start-cell, Unicode, boolean, null, numeric,
full-enumeration, and early-disposal queries on all eight supported RIDs. Each platform also
runs 5,000 lifecycle iterations and fails when private memory grows by more than 32 MB, when
the native handle/file-descriptor count grows by more than four, or when the workbook cannot
be reopened exclusively. This is a bounded resource-growth regression test rather than a
mathematical proof that no leak can exist.

## Benchmark

The benchmark first compares every returned row and cell with the configured MiniExcel NuGet
baseline. It then measures both implementations in alternating fresh processes against the
same generated XLSX file and query options. The scheduled and manually dispatched GitHub
workflow runs on Windows, Linux, and macOS for x64 and Arm64; musl remains covered by the
Alpine correctness and lifecycle job because GitHub does not provide native musl runners.

The latest checked-in cross-platform summary and per-RID reports are in the
[benchmark results](https://github.com/mini-software/MiniExcelRustNuGetTest/blob/main/docs/benchmarks/README.md).
Each report includes elapsed time, first-row latency, managed allocation, peak process memory,
environment metadata, and a JSON file containing all raw iterations and hashes.

After all scheduled benchmarks pass on the default branch, the workflow updates
`docs/benchmarks/` through an `automation/benchmark-results` pull request. Repeated runs refresh
the same PR instead of committing directly to the protected branch. Repository settings must
allow GitHub Actions to create pull requests.

Run the same reproducible comparison locally, or override `-MiniExcelVersion` to test a newer
NuGet release:

```powershell
./build/Benchmark-Package.ps1 -Rid win-x64 -MiniExcelVersion 2.0.0-preview.4
```

## Release

Version tags use the form `v0.1.0-preview.1`. The release workflow builds all eight native
Version tags use the form `v0.1.0-preview.2`. The release workflow builds all eight native
assets, verifies the assembled package, tests it on native GitHub-hosted runners, and publishes
to NuGet.org through the protected `release` environment.

Expand Down
Loading
Loading