diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index b0946c6..8071bfd 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -120,8 +120,10 @@ jobs: echo "NAME=$name" >> "$GITHUB_ENV" if [ "$LANGUAGE" = rust ]; then benchmark=(rust/target/release/sqlite-vs-doublets) + toolchain="$(rustc --version)" else benchmark=(dotnet csharp/SQLiteVSDoublets/bin/Release/net10.0/sqlite-vs-doublets.dll) + toolchain=".NET SDK $(dotnet --version)" fi "${benchmark[@]}" "$CATEGORY" "$BITS" "$SIZE" \ --directory "$RUNNER_TEMP/storage" --output "$RUNNER_TEMP/measured.json" @@ -133,6 +135,7 @@ jobs: --arg date "$(date -u +%Y-%m-%d)" --arg machine "$machine" \ '. + {run_url: $run_url, source_sha: $source_sha, tree_sha: $tree_sha, date: $date, machine: $machine}' \ "$RUNNER_TEMP/measured.json" > "results/$name.json" + python3 scripts/benchmark_provenance.py "results/$name.json" --toolchain "$toolchain" - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: results-${{ env.NAME }} diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 9582669..a04b4c9 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -4,9 +4,9 @@ on: workflow_dispatch: push: branches: [main] - paths: ['.github/**', 'scripts/**', 'experiments/**', 'pyproject.toml', 'README*.md', 'CONTRIBUTING.md', 'cpp/**', '.secretlintrc.json', '.pre-commit-config.yaml', '.markdownlint.json', '.codacy.yml'] + paths: ['.github/**', 'scripts/**', 'experiments/**', 'pyproject.toml', 'README*.md', 'CONTRIBUTING.md', '.secretlintrc.json', '.pre-commit-config.yaml', '.markdownlint.json', '.codacy.yml'] pull_request: - paths: ['.github/**', 'scripts/**', 'experiments/**', 'pyproject.toml', 'README*.md', 'CONTRIBUTING.md', 'cpp/**', '.secretlintrc.json', '.pre-commit-config.yaml', '.markdownlint.json', '.codacy.yml'] + paths: ['.github/**', 'scripts/**', 'experiments/**', 'pyproject.toml', 'README*.md', 'CONTRIBUTING.md', '.secretlintrc.json', '.pre-commit-config.yaml', '.markdownlint.json', '.codacy.yml'] permissions: contents: read @@ -59,7 +59,7 @@ jobs: - run: python -m unittest discover -s experiments/ci -v - name: Verify preserved investigation evidence run: python experiments/ci/package_evidence.py dev/log/issues/110/pulls/111 --verify - - run: npx --yes markdownlint-cli@0.49.1 README.md README.ru.md CONTRIBUTING.md cpp/README.md + - run: npx --yes markdownlint-cli@0.49.1 README.md README.ru.md CONTRIBUTING.md - name: Scan maintained source for secrets run: git ls-files -z -- ':!dev/log/**' ':!docs/benchmarks/**' ':!*.png' | xargs -0 npx --yes -p secretlint@13.0.7 -p @secretlint/secretlint-rule-preset-recommend@13.0.7 secretlint diff --git a/.gitignore b/.gitignore index c5bd78a..14e95b9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,5 @@ # Root level gitignore for the repository # C# specific gitignore is in csharp/.gitignore -# C++ specific files -cpp/build/ -cpp/*.o -cpp/*.a -cpp/*.so -cpp/*.exe - # Common temporary files __pycache__/ .venv/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3c4d45c..89cf057 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,7 +31,7 @@ dotnet format whitespace csharp/SQLiteVSDoublets.slnx --verify-no-changes dotnet build csharp/SQLiteVSDoublets.slnx -c Release -warnaserror (cd csharp && dotnet test --project SQLiteVSDoublets.Tests -c Release \ --no-build -- --timeout 10m) -npx --yes markdownlint-cli@0.49.1 README.md README.ru.md CONTRIBUTING.md cpp/README.md +npx --yes markdownlint-cli@0.49.1 README.md README.ru.md CONTRIBUTING.md ``` Workflow validation also runs actionlint with ShellCheck and zizmor. The pinned @@ -60,7 +60,6 @@ reports from the newly fetched branch on every retry. This repository contains benchmark executables, without a package release or container deployment pipeline. Package changesets, registry credentials, and container build steps should be added with an actual distributable component. -The C++ directory is a placeholder; see [its prerequisites](cpp/README.md). Immutable investigation evidence is preserved in [the issue 110 investigation](dev/log/issues/110/pulls/111/ANALYSIS.md). diff --git a/README.md b/README.md index b267257..54de54b 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,12 @@ repetitions do not overlap and the medians differ by more than 5%, otherwise it is `≈ same`. The file size is measured after creation; Doublets files are preallocated memory-mapped files, so small stores show the preallocation size. +Each results group includes linear and logarithmic charts. Linear charts raise +bars below 0.5% of the panel maximum for visibility; the tables retain exact +times. Provenance records the Doublets library and Rust compiler or .NET SDK +versions used for that run. Older reports without this metadata identify it +as missing rather than attributing current toolchains to past measurements. + Every table is measured by its own GitHub Actions job, with all variants on the same runner, so that a difference is never a difference between machines, by the [Benchmarks workflow](.github/workflows/benchmarks.yml): links with @@ -113,6 +119,10 @@ dotnet run -c Release --project csharp/SQLiteVSDoublets -- \ links 64 1000 --repetitions 3 \ --variants SQLite_Memory,SystemDataSQLite_Memory \ --output results/links-csharp-64-1000.json +python3 scripts/benchmark_provenance.py results/links-rust-64-100000.json \ + --toolchain "$(rustc --version)" +python3 scripts/benchmark_provenance.py results/links-csharp-64-1000.json \ + --toolchain ".NET SDK $(dotnet --version)" # print the tables, add --readme README.md --charts docs/benchmarks to update python3 scripts/benchmark_report.py results ``` @@ -173,7 +183,7 @@ Notes: ##### 100,000 links -_10 repetitions after a warm-up, median time per operation. SQLite 3.53.2, Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB, [GitHub Actions run](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) on 2026-10-05._ +_10 repetitions after a warm-up, median time per operation. SQLite 3.53.2; doublets 0.5.0; rustc 1.99.0 (b940084d7 2026-09-28). Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB, [GitHub Actions run](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) on 2026-10-05._ | Storage | Create | Read all | Read by id | Search (from, to) | Read by from | Read by to | Update | Delete | File size | | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | @@ -186,7 +196,7 @@ _10 repetitions after a warm-up, median time per operation. SQLite 3.53.2, Ubunt ##### 1,000,000 links -_3 repetitions after a warm-up, median time per operation. SQLite 3.53.2, Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB, [GitHub Actions run](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) on 2026-10-05._ +_3 repetitions after a warm-up, median time per operation. SQLite 3.53.2; doublets 0.5.0; rustc 1.99.0 (b940084d7 2026-09-28). Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB, [GitHub Actions run](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) on 2026-10-05._ | Storage | Create | Read all | Read by id | Search (from, to) | Read by from | Read by to | Update | Delete | File size | | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | @@ -199,7 +209,7 @@ _3 repetitions after a warm-up, median time per operation. SQLite 3.53.2, Ubuntu ##### 10,000,000 links -_1 repetition after a warm-up, median time per operation. SQLite 3.53.2, Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB, [GitHub Actions run](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) on 2026-10-05._ +_1 repetition after a warm-up, median time per operation. SQLite 3.53.2; doublets 0.5.0; rustc 1.99.0 (b940084d7 2026-09-28). Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB, [GitHub Actions run](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) on 2026-10-05._ | Storage | Create | Read all | Read by id | Search (from, to) | Read by from | Read by to | Update | Delete | File size | | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | @@ -210,13 +220,15 @@ _1 repetition after a warm-up, median time per operation. SQLite 3.53.2, Ubuntu | Doublets Split Volatile | 337 ns (9.32× faster) | 2.83 ns (34.7× faster) | 30.9 ns (44.9× faster) | 238 ns (5.79× faster) | 171 ns (8.23× faster) | 170 ns (8.18× faster) | 689 ns (12.3× faster) | 2.78 µs (2.09× faster) | — | | Doublets Split NonVolatile | 490 ns (17.9× faster) | 2.8 ns (35.7× faster) | 31.6 ns (67× faster) | 244 ns (9.81× faster) | 177 ns (13.1× faster) | 180 ns (12.9× faster) | 737 ns (31.5× faster) | 3.13 µs (4.39× faster) | 400.0 MiB | -![Rust doublets vs SQLite, 32 bit, links](docs/benchmarks/links-rust-32.png) +![Rust doublets vs SQLite, 32 bit, links, linear scale](docs/benchmarks/links-rust-32-linear.png) + +![Rust doublets vs SQLite, 32 bit, links, log scale](docs/benchmarks/links-rust-32.png) #### 64 bit address/id space benchmarks ##### 100,000 links -_10 repetitions after a warm-up, median time per operation. SQLite 3.53.2, Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB, [GitHub Actions run](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) on 2026-10-05._ +_10 repetitions after a warm-up, median time per operation. SQLite 3.53.2; doublets 0.5.0; rustc 1.99.0 (b940084d7 2026-09-28). Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB, [GitHub Actions run](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) on 2026-10-05._ | Storage | Create | Read all | Read by id | Search (from, to) | Read by from | Read by to | Update | Delete | File size | | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | @@ -229,7 +241,7 @@ _10 repetitions after a warm-up, median time per operation. SQLite 3.53.2, Ubunt ##### 1,000,000 links -_3 repetitions after a warm-up, median time per operation. SQLite 3.53.2, Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB, [GitHub Actions run](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) on 2026-10-05._ +_3 repetitions after a warm-up, median time per operation. SQLite 3.53.2; doublets 0.5.0; rustc 1.99.0 (b940084d7 2026-09-28). Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB, [GitHub Actions run](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) on 2026-10-05._ | Storage | Create | Read all | Read by id | Search (from, to) | Read by from | Read by to | Update | Delete | File size | | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | @@ -242,7 +254,7 @@ _3 repetitions after a warm-up, median time per operation. SQLite 3.53.2, Ubuntu ##### 10,000,000 links -_1 repetition after a warm-up, median time per operation. SQLite 3.53.2, Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB, [GitHub Actions run](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) on 2026-10-05._ +_1 repetition after a warm-up, median time per operation. SQLite 3.53.2; doublets 0.5.0; rustc 1.99.0 (b940084d7 2026-09-28). Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB, [GitHub Actions run](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) on 2026-10-05._ | Storage | Create | Read all | Read by id | Search (from, to) | Read by from | Read by to | Update | Delete | File size | | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | @@ -253,7 +265,9 @@ _1 repetition after a warm-up, median time per operation. SQLite 3.53.2, Ubuntu | Doublets Split Volatile | 381 ns (8.71× faster) | 3.88 ns (24.7× faster) | 28 ns (54.4× faster) | 247 ns (5.91× faster) | 174 ns (8.47× faster) | 176 ns (8.31× faster) | 719 ns (12.1× faster) | 2.78 µs (2.19× faster) | — | | Doublets Split NonVolatile | 677 ns (13.4× faster) | 4.19 ns (23.7× faster) | 36.7 ns (58.3× faster) | 248 ns (9.71× faster) | 173 ns (13.4× faster) | 179 ns (13.1× faster) | 755 ns (32.6× faster) | 3.64 µs (4.18× faster) | 800.0 MiB | -![Rust doublets vs SQLite, 64 bit, links](docs/benchmarks/links-rust-64.png) +![Rust doublets vs SQLite, 64 bit, links, linear scale](docs/benchmarks/links-rust-64-linear.png) + +![Rust doublets vs SQLite, 64 bit, links, log scale](docs/benchmarks/links-rust-64.png) ### C# doublets vs SQLite @@ -261,7 +275,7 @@ _1 repetition after a warm-up, median time per operation. SQLite 3.53.2, Ubuntu ##### 100,000 links -_10 repetitions after a warm-up, median time per operation. SQLite 3.53.3, Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB, [GitHub Actions run](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) on 2026-10-05._ +_10 repetitions after a warm-up, median time per operation. SQLite 3.53.3; Platform.Data.Doublets 0.18.1; .NET SDK 10.0.401. Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB, [GitHub Actions run](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) on 2026-10-05._ | Storage | Create | Read all | Read by id | Search (from, to) | Read by from | Read by to | Update | Delete | File size | | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | @@ -276,7 +290,7 @@ _10 repetitions after a warm-up, median time per operation. SQLite 3.53.3, Ubunt ##### 1,000,000 links -_3 repetitions after a warm-up, median time per operation. SQLite 3.53.3, Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB, [GitHub Actions run](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) on 2026-10-05._ +_3 repetitions after a warm-up, median time per operation. SQLite 3.53.3; Platform.Data.Doublets 0.18.1; .NET SDK 10.0.401. Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB, [GitHub Actions run](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) on 2026-10-05._ | Storage | Create | Read all | Read by id | Search (from, to) | Read by from | Read by to | Update | Delete | File size | | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | @@ -291,7 +305,7 @@ _3 repetitions after a warm-up, median time per operation. SQLite 3.53.3, Ubuntu ##### 10,000,000 links -_1 repetition after a warm-up, median time per operation. SQLite 3.53.3, Ubuntu 24.04.5 LTS, INTEL(R) XEON(R) PLATINUM 8573C, 4 cores, 16 GiB, [GitHub Actions run](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) on 2026-10-05._ +_1 repetition after a warm-up, median time per operation. SQLite 3.53.3; Platform.Data.Doublets 0.18.1; .NET SDK 10.0.401. Ubuntu 24.04.5 LTS, INTEL(R) XEON(R) PLATINUM 8573C, 4 cores, 16 GiB, [GitHub Actions run](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) on 2026-10-05._ | Storage | Create | Read all | Read by id | Search (from, to) | Read by from | Read by to | Update | Delete | File size | | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | @@ -304,13 +318,15 @@ _1 repetition after a warm-up, median time per operation. SQLite 3.53.3, Ubuntu | Doublets Split Volatile | 486 ns (9.61× faster) | 10 ns (47.1× faster) | 176 ns (12.6× faster) | 431 ns (5.82× faster) | 265 ns (9.74× faster) | 257 ns (10× faster) | 694 ns (16.4× faster) | 563 ns (13.2× faster) | — | | Doublets Split NonVolatile | 1.27 µs (6.33× faster) | 10.1 ns (46.4× faster) | 205 ns (12.2× faster) | 507 ns (5.7× faster) | 309 ns (9.38× faster) | 305 ns (9.52× faster) | 979 ns (20.4× faster) | 817 ns (14.5× faster) | 400.0 MiB | -![C# doublets vs SQLite, 32 bit, links](docs/benchmarks/links-csharp-32.png) +![C# doublets vs SQLite, 32 bit, links, linear scale](docs/benchmarks/links-csharp-32-linear.png) + +![C# doublets vs SQLite, 32 bit, links, log scale](docs/benchmarks/links-csharp-32.png) #### 64 bit address/id space benchmarks ##### 100,000 links -_10 repetitions after a warm-up, median time per operation. SQLite 3.53.3, Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB, [GitHub Actions run](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) on 2026-10-05._ +_10 repetitions after a warm-up, median time per operation. SQLite 3.53.3; Platform.Data.Doublets 0.18.1; .NET SDK 10.0.401. Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB, [GitHub Actions run](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) on 2026-10-05._ | Storage | Create | Read all | Read by id | Search (from, to) | Read by from | Read by to | Update | Delete | File size | | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | @@ -325,7 +341,7 @@ _10 repetitions after a warm-up, median time per operation. SQLite 3.53.3, Ubunt ##### 1,000,000 links -_3 repetitions after a warm-up, median time per operation. SQLite 3.53.3, Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB, [GitHub Actions run](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) on 2026-10-05._ +_3 repetitions after a warm-up, median time per operation. SQLite 3.53.3; Platform.Data.Doublets 0.18.1; .NET SDK 10.0.401. Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB, [GitHub Actions run](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) on 2026-10-05._ | Storage | Create | Read all | Read by id | Search (from, to) | Read by from | Read by to | Update | Delete | File size | | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | @@ -340,7 +356,7 @@ _3 repetitions after a warm-up, median time per operation. SQLite 3.53.3, Ubuntu ##### 10,000,000 links -_1 repetition after a warm-up, median time per operation. SQLite 3.53.3, Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB, [GitHub Actions run](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) on 2026-10-05._ +_1 repetition after a warm-up, median time per operation. SQLite 3.53.3; Platform.Data.Doublets 0.18.1; .NET SDK 10.0.401. Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB, [GitHub Actions run](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) on 2026-10-05._ | Storage | Create | Read all | Read by id | Search (from, to) | Read by from | Read by to | Update | Delete | File size | | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | @@ -353,7 +369,9 @@ _1 repetition after a warm-up, median time per operation. SQLite 3.53.3, Ubuntu | Doublets Split Volatile | 449 ns (10× faster) | 12.6 ns (36.6× faster) | 294 ns (7.12× faster) | 443 ns (5.32× faster) | 317 ns (7.65× faster) | 330 ns (7.26× faster) | 859 ns (11.3× faster) | 688 ns (8.95× faster) | — | | Doublets Split NonVolatile | 1.27 µs (8.22× faster) | 12.6 ns (36.8× faster) | 331 ns (8.88× faster) | 484 ns (7.17× faster) | 339 ns (9.86× faster) | 351 ns (9.55× faster) | 1.19 µs (21.4× faster) | 853 ns (18.1× faster) | 800.0 MiB | -![C# doublets vs SQLite, 64 bit, links](docs/benchmarks/links-csharp-64.png) +![C# doublets vs SQLite, 64 bit, links, linear scale](docs/benchmarks/links-csharp-64-linear.png) + +![C# doublets vs SQLite, 64 bit, links, log scale](docs/benchmarks/links-csharp-64.png) ## Doublets vs SQLite as storage for objects @@ -363,7 +381,7 @@ _1 repetition after a warm-up, median time per operation. SQLite 3.53.3, Ubuntu ##### 100,000 blog posts -_5 repetitions after a warm-up, median time per operation. SQLite 3.53.2, Ubuntu 24.04.5 LTS, AMD EPYC 9V45 96-Core Processor, 4 cores, 16 GiB, [GitHub Actions run](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) on 2026-10-05._ +_5 repetitions after a warm-up, median time per operation. SQLite 3.53.2; doublets 0.5.0; rustc 1.99.0 (b940084d7 2026-09-28). Ubuntu 24.04.5 LTS, AMD EPYC 9V45 96-Core Processor, 4 cores, 16 GiB, [GitHub Actions run](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) on 2026-10-05._ | Storage | Create | Read all | Read by id | Delete | File size | | --- | ---: | ---: | ---: | ---: | ---: | @@ -380,7 +398,7 @@ _5 repetitions after a warm-up, median time per operation. SQLite 3.53.2, Ubuntu ##### 1,000,000 blog posts -_1 repetition after a warm-up, median time per operation. SQLite 3.53.2, Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB, [GitHub Actions run](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) on 2026-10-05._ +_1 repetition after a warm-up, median time per operation. SQLite 3.53.2; doublets 0.5.0; rustc 1.99.0 (b940084d7 2026-09-28). Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB, [GitHub Actions run](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) on 2026-10-05._ | Storage | Create | Read all | Read by id | Delete | File size | | --- | ---: | ---: | ---: | ---: | ---: | @@ -395,13 +413,15 @@ _1 repetition after a warm-up, median time per operation. SQLite 3.53.2, Ubuntu | Doublets Split NonVolatile Cached | 6.46 µs (1.89× slower) | 882 ns (1.34× slower) | 961 ns (4.23× faster) | 6.75 µs (1.68× faster) | 400.0 MiB | | Doublets Split NonVolatile Uncached | 36.6 µs (10.7× slower) | 14.5 µs (22.1× slower) | 14.8 µs (3.64× slower) | 6.98 µs (1.63× faster) | 400.0 MiB | -![Rust doublets vs SQLite, 32 bit, objects](docs/benchmarks/objects-rust-32.png) +![Rust doublets vs SQLite, 32 bit, objects, linear scale](docs/benchmarks/objects-rust-32-linear.png) + +![Rust doublets vs SQLite, 32 bit, objects, log scale](docs/benchmarks/objects-rust-32.png) #### 64 bit address/id space benchmarks ##### 100,000 blog posts -_5 repetitions after a warm-up, median time per operation. SQLite 3.53.2, Ubuntu 24.04.5 LTS, AMD EPYC 9V45 96-Core Processor, 4 cores, 16 GiB, [GitHub Actions run](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) on 2026-10-05._ +_5 repetitions after a warm-up, median time per operation. SQLite 3.53.2; doublets 0.5.0; rustc 1.99.0 (b940084d7 2026-09-28). Ubuntu 24.04.5 LTS, AMD EPYC 9V45 96-Core Processor, 4 cores, 16 GiB, [GitHub Actions run](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) on 2026-10-05._ | Storage | Create | Read all | Read by id | Delete | File size | | --- | ---: | ---: | ---: | ---: | ---: | @@ -418,7 +438,7 @@ _5 repetitions after a warm-up, median time per operation. SQLite 3.53.2, Ubuntu ##### 1,000,000 blog posts -_1 repetition after a warm-up, median time per operation. SQLite 3.53.2, Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB, [GitHub Actions run](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) on 2026-10-05._ +_1 repetition after a warm-up, median time per operation. SQLite 3.53.2; doublets 0.5.0; rustc 1.99.0 (b940084d7 2026-09-28). Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB, [GitHub Actions run](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) on 2026-10-05._ | Storage | Create | Read all | Read by id | Delete | File size | | --- | ---: | ---: | ---: | ---: | ---: | @@ -433,7 +453,9 @@ _1 repetition after a warm-up, median time per operation. SQLite 3.53.2, Ubuntu | Doublets Split NonVolatile Cached | 8.1 µs (2.3× slower) | 952 ns (1.5× slower) | 1.02 µs (3.45× faster) | 7.65 µs (1.32× faster) | 800.0 MiB | | Doublets Split NonVolatile Uncached | 55.9 µs (15.9× slower) | 14.7 µs (23.1× slower) | 15.4 µs (4.35× slower) | 8.04 µs (1.26× faster) | 800.0 MiB | -![Rust doublets vs SQLite, 64 bit, objects](docs/benchmarks/objects-rust-64.png) +![Rust doublets vs SQLite, 64 bit, objects, linear scale](docs/benchmarks/objects-rust-64-linear.png) + +![Rust doublets vs SQLite, 64 bit, objects, log scale](docs/benchmarks/objects-rust-64.png) ### C# doublets vs SQLite @@ -441,7 +463,7 @@ _1 repetition after a warm-up, median time per operation. SQLite 3.53.2, Ubuntu ##### 100,000 blog posts -_5 repetitions after a warm-up, median time per operation. SQLite 3.53.3, Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB, [GitHub Actions run](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) on 2026-10-05._ PostgreSQL 18.3 (Debian 18.3-1.pgdg13+1); Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3.0; EF Core 10.0.4.0. +_5 repetitions after a warm-up, median time per operation. SQLite 3.53.3; Platform.Data.Doublets 0.18.1; .NET SDK 10.0.401. Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB, [GitHub Actions run](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) on 2026-10-05. PostgreSQL 18.3 (Debian 18.3-1.pgdg13+1); Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3.0; EF Core 10.0.4.0._ | Storage | Create | Read all | Read by id | Delete | File size | Server relations | | --- | ---: | ---: | ---: | ---: | ---: | ---: | @@ -461,7 +483,7 @@ _5 repetitions after a warm-up, median time per operation. SQLite 3.53.3, Ubuntu ##### 1,000,000 blog posts -_1 repetition after a warm-up, median time per operation. SQLite 3.53.3, Ubuntu 24.04.5 LTS, INTEL(R) XEON(R) PLATINUM 8573C, 4 cores, 16 GiB, [GitHub Actions run](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) on 2026-10-05._ PostgreSQL 18.3 (Debian 18.3-1.pgdg13+1); Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3.0; EF Core 10.0.4.0. +_1 repetition after a warm-up, median time per operation. SQLite 3.53.3; Platform.Data.Doublets 0.18.1; .NET SDK 10.0.401. Ubuntu 24.04.5 LTS, INTEL(R) XEON(R) PLATINUM 8573C, 4 cores, 16 GiB, [GitHub Actions run](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) on 2026-10-05. PostgreSQL 18.3 (Debian 18.3-1.pgdg13+1); Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3.0; EF Core 10.0.4.0._ | Storage | Create | Read all | Read by id | Delete | File size | Server relations | | --- | ---: | ---: | ---: | ---: | ---: | ---: | @@ -479,13 +501,15 @@ _1 repetition after a warm-up, median time per operation. SQLite 3.53.3, Ubuntu | Doublets Split NonVolatile Cached | 21 µs (2.32× slower) | 14 µs (3.79× slower) | 7.1 µs (≈ same) | 10.8 µs (≈ same) | 400.0 MiB | — | | Doublets Split NonVolatile Uncached | 142 µs (15.7× slower) | 460 µs (125× slower) | 460 µs (67.5× slower) | 11.2 µs (≈ same) | 400.0 MiB | — | -![C# doublets vs SQLite, 32 bit, objects](docs/benchmarks/objects-csharp-32.png) +![C# doublets vs SQLite, 32 bit, objects, linear scale](docs/benchmarks/objects-csharp-32-linear.png) + +![C# doublets vs SQLite, 32 bit, objects, log scale](docs/benchmarks/objects-csharp-32.png) #### 64 bit address/id space benchmarks ##### 100,000 blog posts -_5 repetitions after a warm-up, median time per operation. SQLite 3.53.3, Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB, [GitHub Actions run](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) on 2026-10-05._ PostgreSQL 18.3 (Debian 18.3-1.pgdg13+1); Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3.0; EF Core 10.0.4.0. +_5 repetitions after a warm-up, median time per operation. SQLite 3.53.3; Platform.Data.Doublets 0.18.1; .NET SDK 10.0.401. Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB, [GitHub Actions run](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) on 2026-10-05. PostgreSQL 18.3 (Debian 18.3-1.pgdg13+1); Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3.0; EF Core 10.0.4.0._ | Storage | Create | Read all | Read by id | Delete | File size | Server relations | | --- | ---: | ---: | ---: | ---: | ---: | ---: | @@ -505,7 +529,7 @@ _5 repetitions after a warm-up, median time per operation. SQLite 3.53.3, Ubuntu ##### 1,000,000 blog posts -_1 repetition after a warm-up, median time per operation. SQLite 3.53.3, Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB, [GitHub Actions run](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) on 2026-10-05._ PostgreSQL 18.3 (Debian 18.3-1.pgdg13+1); Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3.0; EF Core 10.0.4.0. +_1 repetition after a warm-up, median time per operation. SQLite 3.53.3; Platform.Data.Doublets 0.18.1; .NET SDK 10.0.401. Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB, [GitHub Actions run](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) on 2026-10-05. PostgreSQL 18.3 (Debian 18.3-1.pgdg13+1); Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3.0; EF Core 10.0.4.0._ | Storage | Create | Read all | Read by id | Delete | File size | Server relations | | --- | ---: | ---: | ---: | ---: | ---: | ---: | @@ -523,123 +547,17 @@ _1 repetition after a warm-up, median time per operation. SQLite 3.53.3, Ubuntu | Doublets Split NonVolatile Cached | 27.6 µs (3.25× slower) | 11.3 µs (3.14× slower) | 7.63 µs (1.1× slower) | 13 µs (1.3× faster) | 800.0 MiB | — | | Doublets Split NonVolatile Uncached | 121 µs (14.2× slower) | 323 µs (90× slower) | 320 µs (46.2× slower) | 12.5 µs (1.35× faster) | 800.0 MiB | — | -![C# doublets vs SQLite, 64 bit, objects](docs/benchmarks/objects-csharp-64.png) - - - -## Original comparison - -The original C# object comparison and its historical results. - - - -### SQLite - -```C# -using System.Linq; -using Comparisons.SQLiteVSDoublets.Model; - -namespace Comparisons.SQLiteVSDoublets.SQLite -{ - public class SQLiteTestRun : TestRun - { - public SQLiteTestRun(string dbFilename) : base(dbFilename) { } - - public override void Prepare() - { - using var dbContext = new SQLiteDbContext(DbFilename); - dbContext.Database.EnsureCreated(); - } - - public override void CreateList() - { - using var dbContext = new SQLiteDbContext(DbFilename); - dbContext.BlogPosts.AddRange(BlogPosts.List); - dbContext.SaveChanges(); - } - - public override void ReadList() - { - using var dbContext = new SQLiteDbContext(DbFilename); - foreach (var blogPost in dbContext.BlogPosts) - { - ReadBlogPosts.Add(blogPost); - } - } - - public override void DeleteList() - { - using var dbContext = new SQLiteDbContext(DbFilename); - var blogPostsToDelete = dbContext.BlogPosts.ToList(); - dbContext.BlogPosts.RemoveRange(blogPostsToDelete); - dbContext.SaveChanges(); - } - } -} -``` +![C# doublets vs SQLite, 64 bit, objects, linear scale](docs/benchmarks/objects-csharp-64-linear.png) -### Doublets - -``` C# -using System.IO; -using Platform.IO; -using Comparisons.SQLiteVSDoublets.Model; - -namespace Comparisons.SQLiteVSDoublets.Doublets -{ - public class DoubletsTestRun : TestRun - { - public string DbIndexFilename { get; } - - public DoubletsTestRun(string dbFilename) : base(dbFilename) => DbIndexFilename = $"{Path.GetFileNameWithoutExtension(dbFilename)}.links.index"; - - public override void Prepare() - { - using var dbContext = new DoubletsDbContext(DbFilename, DbIndexFilename); - } - - public override void CreateList() - { - using var dbContext = new DoubletsDbContext(DbFilename, DbIndexFilename); - foreach (var blogPost in BlogPosts.List) - { - dbContext.SaveBlogPost(blogPost); - } - } - - public override void ReadList() - { - using var dbContext = new DoubletsDbContext(DbFilename, DbIndexFilename); - foreach (var blogPost in dbContext.BlogPosts) - { - ReadBlogPosts.Add(blogPost); - } - } - - public override void DeleteList() - { - using var dbContext = new DoubletsDbContext(DbFilename, DbIndexFilename); - var blogPostsToDelete = dbContext.BlogPosts; - foreach (var blogPost in blogPostsToDelete) - { - dbContext.Delete((uint)blogPost.Id); - } - } - - protected override void DeleteDatabase() - { - File.Delete(DbFilename); - File.Delete(DbIndexFilename); - } - - protected override long GetDatabaseSizeInBytes() => FileHelpers.GetSize(DbFilename) + FileHelpers.GetSize(DbIndexFilename); - } -} -``` +![C# doublets vs SQLite, 64 bit, objects, log scale](docs/benchmarks/objects-csharp-64.png) - +## Conclusions -### Conclusion +Each comparison uses SQLite of matching durability and the same noise rule as the tables. Counts span the measured variants, sizes and id widths; they are not an overall speed ranking. RAM usage is not measured. -In this particular comparison, Doublets are faster and use less memory on disk, -but this comes with the cost of additional use of RAM (Sqlite uses it less). +- Rust, links: 187 faster, 3 slower, 2 approximately equal Doublets operation comparisons with SQLite. +- C#, links: 192 faster, 0 slower, 0 approximately equal Doublets operation comparisons with SQLite. +- Rust, objects: 28 faster, 98 slower, 2 approximately equal Doublets operation comparisons with SQLite. +- C#, objects: 10 faster, 109 slower, 9 approximately equal Doublets operation comparisons with SQLite. + + diff --git a/README.ru.md b/README.ru.md index 7518b21..b6d94fa 100644 --- a/README.ru.md +++ b/README.ru.md @@ -107,6 +107,12 @@ docker rm benchmark-postgres выделенные отображаемые в память файлы, поэтому для небольших хранилищ показан размер предварительного выделения. +Для каждой группы результатов строятся линейный и логарифмический графики. +На линейном графике столбцы ниже 0,5% максимума панели увеличены для видимости; +точное время остаётся в таблицах. Происхождение результатов включает версии +библиотеки Дуплетов и компилятора Rust или .NET SDK, использованных при запуске. +Если старый отчёт не содержит этих данных, они отмечены как отсутствующие. + Каждая таблица измеряется отдельной задачей GitHub Actions со всеми вариантами на одной машине, чтобы разница никогда не была разницей между машинами, в [workflow Benchmarks](.github/workflows/benchmarks.yml): связи на 100 000, @@ -130,6 +136,10 @@ dotnet run -c Release --project csharp/SQLiteVSDoublets -- \ links 64 1000 --repetitions 3 \ --variants SQLite_Memory,SystemDataSQLite_Memory \ --output results/links-csharp-64-1000.json +python3 scripts/benchmark_provenance.py results/links-rust-64-100000.json \ + --toolchain "$(rustc --version)" +python3 scripts/benchmark_provenance.py results/links-csharp-64-1000.json \ + --toolchain ".NET SDK $(dotnet --version)" # печатает таблицы, для обновления этого файла добавьте # --readme README.ru.md --charts docs/benchmarks python3 scripts/benchmark_report.py results @@ -175,7 +185,7 @@ python3 scripts/benchmark_report.py results ##### 100,000 связей -_10 повторов после прогрева, медианное время одной операции. SQLite 3.53.2, Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB, [запуск GitHub Actions](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) от 2026-10-05._ +_10 повторов после прогрева, медианное время одной операции. SQLite 3.53.2; doublets 0.5.0; rustc 1.99.0 (b940084d7 2026-09-28). Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB, [запуск GitHub Actions](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) от 2026-10-05._ | Хранилище | Создание | Чтение всех | Чтение по id | Поиск (from, to) | Чтение по from | Чтение по to | Обновление | Удаление | Размер файлов | | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | @@ -188,7 +198,7 @@ _10 повторов после прогрева, медианное время ##### 1,000,000 связей -_3 повтора после прогрева, медианное время одной операции. SQLite 3.53.2, Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB, [запуск GitHub Actions](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) от 2026-10-05._ +_3 повтора после прогрева, медианное время одной операции. SQLite 3.53.2; doublets 0.5.0; rustc 1.99.0 (b940084d7 2026-09-28). Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB, [запуск GitHub Actions](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) от 2026-10-05._ | Хранилище | Создание | Чтение всех | Чтение по id | Поиск (from, to) | Чтение по from | Чтение по to | Обновление | Удаление | Размер файлов | | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | @@ -201,7 +211,7 @@ _3 повтора после прогрева, медианное время о ##### 10,000,000 связей -_1 повтор после прогрева, медианное время одной операции. SQLite 3.53.2, Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB, [запуск GitHub Actions](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) от 2026-10-05._ +_1 повтор после прогрева, медианное время одной операции. SQLite 3.53.2; doublets 0.5.0; rustc 1.99.0 (b940084d7 2026-09-28). Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB, [запуск GitHub Actions](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) от 2026-10-05._ | Хранилище | Создание | Чтение всех | Чтение по id | Поиск (from, to) | Чтение по from | Чтение по to | Обновление | Удаление | Размер файлов | | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | @@ -212,13 +222,15 @@ _1 повтор после прогрева, медианное время од | Doublets Split Volatile | 337 ns (в 9.32× быстрее) | 2.83 ns (в 34.7× быстрее) | 30.9 ns (в 44.9× быстрее) | 238 ns (в 5.79× быстрее) | 171 ns (в 8.23× быстрее) | 170 ns (в 8.18× быстрее) | 689 ns (в 12.3× быстрее) | 2.78 µs (в 2.09× быстрее) | — | | Doublets Split NonVolatile | 490 ns (в 17.9× быстрее) | 2.8 ns (в 35.7× быстрее) | 31.6 ns (в 67× быстрее) | 244 ns (в 9.81× быстрее) | 177 ns (в 13.1× быстрее) | 180 ns (в 12.9× быстрее) | 737 ns (в 31.5× быстрее) | 3.13 µs (в 4.39× быстрее) | 400.0 MiB | -![Дуплеты на Rust против SQLite, 32 бит, связи](docs/benchmarks/links-rust-32.png) +![Дуплеты на Rust против SQLite, 32 бит, связи, линейная шкала](docs/benchmarks/links-rust-32-linear.png) + +![Дуплеты на Rust против SQLite, 32 бит, связи, логарифмическая шкала](docs/benchmarks/links-rust-32.png) #### Тесты с 64-битным пространством адресов/идентификаторов ##### 100,000 связей -_10 повторов после прогрева, медианное время одной операции. SQLite 3.53.2, Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB, [запуск GitHub Actions](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) от 2026-10-05._ +_10 повторов после прогрева, медианное время одной операции. SQLite 3.53.2; doublets 0.5.0; rustc 1.99.0 (b940084d7 2026-09-28). Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB, [запуск GitHub Actions](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) от 2026-10-05._ | Хранилище | Создание | Чтение всех | Чтение по id | Поиск (from, to) | Чтение по from | Чтение по to | Обновление | Удаление | Размер файлов | | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | @@ -231,7 +243,7 @@ _10 повторов после прогрева, медианное время ##### 1,000,000 связей -_3 повтора после прогрева, медианное время одной операции. SQLite 3.53.2, Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB, [запуск GitHub Actions](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) от 2026-10-05._ +_3 повтора после прогрева, медианное время одной операции. SQLite 3.53.2; doublets 0.5.0; rustc 1.99.0 (b940084d7 2026-09-28). Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB, [запуск GitHub Actions](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) от 2026-10-05._ | Хранилище | Создание | Чтение всех | Чтение по id | Поиск (from, to) | Чтение по from | Чтение по to | Обновление | Удаление | Размер файлов | | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | @@ -244,7 +256,7 @@ _3 повтора после прогрева, медианное время о ##### 10,000,000 связей -_1 повтор после прогрева, медианное время одной операции. SQLite 3.53.2, Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB, [запуск GitHub Actions](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) от 2026-10-05._ +_1 повтор после прогрева, медианное время одной операции. SQLite 3.53.2; doublets 0.5.0; rustc 1.99.0 (b940084d7 2026-09-28). Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB, [запуск GitHub Actions](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) от 2026-10-05._ | Хранилище | Создание | Чтение всех | Чтение по id | Поиск (from, to) | Чтение по from | Чтение по to | Обновление | Удаление | Размер файлов | | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | @@ -255,7 +267,9 @@ _1 повтор после прогрева, медианное время од | Doublets Split Volatile | 381 ns (в 8.71× быстрее) | 3.88 ns (в 24.7× быстрее) | 28 ns (в 54.4× быстрее) | 247 ns (в 5.91× быстрее) | 174 ns (в 8.47× быстрее) | 176 ns (в 8.31× быстрее) | 719 ns (в 12.1× быстрее) | 2.78 µs (в 2.19× быстрее) | — | | Doublets Split NonVolatile | 677 ns (в 13.4× быстрее) | 4.19 ns (в 23.7× быстрее) | 36.7 ns (в 58.3× быстрее) | 248 ns (в 9.71× быстрее) | 173 ns (в 13.4× быстрее) | 179 ns (в 13.1× быстрее) | 755 ns (в 32.6× быстрее) | 3.64 µs (в 4.18× быстрее) | 800.0 MiB | -![Дуплеты на Rust против SQLite, 64 бит, связи](docs/benchmarks/links-rust-64.png) +![Дуплеты на Rust против SQLite, 64 бит, связи, линейная шкала](docs/benchmarks/links-rust-64-linear.png) + +![Дуплеты на Rust против SQLite, 64 бит, связи, логарифмическая шкала](docs/benchmarks/links-rust-64.png) ### Дуплеты на C# против SQLite @@ -263,7 +277,7 @@ _1 повтор после прогрева, медианное время од ##### 100,000 связей -_10 повторов после прогрева, медианное время одной операции. SQLite 3.53.3, Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB, [запуск GitHub Actions](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) от 2026-10-05._ +_10 повторов после прогрева, медианное время одной операции. SQLite 3.53.3; Platform.Data.Doublets 0.18.1; .NET SDK 10.0.401. Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB, [запуск GitHub Actions](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) от 2026-10-05._ | Хранилище | Создание | Чтение всех | Чтение по id | Поиск (from, to) | Чтение по from | Чтение по to | Обновление | Удаление | Размер файлов | | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | @@ -278,7 +292,7 @@ _10 повторов после прогрева, медианное время ##### 1,000,000 связей -_3 повтора после прогрева, медианное время одной операции. SQLite 3.53.3, Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB, [запуск GitHub Actions](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) от 2026-10-05._ +_3 повтора после прогрева, медианное время одной операции. SQLite 3.53.3; Platform.Data.Doublets 0.18.1; .NET SDK 10.0.401. Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB, [запуск GitHub Actions](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) от 2026-10-05._ | Хранилище | Создание | Чтение всех | Чтение по id | Поиск (from, to) | Чтение по from | Чтение по to | Обновление | Удаление | Размер файлов | | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | @@ -293,7 +307,7 @@ _3 повтора после прогрева, медианное время о ##### 10,000,000 связей -_1 повтор после прогрева, медианное время одной операции. SQLite 3.53.3, Ubuntu 24.04.5 LTS, INTEL(R) XEON(R) PLATINUM 8573C, 4 cores, 16 GiB, [запуск GitHub Actions](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) от 2026-10-05._ +_1 повтор после прогрева, медианное время одной операции. SQLite 3.53.3; Platform.Data.Doublets 0.18.1; .NET SDK 10.0.401. Ubuntu 24.04.5 LTS, INTEL(R) XEON(R) PLATINUM 8573C, 4 cores, 16 GiB, [запуск GitHub Actions](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) от 2026-10-05._ | Хранилище | Создание | Чтение всех | Чтение по id | Поиск (from, to) | Чтение по from | Чтение по to | Обновление | Удаление | Размер файлов | | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | @@ -306,13 +320,15 @@ _1 повтор после прогрева, медианное время од | Doublets Split Volatile | 486 ns (в 9.61× быстрее) | 10 ns (в 47.1× быстрее) | 176 ns (в 12.6× быстрее) | 431 ns (в 5.82× быстрее) | 265 ns (в 9.74× быстрее) | 257 ns (в 10× быстрее) | 694 ns (в 16.4× быстрее) | 563 ns (в 13.2× быстрее) | — | | Doublets Split NonVolatile | 1.27 µs (в 6.33× быстрее) | 10.1 ns (в 46.4× быстрее) | 205 ns (в 12.2× быстрее) | 507 ns (в 5.7× быстрее) | 309 ns (в 9.38× быстрее) | 305 ns (в 9.52× быстрее) | 979 ns (в 20.4× быстрее) | 817 ns (в 14.5× быстрее) | 400.0 MiB | -![Дуплеты на C# против SQLite, 32 бит, связи](docs/benchmarks/links-csharp-32.png) +![Дуплеты на C# против SQLite, 32 бит, связи, линейная шкала](docs/benchmarks/links-csharp-32-linear.png) + +![Дуплеты на C# против SQLite, 32 бит, связи, логарифмическая шкала](docs/benchmarks/links-csharp-32.png) #### Тесты с 64-битным пространством адресов/идентификаторов ##### 100,000 связей -_10 повторов после прогрева, медианное время одной операции. SQLite 3.53.3, Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB, [запуск GitHub Actions](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) от 2026-10-05._ +_10 повторов после прогрева, медианное время одной операции. SQLite 3.53.3; Platform.Data.Doublets 0.18.1; .NET SDK 10.0.401. Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB, [запуск GitHub Actions](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) от 2026-10-05._ | Хранилище | Создание | Чтение всех | Чтение по id | Поиск (from, to) | Чтение по from | Чтение по to | Обновление | Удаление | Размер файлов | | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | @@ -327,7 +343,7 @@ _10 повторов после прогрева, медианное время ##### 1,000,000 связей -_3 повтора после прогрева, медианное время одной операции. SQLite 3.53.3, Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB, [запуск GitHub Actions](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) от 2026-10-05._ +_3 повтора после прогрева, медианное время одной операции. SQLite 3.53.3; Platform.Data.Doublets 0.18.1; .NET SDK 10.0.401. Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB, [запуск GitHub Actions](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) от 2026-10-05._ | Хранилище | Создание | Чтение всех | Чтение по id | Поиск (from, to) | Чтение по from | Чтение по to | Обновление | Удаление | Размер файлов | | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | @@ -342,7 +358,7 @@ _3 повтора после прогрева, медианное время о ##### 10,000,000 связей -_1 повтор после прогрева, медианное время одной операции. SQLite 3.53.3, Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB, [запуск GitHub Actions](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) от 2026-10-05._ +_1 повтор после прогрева, медианное время одной операции. SQLite 3.53.3; Platform.Data.Doublets 0.18.1; .NET SDK 10.0.401. Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB, [запуск GitHub Actions](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) от 2026-10-05._ | Хранилище | Создание | Чтение всех | Чтение по id | Поиск (from, to) | Чтение по from | Чтение по to | Обновление | Удаление | Размер файлов | | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | @@ -355,7 +371,9 @@ _1 повтор после прогрева, медианное время од | Doublets Split Volatile | 449 ns (в 10× быстрее) | 12.6 ns (в 36.6× быстрее) | 294 ns (в 7.12× быстрее) | 443 ns (в 5.32× быстрее) | 317 ns (в 7.65× быстрее) | 330 ns (в 7.26× быстрее) | 859 ns (в 11.3× быстрее) | 688 ns (в 8.95× быстрее) | — | | Doublets Split NonVolatile | 1.27 µs (в 8.22× быстрее) | 12.6 ns (в 36.8× быстрее) | 331 ns (в 8.88× быстрее) | 484 ns (в 7.17× быстрее) | 339 ns (в 9.86× быстрее) | 351 ns (в 9.55× быстрее) | 1.19 µs (в 21.4× быстрее) | 853 ns (в 18.1× быстрее) | 800.0 MiB | -![Дуплеты на C# против SQLite, 64 бит, связи](docs/benchmarks/links-csharp-64.png) +![Дуплеты на C# против SQLite, 64 бит, связи, линейная шкала](docs/benchmarks/links-csharp-64-linear.png) + +![Дуплеты на C# против SQLite, 64 бит, связи, логарифмическая шкала](docs/benchmarks/links-csharp-64.png) ## Дуплеты против SQLite как хранилище объектов @@ -365,7 +383,7 @@ _1 повтор после прогрева, медианное время од ##### 100,000 записей блога -_5 повторов после прогрева, медианное время одной операции. SQLite 3.53.2, Ubuntu 24.04.5 LTS, AMD EPYC 9V45 96-Core Processor, 4 cores, 16 GiB, [запуск GitHub Actions](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) от 2026-10-05._ +_5 повторов после прогрева, медианное время одной операции. SQLite 3.53.2; doublets 0.5.0; rustc 1.99.0 (b940084d7 2026-09-28). Ubuntu 24.04.5 LTS, AMD EPYC 9V45 96-Core Processor, 4 cores, 16 GiB, [запуск GitHub Actions](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) от 2026-10-05._ | Хранилище | Создание | Чтение всех | Чтение по id | Удаление | Размер файлов | | --- | ---: | ---: | ---: | ---: | ---: | @@ -382,7 +400,7 @@ _5 повторов после прогрева, медианное время ##### 1,000,000 записей блога -_1 повтор после прогрева, медианное время одной операции. SQLite 3.53.2, Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB, [запуск GitHub Actions](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) от 2026-10-05._ +_1 повтор после прогрева, медианное время одной операции. SQLite 3.53.2; doublets 0.5.0; rustc 1.99.0 (b940084d7 2026-09-28). Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB, [запуск GitHub Actions](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) от 2026-10-05._ | Хранилище | Создание | Чтение всех | Чтение по id | Удаление | Размер файлов | | --- | ---: | ---: | ---: | ---: | ---: | @@ -397,13 +415,15 @@ _1 повтор после прогрева, медианное время од | Doublets Split NonVolatile Cached | 6.46 µs (в 1.89× медленнее) | 882 ns (в 1.34× медленнее) | 961 ns (в 4.23× быстрее) | 6.75 µs (в 1.68× быстрее) | 400.0 MiB | | Doublets Split NonVolatile Uncached | 36.6 µs (в 10.7× медленнее) | 14.5 µs (в 22.1× медленнее) | 14.8 µs (в 3.64× медленнее) | 6.98 µs (в 1.63× быстрее) | 400.0 MiB | -![Дуплеты на Rust против SQLite, 32 бит, объекты](docs/benchmarks/objects-rust-32.png) +![Дуплеты на Rust против SQLite, 32 бит, объекты, линейная шкала](docs/benchmarks/objects-rust-32-linear.png) + +![Дуплеты на Rust против SQLite, 32 бит, объекты, логарифмическая шкала](docs/benchmarks/objects-rust-32.png) #### Тесты с 64-битным пространством адресов/идентификаторов ##### 100,000 записей блога -_5 повторов после прогрева, медианное время одной операции. SQLite 3.53.2, Ubuntu 24.04.5 LTS, AMD EPYC 9V45 96-Core Processor, 4 cores, 16 GiB, [запуск GitHub Actions](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) от 2026-10-05._ +_5 повторов после прогрева, медианное время одной операции. SQLite 3.53.2; doublets 0.5.0; rustc 1.99.0 (b940084d7 2026-09-28). Ubuntu 24.04.5 LTS, AMD EPYC 9V45 96-Core Processor, 4 cores, 16 GiB, [запуск GitHub Actions](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) от 2026-10-05._ | Хранилище | Создание | Чтение всех | Чтение по id | Удаление | Размер файлов | | --- | ---: | ---: | ---: | ---: | ---: | @@ -420,7 +440,7 @@ _5 повторов после прогрева, медианное время ##### 1,000,000 записей блога -_1 повтор после прогрева, медианное время одной операции. SQLite 3.53.2, Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB, [запуск GitHub Actions](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) от 2026-10-05._ +_1 повтор после прогрева, медианное время одной операции. SQLite 3.53.2; doublets 0.5.0; rustc 1.99.0 (b940084d7 2026-09-28). Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB, [запуск GitHub Actions](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) от 2026-10-05._ | Хранилище | Создание | Чтение всех | Чтение по id | Удаление | Размер файлов | | --- | ---: | ---: | ---: | ---: | ---: | @@ -435,7 +455,9 @@ _1 повтор после прогрева, медианное время од | Doublets Split NonVolatile Cached | 8.1 µs (в 2.3× медленнее) | 952 ns (в 1.5× медленнее) | 1.02 µs (в 3.45× быстрее) | 7.65 µs (в 1.32× быстрее) | 800.0 MiB | | Doublets Split NonVolatile Uncached | 55.9 µs (в 15.9× медленнее) | 14.7 µs (в 23.1× медленнее) | 15.4 µs (в 4.35× медленнее) | 8.04 µs (в 1.26× быстрее) | 800.0 MiB | -![Дуплеты на Rust против SQLite, 64 бит, объекты](docs/benchmarks/objects-rust-64.png) +![Дуплеты на Rust против SQLite, 64 бит, объекты, линейная шкала](docs/benchmarks/objects-rust-64-linear.png) + +![Дуплеты на Rust против SQLite, 64 бит, объекты, логарифмическая шкала](docs/benchmarks/objects-rust-64.png) ### Дуплеты на C# против SQLite @@ -443,7 +465,7 @@ _1 повтор после прогрева, медианное время од ##### 100,000 записей блога -_5 повторов после прогрева, медианное время одной операции. SQLite 3.53.3, Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB, [запуск GitHub Actions](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) от 2026-10-05._ PostgreSQL 18.3 (Debian 18.3-1.pgdg13+1); Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3.0; EF Core 10.0.4.0. +_5 повторов после прогрева, медианное время одной операции. SQLite 3.53.3; Platform.Data.Doublets 0.18.1; .NET SDK 10.0.401. Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB, [запуск GitHub Actions](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) от 2026-10-05. PostgreSQL 18.3 (Debian 18.3-1.pgdg13+1); Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3.0; EF Core 10.0.4.0._ | Хранилище | Создание | Чтение всех | Чтение по id | Удаление | Размер файлов | Размер на сервере | | --- | ---: | ---: | ---: | ---: | ---: | ---: | @@ -463,7 +485,7 @@ _5 повторов после прогрева, медианное время ##### 1,000,000 записей блога -_1 повтор после прогрева, медианное время одной операции. SQLite 3.53.3, Ubuntu 24.04.5 LTS, INTEL(R) XEON(R) PLATINUM 8573C, 4 cores, 16 GiB, [запуск GitHub Actions](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) от 2026-10-05._ PostgreSQL 18.3 (Debian 18.3-1.pgdg13+1); Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3.0; EF Core 10.0.4.0. +_1 повтор после прогрева, медианное время одной операции. SQLite 3.53.3; Platform.Data.Doublets 0.18.1; .NET SDK 10.0.401. Ubuntu 24.04.5 LTS, INTEL(R) XEON(R) PLATINUM 8573C, 4 cores, 16 GiB, [запуск GitHub Actions](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) от 2026-10-05. PostgreSQL 18.3 (Debian 18.3-1.pgdg13+1); Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3.0; EF Core 10.0.4.0._ | Хранилище | Создание | Чтение всех | Чтение по id | Удаление | Размер файлов | Размер на сервере | | --- | ---: | ---: | ---: | ---: | ---: | ---: | @@ -481,13 +503,15 @@ _1 повтор после прогрева, медианное время од | Doublets Split NonVolatile Cached | 21 µs (в 2.32× медленнее) | 14 µs (в 3.79× медленнее) | 7.1 µs (≈ так же) | 10.8 µs (≈ так же) | 400.0 MiB | — | | Doublets Split NonVolatile Uncached | 142 µs (в 15.7× медленнее) | 460 µs (в 125× медленнее) | 460 µs (в 67.5× медленнее) | 11.2 µs (≈ так же) | 400.0 MiB | — | -![Дуплеты на C# против SQLite, 32 бит, объекты](docs/benchmarks/objects-csharp-32.png) +![Дуплеты на C# против SQLite, 32 бит, объекты, линейная шкала](docs/benchmarks/objects-csharp-32-linear.png) + +![Дуплеты на C# против SQLite, 32 бит, объекты, логарифмическая шкала](docs/benchmarks/objects-csharp-32.png) #### Тесты с 64-битным пространством адресов/идентификаторов ##### 100,000 записей блога -_5 повторов после прогрева, медианное время одной операции. SQLite 3.53.3, Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB, [запуск GitHub Actions](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) от 2026-10-05._ PostgreSQL 18.3 (Debian 18.3-1.pgdg13+1); Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3.0; EF Core 10.0.4.0. +_5 повторов после прогрева, медианное время одной операции. SQLite 3.53.3; Platform.Data.Doublets 0.18.1; .NET SDK 10.0.401. Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB, [запуск GitHub Actions](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) от 2026-10-05. PostgreSQL 18.3 (Debian 18.3-1.pgdg13+1); Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3.0; EF Core 10.0.4.0._ | Хранилище | Создание | Чтение всех | Чтение по id | Удаление | Размер файлов | Размер на сервере | | --- | ---: | ---: | ---: | ---: | ---: | ---: | @@ -507,7 +531,7 @@ _5 повторов после прогрева, медианное время ##### 1,000,000 записей блога -_1 повтор после прогрева, медианное время одной операции. SQLite 3.53.3, Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB, [запуск GitHub Actions](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) от 2026-10-05._ PostgreSQL 18.3 (Debian 18.3-1.pgdg13+1); Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3.0; EF Core 10.0.4.0. +_1 повтор после прогрева, медианное время одной операции. SQLite 3.53.3; Platform.Data.Doublets 0.18.1; .NET SDK 10.0.401. Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB, [запуск GitHub Actions](https://github.com/linksplatform/Comparisons.SQLiteVSDoublets/actions/runs/37381004025) от 2026-10-05. PostgreSQL 18.3 (Debian 18.3-1.pgdg13+1); Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3.0; EF Core 10.0.4.0._ | Хранилище | Создание | Чтение всех | Чтение по id | Удаление | Размер файлов | Размер на сервере | | --- | ---: | ---: | ---: | ---: | ---: | ---: | @@ -525,162 +549,17 @@ _1 повтор после прогрева, медианное время од | Doublets Split NonVolatile Cached | 27.6 µs (в 3.25× медленнее) | 11.3 µs (в 3.14× медленнее) | 7.63 µs (в 1.1× медленнее) | 13 µs (в 1.3× быстрее) | 800.0 MiB | — | | Doublets Split NonVolatile Uncached | 121 µs (в 14.2× медленнее) | 323 µs (в 90× медленнее) | 320 µs (в 46.2× медленнее) | 12.5 µs (в 1.35× быстрее) | 800.0 MiB | — | -![Дуплеты на C# против SQLite, 64 бит, объекты](docs/benchmarks/objects-csharp-64.png) - - - -## Исходное сравнение - -Исходное сравнение объектов на C# и его исторические результаты. - - - -### SQLite - -```C# -using System.Linq; -using Comparisons.SQLiteVSDoublets.Model; - -namespace Comparisons.SQLiteVSDoublets.SQLite -{ - public class SQLiteTestRun : TestRun - { - public SQLiteTestRun(string dbFilename) : base(dbFilename) { } - - public override void Prepare() - { - using var dbContext = new SQLiteDbContext(DbFilename); - dbContext.Database.EnsureCreated(); - } - - public override void CreateList() - { - using var dbContext = new SQLiteDbContext(DbFilename); - dbContext.BlogPosts.AddRange(BlogPosts.List); - dbContext.SaveChanges(); - } - - public override void ReadList() - { - using var dbContext = new SQLiteDbContext(DbFilename); - foreach (var blogPost in dbContext.BlogPosts) - { - ReadBlogPosts.Add(blogPost); - } - } - - public override void DeleteList() - { - using var dbContext = new SQLiteDbContext(DbFilename); - var blogPostsToDelete = dbContext.BlogPosts.ToList(); - dbContext.BlogPosts.RemoveRange(blogPostsToDelete); - dbContext.SaveChanges(); - } - } -} -``` - -### Дуплеты - -``` C# -using System.IO; -using Platform.IO; -using Comparisons.SQLiteVSDoublets.Model; - -namespace Comparisons.SQLiteVSDoublets.Doublets -{ - public class DoubletsTestRun : TestRun - { - public string DbIndexFilename { get; } - - public DoubletsTestRun(string dbFilename) : base(dbFilename) => DbIndexFilename = $"{Path.GetFileNameWithoutExtension(dbFilename)}.links.index"; - - public override void Prepare() - { - using var dbContext = new DoubletsDbContext(DbFilename, DbIndexFilename); - } - - public override void CreateList() - { - using var dbContext = new DoubletsDbContext(DbFilename, DbIndexFilename); - foreach (var blogPost in BlogPosts.List) - { - dbContext.SaveBlogPost(blogPost); - } - } - - public override void ReadList() - { - using var dbContext = new DoubletsDbContext(DbFilename, DbIndexFilename); - foreach (var blogPost in dbContext.BlogPosts) - { - ReadBlogPosts.Add(blogPost); - } - } - - public override void DeleteList() - { - using var dbContext = new DoubletsDbContext(DbFilename, DbIndexFilename); - var blogPostsToDelete = dbContext.BlogPosts; - foreach (var blogPost in blogPostsToDelete) - { - dbContext.Delete((uint)blogPost.Id); - } - } - - protected override void DeleteDatabase() - { - File.Delete(DbFilename); - File.Delete(DbIndexFilename); - } - - protected override long GetDatabaseSizeInBytes() => FileHelpers.GetSize(DbFilename) + FileHelpers.GetSize(DbIndexFilename); - } -} -``` - -### [Результат](https://www.icloud.com/keynote/0cYVNWkWD5RLU0k-XIBs3qWkA#Sqlite_vs_Doublets) - -#### Производительность - -![Изображение с результатом сравнения производительности SQLite и Дуплетов.](https://raw.githubusercontent.com/linksplatform/Documentation/master/doc/Examples/sqlite_vs_doublets_performance.png "Результат сравнения производительности SQLite и Дуплетов") - -#### Использование пространства на диске - -![Изображение с результатом сравнения использования пространства на диске SQLite и Дуплетов.](https://raw.githubusercontent.com/linksplatform/Documentation/master/doc/Examples/sqlite_vs_doublets_disk_usage.png "Результат сравнения использования пространства на диске SQLite и Дуплетов") - -#### Использование оперативной памяти - -![Изображение с результатом сравнения использования оперативной памяти SQLite и Дуплетов.](https://raw.githubusercontent.com/linksplatform/Documentation/master/doc/Examples/sqlite_vs_doublets_ram_usage.png "Результат сравнения использования оперативной памяти SQLite и Дуплетов") +![Дуплеты на C# против SQLite, 64 бит, объекты, линейная шкала](docs/benchmarks/objects-csharp-64-linear.png) -#### Исходные данные +![Дуплеты на C# против SQLite, 64 бит, объекты, логарифмическая шкала](docs/benchmarks/objects-csharp-64.png) -``` ini +## Выводы -BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18362 -Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores -.NET Core SDK=3.0.100 - [Host] : .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), X64 RyuJIT - Job-AYEMIX : .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), X64 RyuJIT - -InvocationCount=1 IterationCount=1 UnrollFactor=1 -WarmupCount=2 - -``` - -| Method | N | Mean | Error | Gen 0 | Gen 1 | Gen 2 | Allocated | SizeAfterCreation | -|--------- |------- |------------:|------:|-------------:|------------:|------:|------------:|------------------:| -| **SQLite** | **1000** | **719.1 ms** | **NA** | **5000.0000** | **-** | **-** | **30.67 MB** | **925696** | -| Doublets | 1000 | 145.0 ms | NA | 34000.0000 | 1000.0000 | - | 139.37 MB | 767616 | -| **SQLite** | **10000** | **2,770.3 ms** | **NA** | **64000.0000** | **19000.0000** | **-** | **315.71 MB** | **9056256** | -| Doublets | 10000 | 1,003.8 ms | NA | 304000.0000 | 31000.0000 | - | 1220.55 MB | 6528256 | -| **SQLite** | **100000** | **35,853.8 ms** | **NA** | **680000.0000** | **151000.0000** | **-** | **3234.09 MB** | **90890240** | -| Doublets | 100000 | 13,083.4 ms | NA | 3088000.0000 | 328000.0000 | - | 12356.33 MB | 64192256 | +Каждое сравнение использует SQLite той же надёжности хранения и то же правило учёта шума, что и таблицы. Числа охватывают измеренные варианты, размеры и разрядности id и не являются общим рейтингом скорости. Использование ОЗУ не измеряется. +- Rust, связи: Дуплеты быстрее в 187, медленнее в 3, примерно равны SQLite в 2 сравнениях операций. +- C#, связи: Дуплеты быстрее в 192, медленнее в 0, примерно равны SQLite в 0 сравнениях операций. +- Rust, объекты: Дуплеты быстрее в 28, медленнее в 98, примерно равны SQLite в 2 сравнениях операций. +- C#, объекты: Дуплеты быстрее в 10, медленнее в 109, примерно равны SQLite в 9 сравнениях операций. - -### Заключение - -В этом конкретном сравнении Дуплеты работают быстрее и используют меньше памяти -на диске, но это достигается за счёт дополнительного использования оперативной -памяти (Sqlite использует её меньше). + diff --git a/cpp/README.md b/cpp/README.md deleted file mode 100644 index 0b0ecfa..0000000 --- a/cpp/README.md +++ /dev/null @@ -1,10 +0,0 @@ - -# C++ placeholder - -This directory contains an empty Conan manifest and no C++ implementation, -`CMakeLists.txt`, tests, or package metadata. The obsolete build and release -workflow was retired in issue #110; it could not build or publish this tree. - -A future C++ implementation must add a real build target and tests before -enabling CI. Use a supported Clang version on a pinned runner, Conan 2 profiles -and generators, pull request validation, and release jobs gated on passing tests. diff --git a/cpp/conanfile.txt b/cpp/conanfile.txt deleted file mode 100644 index a25eecc..0000000 --- a/cpp/conanfile.txt +++ /dev/null @@ -1,4 +0,0 @@ -[requires] - -[generators] -cmake diff --git a/docs/benchmarks/links-csharp-32-linear.png b/docs/benchmarks/links-csharp-32-linear.png new file mode 100644 index 0000000..b3908e9 Binary files /dev/null and b/docs/benchmarks/links-csharp-32-linear.png differ diff --git a/docs/benchmarks/links-csharp-32.png b/docs/benchmarks/links-csharp-32.png index aff9d3f..a408c70 100644 Binary files a/docs/benchmarks/links-csharp-32.png and b/docs/benchmarks/links-csharp-32.png differ diff --git a/docs/benchmarks/links-csharp-64-linear.png b/docs/benchmarks/links-csharp-64-linear.png new file mode 100644 index 0000000..ddc31b4 Binary files /dev/null and b/docs/benchmarks/links-csharp-64-linear.png differ diff --git a/docs/benchmarks/links-csharp-64.png b/docs/benchmarks/links-csharp-64.png index dae5a23..b431558 100644 Binary files a/docs/benchmarks/links-csharp-64.png and b/docs/benchmarks/links-csharp-64.png differ diff --git a/docs/benchmarks/links-rust-32-linear.png b/docs/benchmarks/links-rust-32-linear.png new file mode 100644 index 0000000..615ca54 Binary files /dev/null and b/docs/benchmarks/links-rust-32-linear.png differ diff --git a/docs/benchmarks/links-rust-32.png b/docs/benchmarks/links-rust-32.png index 005d45e..a0648cf 100644 Binary files a/docs/benchmarks/links-rust-32.png and b/docs/benchmarks/links-rust-32.png differ diff --git a/docs/benchmarks/links-rust-64-linear.png b/docs/benchmarks/links-rust-64-linear.png new file mode 100644 index 0000000..e0e9a65 Binary files /dev/null and b/docs/benchmarks/links-rust-64-linear.png differ diff --git a/docs/benchmarks/links-rust-64.png b/docs/benchmarks/links-rust-64.png index 1c2277c..a14d6c2 100644 Binary files a/docs/benchmarks/links-rust-64.png and b/docs/benchmarks/links-rust-64.png differ diff --git a/docs/benchmarks/objects-csharp-32-linear.png b/docs/benchmarks/objects-csharp-32-linear.png new file mode 100644 index 0000000..8fc6e03 Binary files /dev/null and b/docs/benchmarks/objects-csharp-32-linear.png differ diff --git a/docs/benchmarks/objects-csharp-32.png b/docs/benchmarks/objects-csharp-32.png index 5ca27b3..00345c0 100644 Binary files a/docs/benchmarks/objects-csharp-32.png and b/docs/benchmarks/objects-csharp-32.png differ diff --git a/docs/benchmarks/objects-csharp-64-linear.png b/docs/benchmarks/objects-csharp-64-linear.png new file mode 100644 index 0000000..49a1d26 Binary files /dev/null and b/docs/benchmarks/objects-csharp-64-linear.png differ diff --git a/docs/benchmarks/objects-csharp-64.png b/docs/benchmarks/objects-csharp-64.png index 5bd50ef..d1c6502 100644 Binary files a/docs/benchmarks/objects-csharp-64.png and b/docs/benchmarks/objects-csharp-64.png differ diff --git a/docs/benchmarks/objects-rust-32-linear.png b/docs/benchmarks/objects-rust-32-linear.png new file mode 100644 index 0000000..7046c58 Binary files /dev/null and b/docs/benchmarks/objects-rust-32-linear.png differ diff --git a/docs/benchmarks/objects-rust-32.png b/docs/benchmarks/objects-rust-32.png index a600e78..66328d0 100644 Binary files a/docs/benchmarks/objects-rust-32.png and b/docs/benchmarks/objects-rust-32.png differ diff --git a/docs/benchmarks/objects-rust-64-linear.png b/docs/benchmarks/objects-rust-64-linear.png new file mode 100644 index 0000000..fbd006a Binary files /dev/null and b/docs/benchmarks/objects-rust-64-linear.png differ diff --git a/docs/benchmarks/objects-rust-64.png b/docs/benchmarks/objects-rust-64.png index 7d8bada..326c1b7 100644 Binary files a/docs/benchmarks/objects-rust-64.png and b/docs/benchmarks/objects-rust-64.png differ diff --git a/docs/benchmarks/results/links-csharp-32-100000.json b/docs/benchmarks/results/links-csharp-32-100000.json index 0299704..94dc7ca 100644 --- a/docs/benchmarks/results/links-csharp-32-100000.json +++ b/docs/benchmarks/results/links-csharp-32-100000.json @@ -1159,5 +1159,11 @@ "source_sha": "4e269b47eb3e6d0a264b57f27b9a51832562a99b", "tree_sha": "412060b55aeed6f627c46da833f68ae21c71a6aa", "date": "2026-10-05", - "machine": "Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB" + "machine": "Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB", + "software": { + "libraries": { + "Platform.Data.Doublets": "0.18.1" + }, + "toolchain": ".NET SDK 10.0.401" + } } diff --git a/docs/benchmarks/results/links-csharp-32-1000000.json b/docs/benchmarks/results/links-csharp-32-1000000.json index 6119abb..2bbec2d 100644 --- a/docs/benchmarks/results/links-csharp-32-1000000.json +++ b/docs/benchmarks/results/links-csharp-32-1000000.json @@ -711,5 +711,11 @@ "source_sha": "4e269b47eb3e6d0a264b57f27b9a51832562a99b", "tree_sha": "412060b55aeed6f627c46da833f68ae21c71a6aa", "date": "2026-10-05", - "machine": "Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB" + "machine": "Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB", + "software": { + "libraries": { + "Platform.Data.Doublets": "0.18.1" + }, + "toolchain": ".NET SDK 10.0.401" + } } diff --git a/docs/benchmarks/results/links-csharp-32-10000000.json b/docs/benchmarks/results/links-csharp-32-10000000.json index 790a0cd..c54f015 100644 --- a/docs/benchmarks/results/links-csharp-32-10000000.json +++ b/docs/benchmarks/results/links-csharp-32-10000000.json @@ -583,5 +583,11 @@ "source_sha": "4e269b47eb3e6d0a264b57f27b9a51832562a99b", "tree_sha": "412060b55aeed6f627c46da833f68ae21c71a6aa", "date": "2026-10-05", - "machine": "Ubuntu 24.04.5 LTS, INTEL(R) XEON(R) PLATINUM 8573C, 4 cores, 16 GiB" + "machine": "Ubuntu 24.04.5 LTS, INTEL(R) XEON(R) PLATINUM 8573C, 4 cores, 16 GiB", + "software": { + "libraries": { + "Platform.Data.Doublets": "0.18.1" + }, + "toolchain": ".NET SDK 10.0.401" + } } diff --git a/docs/benchmarks/results/links-csharp-64-100000.json b/docs/benchmarks/results/links-csharp-64-100000.json index c7449f1..9a80a0e 100644 --- a/docs/benchmarks/results/links-csharp-64-100000.json +++ b/docs/benchmarks/results/links-csharp-64-100000.json @@ -1159,5 +1159,11 @@ "source_sha": "4e269b47eb3e6d0a264b57f27b9a51832562a99b", "tree_sha": "412060b55aeed6f627c46da833f68ae21c71a6aa", "date": "2026-10-05", - "machine": "Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB" + "machine": "Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB", + "software": { + "libraries": { + "Platform.Data.Doublets": "0.18.1" + }, + "toolchain": ".NET SDK 10.0.401" + } } diff --git a/docs/benchmarks/results/links-csharp-64-1000000.json b/docs/benchmarks/results/links-csharp-64-1000000.json index e929eb7..5d7f84d 100644 --- a/docs/benchmarks/results/links-csharp-64-1000000.json +++ b/docs/benchmarks/results/links-csharp-64-1000000.json @@ -711,5 +711,11 @@ "source_sha": "4e269b47eb3e6d0a264b57f27b9a51832562a99b", "tree_sha": "412060b55aeed6f627c46da833f68ae21c71a6aa", "date": "2026-10-05", - "machine": "Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB" + "machine": "Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB", + "software": { + "libraries": { + "Platform.Data.Doublets": "0.18.1" + }, + "toolchain": ".NET SDK 10.0.401" + } } diff --git a/docs/benchmarks/results/links-csharp-64-10000000.json b/docs/benchmarks/results/links-csharp-64-10000000.json index 23b5bd2..1066ce7 100644 --- a/docs/benchmarks/results/links-csharp-64-10000000.json +++ b/docs/benchmarks/results/links-csharp-64-10000000.json @@ -583,5 +583,11 @@ "source_sha": "4e269b47eb3e6d0a264b57f27b9a51832562a99b", "tree_sha": "412060b55aeed6f627c46da833f68ae21c71a6aa", "date": "2026-10-05", - "machine": "Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB" + "machine": "Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB", + "software": { + "libraries": { + "Platform.Data.Doublets": "0.18.1" + }, + "toolchain": ".NET SDK 10.0.401" + } } diff --git a/docs/benchmarks/results/links-rust-32-100000.json b/docs/benchmarks/results/links-rust-32-100000.json index 1068928..e4cd651 100644 --- a/docs/benchmarks/results/links-rust-32-100000.json +++ b/docs/benchmarks/results/links-rust-32-100000.json @@ -865,5 +865,11 @@ "source_sha": "4e269b47eb3e6d0a264b57f27b9a51832562a99b", "tree_sha": "412060b55aeed6f627c46da833f68ae21c71a6aa", "date": "2026-10-05", - "machine": "Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB" + "machine": "Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB", + "software": { + "libraries": { + "doublets": "0.5.0" + }, + "toolchain": "rustc 1.99.0 (b940084d7 2026-09-28)" + } } diff --git a/docs/benchmarks/results/links-rust-32-1000000.json b/docs/benchmarks/results/links-rust-32-1000000.json index 35318f7..ff0afb5 100644 --- a/docs/benchmarks/results/links-rust-32-1000000.json +++ b/docs/benchmarks/results/links-rust-32-1000000.json @@ -529,5 +529,11 @@ "source_sha": "4e269b47eb3e6d0a264b57f27b9a51832562a99b", "tree_sha": "412060b55aeed6f627c46da833f68ae21c71a6aa", "date": "2026-10-05", - "machine": "Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB" + "machine": "Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB", + "software": { + "libraries": { + "doublets": "0.5.0" + }, + "toolchain": "rustc 1.99.0 (b940084d7 2026-09-28)" + } } diff --git a/docs/benchmarks/results/links-rust-32-10000000.json b/docs/benchmarks/results/links-rust-32-10000000.json index 1edff3a..2ef23df 100644 --- a/docs/benchmarks/results/links-rust-32-10000000.json +++ b/docs/benchmarks/results/links-rust-32-10000000.json @@ -433,5 +433,11 @@ "source_sha": "4e269b47eb3e6d0a264b57f27b9a51832562a99b", "tree_sha": "412060b55aeed6f627c46da833f68ae21c71a6aa", "date": "2026-10-05", - "machine": "Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB" + "machine": "Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB", + "software": { + "libraries": { + "doublets": "0.5.0" + }, + "toolchain": "rustc 1.99.0 (b940084d7 2026-09-28)" + } } diff --git a/docs/benchmarks/results/links-rust-64-100000.json b/docs/benchmarks/results/links-rust-64-100000.json index 5b9f627..38b1129 100644 --- a/docs/benchmarks/results/links-rust-64-100000.json +++ b/docs/benchmarks/results/links-rust-64-100000.json @@ -865,5 +865,11 @@ "source_sha": "4e269b47eb3e6d0a264b57f27b9a51832562a99b", "tree_sha": "412060b55aeed6f627c46da833f68ae21c71a6aa", "date": "2026-10-05", - "machine": "Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB" + "machine": "Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB", + "software": { + "libraries": { + "doublets": "0.5.0" + }, + "toolchain": "rustc 1.99.0 (b940084d7 2026-09-28)" + } } diff --git a/docs/benchmarks/results/links-rust-64-1000000.json b/docs/benchmarks/results/links-rust-64-1000000.json index 4d821df..4c18236 100644 --- a/docs/benchmarks/results/links-rust-64-1000000.json +++ b/docs/benchmarks/results/links-rust-64-1000000.json @@ -529,5 +529,11 @@ "source_sha": "4e269b47eb3e6d0a264b57f27b9a51832562a99b", "tree_sha": "412060b55aeed6f627c46da833f68ae21c71a6aa", "date": "2026-10-05", - "machine": "Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB" + "machine": "Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB", + "software": { + "libraries": { + "doublets": "0.5.0" + }, + "toolchain": "rustc 1.99.0 (b940084d7 2026-09-28)" + } } diff --git a/docs/benchmarks/results/links-rust-64-10000000.json b/docs/benchmarks/results/links-rust-64-10000000.json index 440fbe5..f7addfa 100644 --- a/docs/benchmarks/results/links-rust-64-10000000.json +++ b/docs/benchmarks/results/links-rust-64-10000000.json @@ -433,5 +433,11 @@ "source_sha": "4e269b47eb3e6d0a264b57f27b9a51832562a99b", "tree_sha": "412060b55aeed6f627c46da833f68ae21c71a6aa", "date": "2026-10-05", - "machine": "Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB" + "machine": "Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB", + "software": { + "libraries": { + "doublets": "0.5.0" + }, + "toolchain": "rustc 1.99.0 (b940084d7 2026-09-28)" + } } diff --git a/docs/benchmarks/results/objects-csharp-32-100000.json b/docs/benchmarks/results/objects-csharp-32-100000.json index 66776a8..651072e 100644 --- a/docs/benchmarks/results/objects-csharp-32-100000.json +++ b/docs/benchmarks/results/objects-csharp-32-100000.json @@ -732,5 +732,11 @@ "source_sha": "4e269b47eb3e6d0a264b57f27b9a51832562a99b", "tree_sha": "412060b55aeed6f627c46da833f68ae21c71a6aa", "date": "2026-10-05", - "machine": "Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB" + "machine": "Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB", + "software": { + "libraries": { + "Platform.Data.Doublets": "0.18.1" + }, + "toolchain": ".NET SDK 10.0.401" + } } diff --git a/docs/benchmarks/results/objects-csharp-32-1000000.json b/docs/benchmarks/results/objects-csharp-32-1000000.json index 91004d8..7a4e962 100644 --- a/docs/benchmarks/results/objects-csharp-32-1000000.json +++ b/docs/benchmarks/results/objects-csharp-32-1000000.json @@ -524,5 +524,11 @@ "source_sha": "4e269b47eb3e6d0a264b57f27b9a51832562a99b", "tree_sha": "412060b55aeed6f627c46da833f68ae21c71a6aa", "date": "2026-10-05", - "machine": "Ubuntu 24.04.5 LTS, INTEL(R) XEON(R) PLATINUM 8573C, 4 cores, 16 GiB" + "machine": "Ubuntu 24.04.5 LTS, INTEL(R) XEON(R) PLATINUM 8573C, 4 cores, 16 GiB", + "software": { + "libraries": { + "Platform.Data.Doublets": "0.18.1" + }, + "toolchain": ".NET SDK 10.0.401" + } } diff --git a/docs/benchmarks/results/objects-csharp-64-100000.json b/docs/benchmarks/results/objects-csharp-64-100000.json index 0666978..75783d1 100644 --- a/docs/benchmarks/results/objects-csharp-64-100000.json +++ b/docs/benchmarks/results/objects-csharp-64-100000.json @@ -732,5 +732,11 @@ "source_sha": "4e269b47eb3e6d0a264b57f27b9a51832562a99b", "tree_sha": "412060b55aeed6f627c46da833f68ae21c71a6aa", "date": "2026-10-05", - "machine": "Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB" + "machine": "Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB", + "software": { + "libraries": { + "Platform.Data.Doublets": "0.18.1" + }, + "toolchain": ".NET SDK 10.0.401" + } } diff --git a/docs/benchmarks/results/objects-csharp-64-1000000.json b/docs/benchmarks/results/objects-csharp-64-1000000.json index cd27699..7bb4544 100644 --- a/docs/benchmarks/results/objects-csharp-64-1000000.json +++ b/docs/benchmarks/results/objects-csharp-64-1000000.json @@ -524,5 +524,11 @@ "source_sha": "4e269b47eb3e6d0a264b57f27b9a51832562a99b", "tree_sha": "412060b55aeed6f627c46da833f68ae21c71a6aa", "date": "2026-10-05", - "machine": "Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB" + "machine": "Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB", + "software": { + "libraries": { + "Platform.Data.Doublets": "0.18.1" + }, + "toolchain": ".NET SDK 10.0.401" + } } diff --git a/docs/benchmarks/results/objects-rust-32-100000.json b/docs/benchmarks/results/objects-rust-32-100000.json index 54c4f90..3db654d 100644 --- a/docs/benchmarks/results/objects-rust-32-100000.json +++ b/docs/benchmarks/results/objects-rust-32-100000.json @@ -553,5 +553,11 @@ "source_sha": "4e269b47eb3e6d0a264b57f27b9a51832562a99b", "tree_sha": "412060b55aeed6f627c46da833f68ae21c71a6aa", "date": "2026-10-05", - "machine": "Ubuntu 24.04.5 LTS, AMD EPYC 9V45 96-Core Processor, 4 cores, 16 GiB" + "machine": "Ubuntu 24.04.5 LTS, AMD EPYC 9V45 96-Core Processor, 4 cores, 16 GiB", + "software": { + "libraries": { + "doublets": "0.5.0" + }, + "toolchain": "rustc 1.99.0 (b940084d7 2026-09-28)" + } } diff --git a/docs/benchmarks/results/objects-rust-32-1000000.json b/docs/benchmarks/results/objects-rust-32-1000000.json index 41a9a7e..529efae 100644 --- a/docs/benchmarks/results/objects-rust-32-1000000.json +++ b/docs/benchmarks/results/objects-rust-32-1000000.json @@ -393,5 +393,11 @@ "source_sha": "4e269b47eb3e6d0a264b57f27b9a51832562a99b", "tree_sha": "412060b55aeed6f627c46da833f68ae21c71a6aa", "date": "2026-10-05", - "machine": "Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB" + "machine": "Ubuntu 24.04.5 LTS, AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GiB", + "software": { + "libraries": { + "doublets": "0.5.0" + }, + "toolchain": "rustc 1.99.0 (b940084d7 2026-09-28)" + } } diff --git a/docs/benchmarks/results/objects-rust-64-100000.json b/docs/benchmarks/results/objects-rust-64-100000.json index 553c4bb..a80e09b 100644 --- a/docs/benchmarks/results/objects-rust-64-100000.json +++ b/docs/benchmarks/results/objects-rust-64-100000.json @@ -553,5 +553,11 @@ "source_sha": "4e269b47eb3e6d0a264b57f27b9a51832562a99b", "tree_sha": "412060b55aeed6f627c46da833f68ae21c71a6aa", "date": "2026-10-05", - "machine": "Ubuntu 24.04.5 LTS, AMD EPYC 9V45 96-Core Processor, 4 cores, 16 GiB" + "machine": "Ubuntu 24.04.5 LTS, AMD EPYC 9V45 96-Core Processor, 4 cores, 16 GiB", + "software": { + "libraries": { + "doublets": "0.5.0" + }, + "toolchain": "rustc 1.99.0 (b940084d7 2026-09-28)" + } } diff --git a/docs/benchmarks/results/objects-rust-64-1000000.json b/docs/benchmarks/results/objects-rust-64-1000000.json index b406af2..4f19373 100644 --- a/docs/benchmarks/results/objects-rust-64-1000000.json +++ b/docs/benchmarks/results/objects-rust-64-1000000.json @@ -393,5 +393,11 @@ "source_sha": "4e269b47eb3e6d0a264b57f27b9a51832562a99b", "tree_sha": "412060b55aeed6f627c46da833f68ae21c71a6aa", "date": "2026-10-05", - "machine": "Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB" + "machine": "Ubuntu 24.04.5 LTS, AMD EPYC 7763 64-Core Processor, 4 cores, 16 GiB", + "software": { + "libraries": { + "doublets": "0.5.0" + }, + "toolchain": "rustc 1.99.0 (b940084d7 2026-09-28)" + } } diff --git a/experiments/backfill_benchmark_provenance.py b/experiments/backfill_benchmark_provenance.py new file mode 100644 index 0000000..700b949 --- /dev/null +++ b/experiments/backfill_benchmark_provenance.py @@ -0,0 +1,70 @@ +"""Recover provenance for committed results from their original CI logs and source commit.""" + +import argparse +import json +import re +import subprocess # nosec B404 # Only local git object reads, with argument lists and no shell. +import sys +import tempfile +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts")) +from benchmark_provenance import record # noqa: E402 +from ci_process import executable # noqa: E402 + + +def backfill(directory, log): + versions = {} + source_shas = {} + for line in log.read_text(encoding="utf-8").splitlines(): + parts = line.split("\t", 2) + if len(parts) != 3: + continue + job, _, timed_message = parts + message = timed_message.partition(" ")[2] + source = re.search(r"\bSOURCE_SHA: ([0-9a-f]{40})\b", message) + if source: + source_shas[job] = source[1] + if re.fullmatch(r"rustc \d+\.\d+\.\d+ .*", message): + versions[job] = message + match = re.search(r"\.NET Core SDK with version '([^']+)'", message) + if match: + versions[job] = f".NET SDK {match[1]}" + for path in sorted(directory.glob("*.json")): + data = json.loads(path.read_text(encoding="utf-8")) + language = "rust" if data["language"] == "Rust" else "csharp" + job = f"{language} {data['category']} {data['bits']} bit {data['size']}" + toolchain = versions[job] + sha = data["source_sha"] + if not re.fullmatch(r"[0-9a-f]{40}", sha): + raise ValueError(f"Invalid measured source SHA: {sha}") + if source_shas.get(job) != sha: + raise ValueError(f"{path}: logs do not match the measured source commit") + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + manifest = ( + "rust/Cargo.lock" if language == "rust" else "csharp/SQLiteVSDoublets/SQLiteVSDoublets.csproj" + ) + original = subprocess.run( # nosec B603 # Validated SHA and fixed manifest paths; no shell. + [executable("git"), "show", f"{sha}:{manifest}"], + capture_output=True, + text=True, + check=True, + ).stdout + target = root / manifest + target.parent.mkdir(parents=True) + target.write_text(original, encoding="utf-8") + record(path, toolchain, root) + print(f"{path.name}: {toolchain}, libraries from {sha}") + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("results", type=Path) + parser.add_argument("log", type=Path, help="gh run view RUN --log output from the measured run") + args = parser.parse_args() + backfill(args.results, args.log) + + +if __name__ == "__main__": + main() diff --git a/scripts/benchmark_provenance.py b/scripts/benchmark_provenance.py new file mode 100644 index 0000000..8b7964e --- /dev/null +++ b/scripts/benchmark_provenance.py @@ -0,0 +1,48 @@ +"""Record the library and toolchain versions used by a benchmark job.""" + +import argparse +import json +import re +import tomllib +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] + + +def library_versions(language, root=ROOT): + """Read the Rust lockfile or C# package reference for the measured Doublets library.""" + if language == "Rust": + lock = tomllib.loads((root / "rust/Cargo.lock").read_text(encoding="utf-8")) + versions = {entry["version"] for entry in lock["package"] if entry["name"] == "doublets"} + if len(versions) != 1: + raise ValueError("Cargo.lock must resolve exactly one doublets version") + return {"doublets": versions.pop()} + if language != "C#": + raise ValueError(f"Unknown benchmark language: {language}") + project = (root / "csharp/SQLiteVSDoublets/SQLiteVSDoublets.csproj").read_text(encoding="utf-8") + for reference in re.findall(r"]*>", project): + attributes = dict(re.findall(r'([\w]+)\s*=\s*"([^"]*)"', reference)) + if attributes.get("Include") == "Platform.Data.Doublets" and attributes.get("Version"): + return {"Platform.Data.Doublets": attributes["Version"]} + raise ValueError("Missing Platform.Data.Doublets version in the C# project") + + +def record(path, toolchain, root=ROOT): + """Add a software snapshot while preserving every measurement and existing metadata field.""" + if not toolchain.strip(): + raise ValueError("Missing measured toolchain version") + data = json.loads(path.read_text(encoding="utf-8")) + data["software"] = {"libraries": library_versions(data["language"], root), "toolchain": toolchain} + path.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("report", type=Path) + parser.add_argument("--toolchain", required=True, help="rustc --version or the measured .NET SDK version") + args = parser.parse_args() + record(args.report, args.toolchain) + + +if __name__ == "__main__": + main() diff --git a/scripts/benchmark_report.py b/scripts/benchmark_report.py index 7508196..3a73810 100644 --- a/scripts/benchmark_report.py +++ b/scripts/benchmark_report.py @@ -16,6 +16,8 @@ from pathlib import Path from typing import Any +from benchmark_provenance import library_versions + START_MARKER = "" END_MARKER = "" # The wide tables cannot be wrapped, and the hierarchy repeats headings under different parents. @@ -75,7 +77,14 @@ def russian_plural(count, one, few, many): "faster": "{ratio}× faster", "slower": "{ratio}× slower", "same": "≈ same", - "provenance": "_{repetitions} after a warm-up, median time per operation. SQLite {sqlite}, {machine}, {source}._", + "provenance": "{repetitions} after a warm-up, median time per operation. SQLite {sqlite}; {software}. {machine}, {source}.", + "unrecorded": "version at measurement not recorded", + "toolchain_missing": "toolchain version not recorded", + "log_scale": "log scale", + "linear_scale": "linear scale", + "conclusions": "Conclusions", + "summary": "{language}, {category}: {faster} faster, {slower} slower, {same} approximately equal Doublets operation comparisons with SQLite.", + "summary_note": "Each comparison uses SQLite of matching durability and the same noise rule as the tables. Counts span the measured variants, sizes and id widths; they are not an overall speed ranking. RAM usage is not measured.", "repetitions": lambda count: f"{count} repetition" + ("" if count == 1 else "s"), "machine": "unknown machine", "local": "a local run", @@ -110,7 +119,14 @@ def russian_plural(count, one, few, many): "faster": "в {ratio}× быстрее", "slower": "в {ratio}× медленнее", "same": "≈ так же", - "provenance": "_{repetitions} после прогрева, медианное время одной операции. SQLite {sqlite}, {machine}, {source}._", + "provenance": "{repetitions} после прогрева, медианное время одной операции. SQLite {sqlite}; {software}. {machine}, {source}.", + "unrecorded": "версия при измерении не записана", + "toolchain_missing": "версия среды разработки не записана", + "log_scale": "логарифмическая шкала", + "linear_scale": "линейная шкала", + "conclusions": "Выводы", + "summary": "{language}, {category}: Дуплеты быстрее в {faster}, медленнее в {slower}, примерно равны SQLite в {same} сравнениях операций.", + "summary_note": "Каждое сравнение использует SQLite той же надёжности хранения и то же правило учёта шума, что и таблицы. Числа охватывают измеренные варианты, размеры и разрядности id и не являются общим рейтингом скорости. Использование ОЗУ не измеряется.", "repetitions": lambda count: f"{count} повтор" + russian_plural(count, "", "а", "ов"), "machine": "неизвестная машина", "local": "локальный запуск", @@ -161,6 +177,18 @@ def validate_metadata(report): raise ValueError("invalid repetitions") if not report["sqlite_version"]: raise ValueError("missing SQLite version") + if "software" in report: + software = report["software"] + library = "doublets" if report["language"] == "Rust" else "Platform.Data.Doublets" + if ( + not isinstance(software, dict) + or not isinstance(software.get("libraries"), dict) + or not isinstance(software["libraries"].get(library), str) + or not software["libraries"][library].strip() + or not isinstance(software.get("toolchain"), str) + or not software["toolchain"].strip() + ): + raise ValueError("missing or invalid software provenance") def validate_variants(results): @@ -291,9 +319,21 @@ def table(report, text): def provenance(report, text): run = report.get("run_url") + software = report.get("software") + if software: + libraries = software["libraries"] + versions = "; ".join(f"{name} {version}" for name, version in libraries.items()) + versions += f"; {software['toolchain']}" + else: + # Older reports remain readable, but today's manifests are not measured provenance. + versions = "; ".join( + f"{name} {version}" for name, version in library_versions(report["language"]).items() + ) + versions += f" ({text['unrecorded']}); {text['toolchain_missing']}" value = text["provenance"].format( repetitions=text["repetitions"](report["repetitions"]), sqlite=report["sqlite_version"], + software=versions, machine=report.get("machine") or text["machine"], source=text["run"].format(url=run, date=report.get("date")) if run else text["local"], ) @@ -304,11 +344,44 @@ def provenance(report, text): f"Npgsql.EntityFrameworkCore.PostgreSQL {postgres['provider_version']}; " f"EF Core {postgres['ef_core_version']}." ) - return value + return f"_{value}_" + +def chart_name(category, language, bits, scale="log"): + suffix = "-linear" if scale == "linear" else "" + return f"{category}-{language.lower().replace('#', 'sharp')}-{bits}{suffix}.png" -def chart_name(category, language, bits): - return f"{category}-{language.lower().replace('#', 'sharp')}-{bits}.png" + +def conclusions(reports, text): + """Summarize the measured Doublets comparisons with the table's durability and noise rules.""" + lines = [f"## {text['conclusions']}", "", text["summary_note"], ""] + for category in CATEGORIES: + for language in LANGUAGES: + counts = {"faster": 0, "slower": 0, "same": 0} + for key, data in reports.items(): + if key[:2] != (category, language): + continue + variants = {result["variant"]: result for result in data["results"]} + for variant, result in variants.items(): + if not variant.startswith("Doublets_"): + continue + reference = variants[baseline(variant)] + for operation, measured in result["operations"].items(): + other = reference["operations"][operation] + outcome = ( + "same" + if comparison(measured, other, text) == text["same"] + else "faster" + if measured["median_ns"] < other["median_ns"] + else "slower" + ) + counts[outcome] += 1 + if sum(counts.values()): + lines.append( + "- " + + text["summary"].format(language=language, category=text["nouns"][category], **counts) + ) + return lines def section(reports, language_code, charts_link): @@ -336,11 +409,13 @@ def section(reports, language_code, charts_link): "", ] if charts_link is not None: - name = chart_name(category, language, bits) title = text["chart"].format( language=language, bits=bits, category=text["nouns"][category] ) - lines += [f"![{title}]({charts_link}/{name})", ""] + for scale in ("linear", "log"): + name = chart_name(category, language, bits, scale) + lines += [f"![{title}, {text[f'{scale}_scale']}]({charts_link}/{name})", ""] + lines += conclusions(reports, text) return "\n".join(lines).rstrip() + "\n" @@ -352,52 +427,77 @@ def replace_section(document, generated): def charts(reports, directory): - """One logarithmic bar chart per category, language and bits, with a panel per size.""" + """Linear and log charts per category, language and bits, with a panel per size.""" import matplotlib matplotlib.use("Agg") - import matplotlib.pyplot as plt - directory.mkdir(parents=True, exist_ok=True) written = [] for category in CATEGORIES: - operations = OPERATIONS[category] - labels = [TEXT["en"]["operations"][operation] for operation in operations] for language in LANGUAGES: for bits in BITS: sizes = sorted(key[3] for key in reports if key[:3] == (category, language, bits)) if not sizes: continue - figure, axes = plt.subplots( - 1, len(sizes), figsize=(6 * len(sizes), 5), squeeze=False, sharey=False - ) - for axis, count in zip(axes[0], sizes, strict=True): - results = reports[(category, language, bits, count)]["results"] - width = 0.8 / len(results) - for index, result in enumerate(results): - axis.bar( - [position + index * width for position in range(len(operations))], - [result["operations"][operation]["median_ns"] for operation in operations], - width, - label=result["variant"].replace("_", " "), - color=plt.get_cmap("tab10")(index % 10), - ) - axis.set_yscale("log") - axis.set_title(TEXT["en"]["size"][category].format(size=size(count))) - axis.set_ylabel("median ns per operation (log scale)") - axis.set_xticks([position + 0.4 - width / 2 for position in range(len(operations))]) - axis.set_xticklabels(labels, rotation=30, ha="right") - axis.grid(axis="y", which="major", alpha=0.3) - axes[0][0].legend(fontsize="small") - figure.suptitle(TEXT["en"]["chart"].format(language=language, bits=bits, category=category)) - figure.tight_layout() - path = directory / chart_name(category, language, bits) - figure.savefig(path, dpi=80) - plt.close(figure) - written.append(path) + for scale in ("linear", "log"): + path = directory / chart_name(category, language, bits, scale) + draw_chart(reports, category, language, bits, sizes, scale, path) + written.append(path) return written +def draw_chart(reports, category, language, bits, sizes, scale, path): + """Draw each size in its own panel, with a visibility floor only on the linear scale.""" + import matplotlib.pyplot as plt + + operations = OPERATIONS[category] + labels = [TEXT["en"]["operations"][operation] for operation in operations] + figure, axes = plt.subplots(1, len(sizes), figsize=(6 * len(sizes), 6), squeeze=False, sharey=False) + try: + for axis, count in zip(axes[0], sizes, strict=True): + results = reports[(category, language, bits, count)]["results"] + width = 0.8 / len(results) + maximum = max( + result["operations"][operation]["median_ns"] for result in results for operation in operations + ) + minimum = maximum * 0.005 if scale == "linear" else 0 + for index, result in enumerate(results): + axis.bar( + [position + index * width for position in range(len(operations))], + [max(result["operations"][operation]["median_ns"], minimum) for operation in operations], + width, + label=result["variant"].replace("_", " "), + color=plt.get_cmap("tab20" if len(results) > 10 else "tab10")(index), + ) + axis.set_yscale(scale) + if scale == "linear": + axis.set_ylim(bottom=0) + axis.set_title(TEXT["en"]["size"][category].format(size=size(count))) + axis.set_ylabel(f"median ns per operation ({scale} scale)") + axis.set_xticks([position + 0.4 - width / 2 for position in range(len(operations))]) + axis.set_xticklabels(labels, rotation=30, ha="right") + axis.grid(axis="y", which="major", alpha=0.3) + handles, names = axes[0][0].get_legend_handles_labels() + footer = 0.04 if scale == "linear" else 0 + figure.legend( + handles, names, loc="lower center", ncol=3, fontsize="small", bbox_to_anchor=(0.5, footer) + ) + figure.suptitle(TEXT["en"]["chart"].format(language=language, bits=bits, category=category)) + if scale == "linear": + figure.text( + 0.5, + 0.01, + "Bars below 0.5% of each panel's maximum are raised for visibility.\nSee tables for exact times.", + ha="center", + fontsize="small", + ) + legend_height = 0.05 + math.ceil(len(names) / 3) * 0.035 + figure.tight_layout(rect=(0, legend_height + footer, 1, 1)) + figure.savefig(path, dpi=80) + finally: + plt.close(figure) + + def main(arguments=None): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("results", type=Path, help="directory with the JSON reports") diff --git a/scripts/ci_checks.py b/scripts/ci_checks.py index 5af8607..5a3eeac 100644 --- a/scripts/ci_checks.py +++ b/scripts/ci_checks.py @@ -10,18 +10,17 @@ DOCUMENT_SECTIONS = { "README.md": ( "## Benchmarks", - "## Original comparison", + "## Conclusions", "", "", ), "README.ru.md": ( "## Тесты производительности", - "## Исходное сравнение", + "## Выводы", "", "", ), "CONTRIBUTING.md": ("## Local checks", "## Dependencies and diagnostics"), - "cpp/README.md": ("# C++ placeholder",), } @@ -70,8 +69,6 @@ def main(): for path in sorted((ROOT / ".github/workflows").glob("*.yml")): workflow = load_workflow(path.read_text(encoding="utf-8")) errors += [f"{path.name}: {error}" for error in workflow_errors(workflow)] - if (ROOT / ".github/workflows/cpp.yml").exists() and not (ROOT / "cpp/CMakeLists.txt").exists(): - errors.append("C++ build workflow has no CMake project") for directory in ("scripts", "rust/src", "rust/tests", "csharp", "experiments"): for path in (ROOT / directory).rglob("*"): if path.suffix not in (".py", ".rs", ".cs", ".sh") or any( diff --git a/scripts/test_benchmark_provenance.py b/scripts/test_benchmark_provenance.py new file mode 100644 index 0000000..c4ebce0 --- /dev/null +++ b/scripts/test_benchmark_provenance.py @@ -0,0 +1,49 @@ +"""Snapshot measured software versions rather than attributing today's versions to old runs.""" + +import json +import tempfile +import unittest +from pathlib import Path + +import benchmark_provenance as provenance + + +class SoftwareTests(unittest.TestCase): + def setUp(self): + temporary = tempfile.TemporaryDirectory() + self.addCleanup(temporary.cleanup) + self.root = Path(temporary.name) + (self.root / "rust").mkdir() + (self.root / "rust/Cargo.lock").write_text( + 'version = 4\n[[package]]\nname = "doublets"\nversion = "0.5.0"\n' + ) + (self.root / "csharp/SQLiteVSDoublets").mkdir(parents=True) + (self.root / "csharp/SQLiteVSDoublets/SQLiteVSDoublets.csproj").write_text( + '' + ) + + def test_reads_resolved_rust_and_csharp_library_versions(self): + self.assertEqual(provenance.library_versions("Rust", self.root), {"doublets": "0.5.0"}) + self.assertEqual(provenance.library_versions("C#", self.root), {"Platform.Data.Doublets": "0.18.1"}) + + def test_recording_preserves_measurements_and_captures_toolchain(self): + path = self.root / "report.json" + data = {"language": "Rust", "results": [{"median_ns": 1234}]} + path.write_text(json.dumps(data)) + provenance.record(path, "rustc 1.98.1", self.root) + recorded = json.loads(path.read_text()) + self.assertEqual(recorded["results"], data["results"]) + self.assertEqual( + recorded["software"], + {"libraries": {"doublets": "0.5.0"}, "toolchain": "rustc 1.98.1"}, + ) + + def test_missing_manifests_cannot_silently_record_unknown_versions(self): + (self.root / "rust/Cargo.lock").unlink() + with self.assertRaises((ValueError, FileNotFoundError)): + provenance.library_versions("Rust", self.root) + + +if __name__ == "__main__": + unittest.main() diff --git a/scripts/test_benchmark_report.py b/scripts/test_benchmark_report.py index a82eb11..2bc9d67 100644 --- a/scripts/test_benchmark_report.py +++ b/scripts/test_benchmark_report.py @@ -8,6 +8,7 @@ import tempfile import unittest from pathlib import Path +from unittest.mock import patch import benchmark_report as report @@ -120,6 +121,23 @@ def test_table_compares_every_operation_with_the_baseline(self): class SectionTests(unittest.TestCase): + def test_recorded_software_versions_are_inside_the_provenance_line(self): + data = sample(software={"libraries": {"doublets": "0.4.0"}, "toolchain": "rustc 1.97.0"}) + text = report.provenance(data, report.TEXT["en"]) + self.assertIn("doublets 0.4.0", text) + self.assertIn("rustc 1.97.0", text) + self.assertEqual(text.count("_"), 2) + + def test_conclusions_are_generated_from_the_current_measurements(self): + text = report.section({("links", "Rust", 64, 100_000): sample()}, "en", None) + self.assertIn("## Conclusions", text) + self.assertIn("8 faster, 0 slower, 0 approximately equal", text) + data = sample() + for measured in data["results"][-1]["operations"].values(): + measured.update(measurement(10_000)) + text = report.section({("links", "Rust", 64, 100_000): data}, "en", None) + self.assertIn("0 faster, 8 slower, 0 approximately equal", text) + def test_hierarchy_is_category_language_bits_size(self): reports = { ("links", "Rust", 64, 1_000_000): sample(size=1_000_000), @@ -149,6 +167,7 @@ def test_hierarchy_is_category_language_bits_size(self): "#### 32 bit address/id space benchmarks", "##### 100,000 blog posts", "#### 64 bit address/id space benchmarks", + "## Conclusions", ], ) @@ -160,10 +179,10 @@ def test_missing_groups_and_provenance(self): text = report.section(reports, "en", None) self.assertEqual(text.count("_No results yet._"), 7) self.assertIn( - f"_3 repetitions after a warm-up, median time per operation. SQLite 3.53.2, ubuntu-24.04, " f"[GitHub Actions run]({url}) on 2026-10-04._", text, ) + self.assertIn("toolchain version not recorded", text) self.assertNotIn("![", text) def test_charts_are_linked_per_category_language_and_bits(self): @@ -224,6 +243,36 @@ def test_incomplete_and_nonfinite_measurements_are_rejected(self): with self.assertRaises(ValueError): report.load(self.root) + def test_recorded_provenance_requires_the_measured_library_and_toolchain(self): + for software in ( + {}, + {"libraries": {"doublets": "0.5.0"}, "toolchain": ""}, + {"libraries": {"other": "0.5.0"}, "toolchain": "rustc 1.99.0"}, + ): + with self.subTest(software=software), self.assertRaisesRegex(ValueError, "software provenance"): + report.validate(sample(software=software), "report.json") + + @unittest.skipUnless(importlib.util.find_spec("matplotlib"), "matplotlib is not installed") + def test_linear_charts_keep_small_bars_visible_and_log_charts_keep_actual_values(self): + data = sample() + data["results"][-1]["operations"]["create"] = measurement(1) + captured = {} + + def capture(figure, path, **kwargs): + captured[path.name] = figure + + with patch("matplotlib.figure.Figure.savefig", autospec=True, side_effect=capture): + paths = report.charts({("links", "Rust", 64, 100_000): data}, self.root) + self.assertEqual({path.name for path in paths}, {"links-rust-64.png", "links-rust-64-linear.png"}) + for name, scale, minimum in ( + ("links-rust-64.png", "log", 1), + ("links-rust-64-linear.png", "linear", 10), + ): + axis = captured[name].axes[0] + self.assertEqual(axis.get_yscale(), scale) + self.assertEqual(min(bar.get_height() for bar in axis.patches), minimum) + self.assertEqual(data["results"][-1]["operations"]["create"]["median_ns"], 1) + @unittest.skipUnless(importlib.util.find_spec("matplotlib"), "matplotlib is not installed") def test_main_updates_readmes_and_writes_charts(self): results_directory = self.root / "results" @@ -247,12 +296,34 @@ def test_main_updates_readmes_and_writes_charts(self): ) self.assertEqual( sorted(path.name for path in (self.root / "docs").iterdir()), - ["links-rust-64.png", "objects-csharp-32.png"], + [ + "links-rust-64-linear.png", + "links-rust-64.png", + "objects-csharp-32-linear.png", + "objects-csharp-32.png", + ], ) english = (self.root / "README.md").read_text(encoding="utf-8") - self.assertIn("![Rust doublets vs SQLite, 64 bit, links](docs/links-rust-64.png)", english) + self.assertIn("![Rust doublets vs SQLite, 64 bit, links, log scale](docs/links-rust-64.png)", english) + self.assertIn( + "![Rust doublets vs SQLite, 64 bit, links, linear scale](docs/links-rust-64-linear.png)", english + ) self.assertIn("Дуплеты на C# против SQLite", (self.root / "README.ru.md").read_text(encoding="utf-8")) +class RepositoryReportsTests(unittest.TestCase): + def test_both_committed_readmes_match_the_generator_without_a_stale_tail(self): + root = Path(__file__).resolve().parents[1] + reports = report.load(root / "docs/benchmarks/results") + for name, language in (("README.md", "en"), ("README.ru.md", "ru")): + with self.subTest(name=name): + document = (root / name).read_text(encoding="utf-8") + self.assertTrue(document.rstrip().endswith(report.END_MARKER)) + self.assertEqual( + document, + report.replace_section(document, report.section(reports, language, "docs/benchmarks")), + ) + + if __name__ == "__main__": unittest.main() diff --git a/scripts/test_postgresql_report.py b/scripts/test_postgresql_report.py index 1c76863..ef3e1ac 100644 --- a/scripts/test_postgresql_report.py +++ b/scripts/test_postgresql_report.py @@ -7,6 +7,21 @@ class PostgreSQLReportTests(unittest.TestCase): + def test_all_postgresql_versions_stay_inside_the_italic_provenance(self): + data = sample( + "objects", + "C#", + 32, + 1000, + postgresql={"server_version": "18.3", "provider_version": "10.0.3", "ef_core_version": "10.0.4"}, + ) + for language in ("en", "ru"): + text = report.provenance(data, report.TEXT[language]) + self.assertIn("PostgreSQL 18.3", text) + self.assertIn("EF Core 10.0.4", text) + self.assertEqual(text.count("_"), 2) + self.assertTrue(text.endswith("._")) + def test_server_provider_has_no_embedded_durability_baseline(self): self.assertIsNone(report.baseline("PostgreSQL_EFCore"))