diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 5bfc8a5..8a69f9d 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -122,7 +122,10 @@ jobs: BASE_BUILT: ${{ steps.build_base.outcome == 'success' }} run: | DLL=tests/EncDotNet.Benchmarks/bin/Release/net10.0/EncDotNet.Benchmarks.dll - ARGS=(--filter '*' --exporters json --warmupCount 3 --iterationCount 10 --launchCount 1) + # BenchmarkDotNet's default 500 ms iterations make each case take 10-15 s however + # fast the operation is. Shorter iterations, with more of them, halve the run time + # while keeping the median as precise (standard error about 2%). + ARGS=(--filter '*' --exporters json --warmupCount 3 --iterationCount 15 --iterationTime 200 --launchCount 1) if [ "$SCOPE" = "core" ]; then ARGS+=(--anyCategories Core) fi diff --git a/scripts/DownloadBenchmarkCells.cs b/scripts/DownloadBenchmarkCells.cs index 1b2442a..803f7bb 100644 --- a/scripts/DownloadBenchmarkCells.cs +++ b/scripts/DownloadBenchmarkCells.cs @@ -7,11 +7,17 @@ using EncDotNet.Noaa; // ============================================================================ -// DownloadBenchmarkCells.cs — Downloads a fixed set of real NOAA ENC cells for +// DownloadBenchmarkCells.cs — Downloads a few real NOAA ENC cells for // the benchmarks in tests/EncDotNet.Benchmarks. // // Usage: dotnet run scripts/DownloadBenchmarkCells.cs -- [cell...] // +// Without cell names, the script picks one cell per usage band (harbour, approach +// and coastal) from the live NOAA catalog: among active cells with update files, +// the one at the 90th percentile of download size, so the set is substantial but +// typical. Picking by rule rather than by name survives NOAA withdrawing or +// renaming cells, which it does as it re-schemes its coverage. +// // Each cell is extracted to // with its update files. Point // ENCDOTNET_ENC_ROOT at to include them in a benchmark run. The // script writes /cells.txt recording each cell's edition, update @@ -24,9 +30,10 @@ // Never commit the downloaded cells (see CONTRIBUTING.md). // ============================================================================ -// A harbour, an approach and a coastal cell, all with update files. Together they -// cover the range of record counts and edge lengths in typical NOAA cells. -string[] defaultCells = ["US5WA50M", "US4MD81M", "US3WA01M"]; +// Navigational purpose (usage band), the third character of the cell file name +// (S-57 Appendix B.1, 5.6.4). +(char Band, string Purpose)[] bands = [('5', "harbour"), ('4', "approach"), ('3', "coastal")]; +const double sizePercentile = 0.9; if (args.Length == 0) { @@ -35,13 +42,43 @@ } var outputDirectory = Path.GetFullPath(args[0]); -var cellNames = args.Length > 1 ? args[1..] : defaultCells; Directory.CreateDirectory(outputDirectory); using var catalogClient = new EncProductCatalogClient(); var catalog = await catalogClient.GetNoaaCatalogAsync(); var cellsByName = catalog.Cells.ToDictionary(c => c.Name, StringComparer.OrdinalIgnoreCase); +string[] cellNames; +if (args.Length > 1) +{ + cellNames = args[1..]; +} +else +{ + var picked = new List(); + foreach (var (band, purpose) in bands) + { + var candidates = catalog.Cells + .Where(c => c.Name.Length == 8 && c.Name[2] == band) + .Where(c => c.UpdateNumber > 0 && c.ZipfileSize > 0) + .Where(c => !c.Status.Contains("cancel", StringComparison.OrdinalIgnoreCase)) + .OrderBy(c => c.ZipfileSize) + .ThenBy(c => c.Name, StringComparer.Ordinal) + .ToList(); + if (candidates.Count == 0) + { + Console.Error.WriteLine($"warning: no active {purpose} cell with updates in the NOAA catalog."); + continue; + } + + var cell = candidates[(int)((candidates.Count - 1) * sizePercentile)]; + Console.WriteLine($"Picked {cell.Name} as the {purpose} cell ({candidates.Count} candidates, zip {cell.ZipfileSize:N0} bytes)."); + picked.Add(cell.Name); + } + + cellNames = [.. picked]; +} + using var httpClient = new HttpClient(); var manifest = new List(); var missing = 0; @@ -96,4 +133,4 @@ } File.WriteAllLines(Path.Combine(outputDirectory, "cells.txt"), manifest); -return missing == cellNames.Length ? 1 : 0; +return cellNames.Length == 0 || missing == cellNames.Length ? 1 : 0; diff --git a/tests/EncDotNet.PerformanceTests/PerformanceCells.cs b/tests/EncDotNet.PerformanceTests/PerformanceCells.cs index 37cab38..500dc4c 100644 --- a/tests/EncDotNet.PerformanceTests/PerformanceCells.cs +++ b/tests/EncDotNet.PerformanceTests/PerformanceCells.cs @@ -13,7 +13,7 @@ internal static class PerformanceCells private static readonly Lazy LazyHarbourX4 = new(() => new Inputs(SyntheticCellProfile.Harbour.Scale(4))); /// - /// Gets the harbour-scale cell, shaped like NOAA US5WA50M. + /// Gets the harbour-scale cell, shaped like an earlier edition of NOAA US5WA50M. /// public static Inputs Harbour => LazyHarbour.Value; diff --git a/tests/Shared/SyntheticCellProfile.cs b/tests/Shared/SyntheticCellProfile.cs index f3f435e..770bcaf 100644 --- a/tests/Shared/SyntheticCellProfile.cs +++ b/tests/Shared/SyntheticCellProfile.cs @@ -6,7 +6,10 @@ namespace EncDotNet.Testing; /// /// The built-in profiles are calibrated against real NOAA cells: record counts, the /// distribution of edge lengths, attributes per feature and spatial pointers per feature -/// follow the measured averages of the cell named in each profile's remarks. +/// follow the measured averages of the cell named in each profile's summary. Those were +/// earlier editions; NOAA has since re-schemed its coverage into smaller cells (edition 36 +/// of US4MD81M is about 320 KB), so the larger profiles now stand for the bigger cells an +/// application may still meet rather than for a typical current one. /// /// The profile name, used in the generated data set name and in benchmark output. /// The seed for the generator; the same profile always yields the same bytes. @@ -35,7 +38,7 @@ public sealed record SyntheticCellProfile( int AreaFeatures) { /// - /// A harbour-scale cell shaped like NOAA US5WA50M (about 400 KB). + /// A harbour-scale cell shaped like an earlier edition of NOAA US5WA50M (about 400 KB). /// public static SyntheticCellProfile Harbour { get; } = new( Name: "Harbour", @@ -52,7 +55,7 @@ public sealed record SyntheticCellProfile( AreaFeatures: 211); /// - /// An approach-scale cell shaped like NOAA US4MD81M (about 3.5 MB). + /// An approach-scale cell shaped like an earlier edition of NOAA US4MD81M (about 3.5 MB). /// public static SyntheticCellProfile Approach { get; } = new( Name: "Approach",