diff --git a/BENCHMARKS.md b/BENCHMARKS.md index 98e540a..f8041d4 100644 --- a/BENCHMARKS.md +++ b/BENCHMARKS.md @@ -8,8 +8,6 @@ Raw JSON string in, parsed patch out — the real-world scenario. | Category | NativeJsonDiff | JsonDiffPatch.Net | SystemTextJson.JsonDiffPatch | JsonPatch.Net | |----------|---------------|-------------------|------------------------------|---------------| -| Small | **2.90 us / 488 B** | 2.82 us / 15.2 KB (0.97x) | 2.68 us / 5.9 KB (0.92x) | 4.91 us / 6.7 KB (1.69x) | -| Medium | **39.21 us / 2.7 KB** | 26.15 us / 100.6 KB (0.67x) | 26.44 us / 53.6 KB (0.67x) | 20.25 us / 62.7 KB (0.52x) | | Large | **487.54 us / 26.5 KB** | 501.18 us / 1.1 MB (1.03x) | 286.07 us / 678.4 KB (0.59x) | 218.51 us / 854.1 KB (0.45x) | | DeepNested | **4.27 us / 680 B** | 6.43 us / 36.1 KB (1.51x) | 5.58 us / 15.9 KB (1.31x) | 13.18 us / 15.7 KB (3.09x) | | ArrayHeavy | **164.73 us / 114.7 KB** | 217.00 us / 795.9 KB (1.32x) | 1,976.87 us / 2.8 MB (12.00x) | 105.99 us / 339.1 KB (0.64x) | @@ -24,8 +22,6 @@ Competitors get pre-parsed DOM objects; NativeJsonDiff still takes raw bytes. | Category | NativeJsonDiff | JsonDiffPatch.Net | SystemTextJson.JsonDiffPatch | JsonPatch.Net | |----------|---------------|-------------------|------------------------------|---------------| -| Small | **2.57 us / 488 B** | 476 ns / 1.5 KB (0.19x) | 1.23 us / 1.0 KB (0.48x) | 780 ns / 1.9 KB (0.30x) | -| Medium | **35.25 us / 2.7 KB** | 4.43 us / 9.1 KB (0.13x) | 13.15 us / 7.5 KB (0.37x) | 7.62 us / 16.6 KB (0.22x) | | Large | **463.25 us / 26.5 KB** | 60.38 us / 86.0 KB (0.13x) | 153.13 us / 144.4 KB (0.33x) | 96.31 us / 320.1 KB (0.21x) | | DeepNested | **4.30 us / 680 B** | 1.98 us / 9.6 KB (0.46x) | 2.28 us / 4.7 KB (0.53x) | 1.53 us / 5.0 KB (0.35x) | | ArrayHeavy | **169.33 us / 114.7 KB** | 89.47 us / 378.6 KB (0.53x) | 2,016.13 us / 2.6 MB (11.91x) | 45.63 us / 145.9 KB (0.27x) | @@ -40,8 +36,6 @@ Apply a pre-computed patch to a document. | Category | NativeJsonDiff | JsonDiffPatch.Net | SystemTextJson.JsonDiffPatch | JsonPatch.Net | |----------|---------------|-------------------|------------------------------|---------------| -| Small | **2.63 us / 1.0 KB** | 1.63 us / 7.8 KB (0.71x) | 341 ns / 1.9 KB (0.15x) | 382 ns / 2.2 KB (0.17x) | -| Medium | **19.14 us / 8.4 KB** | 15.44 us / 70.7 KB (0.81x) | 3.52 us / 19.1 KB (0.18x) | 10.07 us / 20.2 KB (0.53x) | | Large | **256.75 us / 191.2 KB** | 141.11 us / 692.0 KB (0.55x) | 35.48 us / 190.5 KB (0.14x) | 456.89 us / 200.1 KB (1.78x) | | DeepNested | **3.90 us / 1.8 KB** | 12.19 us / 65.9 KB (3.13x) | 881 ns / 4.3 KB (0.23x) | 1.42 us / 5.7 KB (0.36x) | | ArrayHeavy | **218.24 us / 31.8 KB** | 175.55 us / 753.5 KB (0.80x) | 6.67 us / 28.2 KB (0.03x) | 62.74 us / 185.8 KB (0.29x) | diff --git a/README.md b/README.md index a12691e..197f2b2 100644 --- a/README.md +++ b/README.md @@ -22,14 +22,12 @@ Most .NET JSON diff libraries run entirely in managed code — parsing JSON tree ## Performance -NativeJsonDiff is benchmarked against every major .NET JSON diff library using [BenchmarkDotNet](https://benchmarkdotnet.org/) across 9 payload categories (Small, Medium, Large, DeepNested, ArrayHeavy, ApiResponse, ConfigFile, LargeStateTree, VeryLargeStateTree — up to 2MB JSON trees). +NativeJsonDiff is benchmarked against every major .NET JSON diff library using [BenchmarkDotNet](https://benchmarkdotnet.org/) across 8 payload categories (Large, DeepNested, ArrayHeavy, ApiResponse, ConfigFile, LargeStateTree, VeryLargeStateTree, WidgetTree — up to 2MB JSON trees). ### End-to-End Diff (raw JSON in, patch out) | Category | NativeJsonDiff | JsonDiffPatch.Net | SystemTextJson.JsonDiffPatch | JsonPatch.Net | |----------|---------------|-------------------|------------------------------|---------------| -| Small | **2.90 us / 488 B** | 2.82 us / 15.2 KB | 2.68 us / 5.9 KB | 4.91 us / 6.7 KB | -| Medium | **39.21 us / 2.7 KB** | 26.15 us / 100.6 KB | 26.44 us / 53.6 KB | 20.25 us / 62.7 KB | | Large | **487.54 us / 26.5 KB** | 501.18 us / 1.1 MB | 286.07 us / 678.4 KB | 218.51 us / 854.1 KB | | LargeStateTree | **717.72 us / 40.5 KB** | 1,766.34 us / 4.3 MB (2.5x) | 861.40 us / 1.7 MB (1.2x) | 852.54 us / 2.0 MB (1.2x) | | VeryLargeStateTree | **18.39 ms / 121.8 KB** | 136.29 ms / 83.9 MB (7.4x) | 44.98 ms / 31.8 MB (2.4x) | 53.44 ms / 61.9 MB (2.9x) | @@ -38,8 +36,6 @@ NativeJsonDiff is benchmarked against every major .NET JSON diff library using [ | Category | NativeJsonDiff | JsonDiffPatch.Net | SystemTextJson.JsonDiffPatch | JsonPatch.Net | |----------|---------------|-------------------|------------------------------|---------------| -| Small | **2.63 us / 1.0 KB** | 1.63 us / 7.8 KB | 341 ns / 1.9 KB | 382 ns / 2.2 KB | -| Medium | **19.14 us / 8.4 KB** | 15.44 us / 70.7 KB | 3.52 us / 19.1 KB | 10.07 us / 20.2 KB | | Large | **256.75 us / 191.2 KB** | 141.11 us / 692.0 KB | 35.48 us / 190.5 KB | 456.89 us / 200.1 KB | | LargeStateTree | **488.27 us / 263.0 KB** | 1,862.22 us / 4.6 MB (3.8x) | 30.36 us / 140.6 KB | 191.85 us / 177.2 KB | | VeryLargeStateTree | **20.17 ms / 6.9 MB** | 291.13 ms / 197.4 MB (16.3x) | 1.66 ms / 4.7 MB | 311.73 us / 507.3 KB | diff --git a/src/Ivy.NativeJsonDiff.Benchmarks/Benchmarks/EndToEndDiffBenchmarks.cs b/src/Ivy.NativeJsonDiff.Benchmarks/Benchmarks/EndToEndDiffBenchmarks.cs index 32f6e2d..f13a40c 100644 --- a/src/Ivy.NativeJsonDiff.Benchmarks/Benchmarks/EndToEndDiffBenchmarks.cs +++ b/src/Ivy.NativeJsonDiff.Benchmarks/Benchmarks/EndToEndDiffBenchmarks.cs @@ -17,7 +17,7 @@ namespace Ivy.NativeJsonDiff.Benchmarks.Benchmarks; [Config(typeof(CiBenchmarkConfig))] public class EndToEndDiffBenchmarks { - [Params("Small", "Medium", "Large", "DeepNested", "ArrayHeavy", "ApiResponse", "ConfigFile", "LargeStateTree", "VeryLargeStateTree", "WidgetTree")] + [Params("Large", "DeepNested", "ArrayHeavy", "ApiResponse", "ConfigFile", "LargeStateTree", "VeryLargeStateTree", "WidgetTree")] public string Category { get; set; } = null!; private byte[] _oldBytes = null!; diff --git a/src/Ivy.NativeJsonDiff.Benchmarks/Benchmarks/JsonApplyBenchmarks.cs b/src/Ivy.NativeJsonDiff.Benchmarks/Benchmarks/JsonApplyBenchmarks.cs index 5defeb4..754bc3e 100644 --- a/src/Ivy.NativeJsonDiff.Benchmarks/Benchmarks/JsonApplyBenchmarks.cs +++ b/src/Ivy.NativeJsonDiff.Benchmarks/Benchmarks/JsonApplyBenchmarks.cs @@ -17,7 +17,7 @@ namespace Ivy.NativeJsonDiff.Benchmarks.Benchmarks; [Config(typeof(CiBenchmarkConfig))] public class JsonApplyBenchmarks { - [Params("Small", "Medium", "Large", "DeepNested", "ArrayHeavy", "ApiResponse", "ConfigFile", "LargeStateTree", "VeryLargeStateTree", "WidgetTree")] + [Params("Large", "DeepNested", "ArrayHeavy", "ApiResponse", "ConfigFile", "LargeStateTree", "VeryLargeStateTree", "WidgetTree")] public string Category { get; set; } = null!; private byte[] _oldBytes = null!; diff --git a/src/Ivy.NativeJsonDiff.Benchmarks/Benchmarks/PreParsedDiffBenchmarks.cs b/src/Ivy.NativeJsonDiff.Benchmarks/Benchmarks/PreParsedDiffBenchmarks.cs index 4779c87..51b0485 100644 --- a/src/Ivy.NativeJsonDiff.Benchmarks/Benchmarks/PreParsedDiffBenchmarks.cs +++ b/src/Ivy.NativeJsonDiff.Benchmarks/Benchmarks/PreParsedDiffBenchmarks.cs @@ -17,7 +17,7 @@ namespace Ivy.NativeJsonDiff.Benchmarks.Benchmarks; [Config(typeof(CiBenchmarkConfig))] public class PreParsedDiffBenchmarks { - [Params("Small", "Medium", "Large", "DeepNested", "ArrayHeavy", "ApiResponse", "ConfigFile", "LargeStateTree", "VeryLargeStateTree", "WidgetTree")] + [Params("Large", "DeepNested", "ArrayHeavy", "ApiResponse", "ConfigFile", "LargeStateTree", "VeryLargeStateTree", "WidgetTree")] public string Category { get; set; } = null!; private byte[] _oldBytes = null!; diff --git a/src/Ivy.NativeJsonDiff.Benchmarks/TestData/JsonPayloadGenerator.cs b/src/Ivy.NativeJsonDiff.Benchmarks/TestData/JsonPayloadGenerator.cs index db284b7..f5985bd 100644 --- a/src/Ivy.NativeJsonDiff.Benchmarks/TestData/JsonPayloadGenerator.cs +++ b/src/Ivy.NativeJsonDiff.Benchmarks/TestData/JsonPayloadGenerator.cs @@ -6,12 +6,10 @@ namespace Ivy.NativeJsonDiff.Benchmarks.TestData; public static class JsonPayloadGenerator { public static readonly string[] AllCategories = - ["Small", "Medium", "Large", "DeepNested", "ArrayHeavy", "ApiResponse", "ConfigFile", "LargeStateTree", "VeryLargeStateTree", "WidgetTree"]; + ["Large", "DeepNested", "ArrayHeavy", "ApiResponse", "ConfigFile", "LargeStateTree", "VeryLargeStateTree", "WidgetTree"]; public static (byte[] Old, byte[] New) Generate(string category) => category switch { - "Small" => GenerateSmall(), - "Medium" => GenerateMedium(), "Large" => GenerateLarge(), "DeepNested" => GenerateDeepNested(), "ArrayHeavy" => GenerateArrayHeavy(), @@ -23,32 +21,6 @@ public static class JsonPayloadGenerator _ => throw new ArgumentException($"Unknown category: {category}") }; - private static (byte[] Old, byte[] New) GenerateSmall() - { - var old = new Dictionary(); - var @new = new Dictionary(); - for (int i = 0; i < 10; i++) - { - old[$"field{i}"] = $"value{i}"; - @new[$"field{i}"] = i == 5 ? "changed" : $"value{i}"; - } - - return (Serialize(old), Serialize(@new)); - } - - private static (byte[] Old, byte[] New) GenerateMedium() - { - var old = new Dictionary(); - var @new = new Dictionary(); - for (int i = 0; i < 100; i++) - { - old[$"field{i}"] = $"value{i}"; - @new[$"field{i}"] = i % 10 == 0 ? "changed" : $"value{i}"; - } - - return (Serialize(old), Serialize(@new)); - } - private static (byte[] Old, byte[] New) GenerateLarge() { var old = new Dictionary(); diff --git a/src/Ivy.NativeJsonDiff.Tests/JsonPayloadGeneratorTests.cs b/src/Ivy.NativeJsonDiff.Tests/JsonPayloadGeneratorTests.cs index b81966c..877f64b 100644 --- a/src/Ivy.NativeJsonDiff.Tests/JsonPayloadGeneratorTests.cs +++ b/src/Ivy.NativeJsonDiff.Tests/JsonPayloadGeneratorTests.cs @@ -7,8 +7,6 @@ namespace Ivy.NativeJsonDiff.Tests; public class JsonPayloadGeneratorTests { [Theory] - [InlineData("Small")] - [InlineData("Medium")] [InlineData("Large")] [InlineData("DeepNested")] [InlineData("ArrayHeavy")] @@ -83,13 +81,12 @@ public void Generate_OldAndNew_AreDifferent() [Fact] public void AllCategories_ContainsExpectedEntries() { - Assert.Contains("Small", JsonPayloadGenerator.AllCategories); Assert.Contains("ApiResponse", JsonPayloadGenerator.AllCategories); Assert.Contains("ConfigFile", JsonPayloadGenerator.AllCategories); Assert.Contains("LargeStateTree", JsonPayloadGenerator.AllCategories); Assert.Contains("VeryLargeStateTree", JsonPayloadGenerator.AllCategories); Assert.Contains("WidgetTree", JsonPayloadGenerator.AllCategories); - Assert.Equal(10, JsonPayloadGenerator.AllCategories.Length); + Assert.Equal(8, JsonPayloadGenerator.AllCategories.Length); } [Fact]