using System; using System.Diagnostics; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Engines; namespace ParadoxSaveParser.Benchmarks; /// /// jq runs as an external process on the JSON twin of the save. /// Numbers therefore include process start and reading the file from the OS page cache, /// which is exactly what you pay when you shell out to jq. /// measures that fixed overhead so it can be subtracted. /// [SimpleJob(RunStrategy.Monitoring, launchCount: 1, warmupCount: 1, iterationCount: 5)] public class JqBenchmarks { public static string JqPath { get; set; } = "jq"; [GlobalSetup] public void Setup() => Run("--version", null); [Benchmark(Description = "jq .countries | map_values(.technology)")] public int Jq() => Run("-c", BenchData.JqQuery); [Benchmark(Description = "jq empty (parse only)")] public int JqParseOnly() => Run("-c", "empty"); [Benchmark(Description = "jq --stream (no DOM)")] public int JqStream() => Run("-c --stream", "empty"); [Benchmark(Description = "jq process startup only (tiny input)")] public int JqNoop() { var psi = new ProcessStartInfo(JqPath) { RedirectStandardOutput = true, RedirectStandardInput = true, UseShellExecute = false, }; psi.ArgumentList.Add("-c"); psi.ArgumentList.Add("empty"); using var p = Process.Start(psi)!; p.StandardInput.Write("{}"); p.StandardInput.Close(); p.StandardOutput.ReadToEnd(); p.WaitForExit(); return p.ExitCode; } private static int Run(string flags, string? program) { var psi = new ProcessStartInfo(JqPath) { RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, }; foreach (var f in flags.Split(' ', StringSplitOptions.RemoveEmptyEntries)) psi.ArgumentList.Add(f); if (program != null) { psi.ArgumentList.Add(program); psi.ArgumentList.Add(BenchData.JsonPath); } using var p = Process.Start(psi)!; // output must be drained, otherwise jq blocks on a full pipe long bytes = 0; var buf = new char[1 << 16]; int read; while ((read = p.StandardOutput.Read(buf, 0, buf.Length)) > 0) bytes += read; p.StandardError.ReadToEnd(); p.WaitForExit(); if (p.ExitCode != 0) throw new Exception($"jq exited with {p.ExitCode}"); return (int)(bytes & int.MaxValue); } }