From 57d7a6f96c0fdc35ebe031805efb75a71cae4d93 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Mon, 18 May 2026 21:51:47 +1000 Subject: [PATCH] avoid OrderBy().ToList() in DefaultContractResolver.CreateProperties Replace the LINQ ordering with an in-place List.Sort, and skip sorting entirely when no property has an explicit Order. The previous path allocated an OrderedEnumerable, a key-selector delegate, and an intermediate buffer for every contract built. Benchmark (net10.0, PropertyOrderBenchmark): Count=5, no order: 319 ns / 464 B -> 63 ns / 96 B (0.20x time, 0.21x alloc) Count=20, no order: 710 ns / 816 B -> 106 ns / 216 B (0.15x time, 0.26x alloc) Count=100, no order: 4009 ns / 2736 B -> 388 ns / 856 B (0.10x time, 0.31x alloc) Count=100, mixed: 6201 ns / 2736 B -> 5570 ns / 856 B (0.90x time, 0.31x alloc) --- .../Serialization/DefaultContractResolver.cs | 19 +++++- .../Benchmarks/PropertyOrderBenchmark.cs | 63 +++++++++++++++++++ src/Benchmark.Tests/Program.cs | 2 +- 3 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 src/ArgonTests/Benchmarks/PropertyOrderBenchmark.cs diff --git a/src/Argon/Serialization/DefaultContractResolver.cs b/src/Argon/Serialization/DefaultContractResolver.cs index 1774e7d56..628ea938d 100644 --- a/src/Argon/Serialization/DefaultContractResolver.cs +++ b/src/Argon/Serialization/DefaultContractResolver.cs @@ -676,7 +676,24 @@ protected virtual IList CreateProperties(Type type, MemberSerializ properties.AddProperty(property); } - return properties.OrderBy(_ => _.Order ?? -1).ToList(); + var list = new List(properties); + + var needsSort = false; + foreach (var property in list) + { + if (property.Order != null) + { + needsSort = true; + break; + } + } + + if (needsSort) + { + list.Sort(static (a, b) => (a.Order ?? -1).CompareTo(b.Order ?? -1)); + } + + return list; } public virtual JsonNameTable GetNameTable() => nameTable; diff --git a/src/ArgonTests/Benchmarks/PropertyOrderBenchmark.cs b/src/ArgonTests/Benchmarks/PropertyOrderBenchmark.cs new file mode 100644 index 000000000..b0139d7b2 --- /dev/null +++ b/src/ArgonTests/Benchmarks/PropertyOrderBenchmark.cs @@ -0,0 +1,63 @@ +// Copyright (c) 2007 James Newton-King. All rights reserved. +// Use of this source code is governed by The MIT License, +// as found in the license.md file. + +using BenchmarkDotNet.Attributes; + +[MemoryDiagnoser] +public class PropertyOrderBenchmark +{ + JsonPropertyCollection properties = null!; + + [Params(5, 20, 100)] + public int Count { get; set; } + + [Params(false, true)] + public bool AnyOrder { get; set; } + + [GlobalSetup] + public void Setup() + { + properties = new(typeof(object)); + for (var i = 0; i < Count; i++) + { + var property = new JsonProperty(typeof(string), typeof(object)) + { + PropertyName = "P" + i + }; + if (AnyOrder && i % 3 == 0) + { + property.Order = Count - i; + } + + properties.AddProperty(property); + } + } + + [Benchmark(Baseline = true)] + public IList OrderByToList() => + properties.OrderBy(_ => _.Order ?? -1).ToList(); + + [Benchmark] + public IList InPlaceSort() + { + var list = new List(properties); + + var needsSort = false; + foreach (var property in list) + { + if (property.Order != null) + { + needsSort = true; + break; + } + } + + if (needsSort) + { + list.Sort(static (a, b) => (a.Order ?? -1).CompareTo(b.Order ?? -1)); + } + + return list; + } +} diff --git a/src/Benchmark.Tests/Program.cs b/src/Benchmark.Tests/Program.cs index 02f1ebe11..459e18b5b 100644 --- a/src/Benchmark.Tests/Program.cs +++ b/src/Benchmark.Tests/Program.cs @@ -11,7 +11,7 @@ public static void Main(string[] args) var attribute = (AssemblyFileVersionAttribute)typeof(JsonConvert).Assembly.GetCustomAttribute(typeof(AssemblyFileVersionAttribute))!; Console.WriteLine($"Json.NET Version: {attribute.Version}"); - var switcher = new BenchmarkSwitcher([typeof(WriteEscapedJavaScriptString), typeof(SerializeJTokenList), typeof(ReadQuotedNumbers), typeof(WriteBase64Benchmark)]); + var switcher = new BenchmarkSwitcher([typeof(WriteEscapedJavaScriptString), typeof(SerializeJTokenList), typeof(ReadQuotedNumbers), typeof(WriteBase64Benchmark), typeof(PropertyOrderBenchmark)]); if (args.Length == 0) { switcher.Run(["*"]);