From 1013a7d8b09ff2aa9ab68d4c6793bd3719e2aaf5 Mon Sep 17 00:00:00 2001 From: Brenton Farmer Date: Sun, 1 Jun 2025 16:29:50 -0700 Subject: [PATCH 1/3] performance improvements --- .../Descriptors/ChildEnumerationOptions.cs | 9 + .../Descriptors/Element/ElementActions.cs | 73 +++- src/Hyperbee.Json/Descriptors/INodeActions.cs | 3 +- .../Descriptors/Node/NodeActions.cs | 73 +++- .../Extensions/ListExtensions.cs | 10 + .../Expressions/JsonExpressionFactory.cs | 1 + src/Hyperbee.Json/Path/JsonPath.cs | 388 ++++++++++++------ src/Hyperbee.Json/Query/JsonSegment.cs | 54 ++- .../Hyperbee.Json.Benchmark.csproj | 1 + .../JsonPathParseAndSelectEvaluator.cs | 130 +++--- .../JsonPathSelectEvaluator.cs | 26 +- ...pressionParserEvaluator-report-jsonpath.md | 10 +- ...hmark.JsonDiffBenchmark-report-jsonpath.md | 24 +- ...mark.JsonPatchBenchmark-report-jsonpath.md | 22 +- ...ParseAndSelectEvaluator-report-jsonpath.md | 113 +++-- ...JsonPathSelectEvaluator-report-jsonpath.md | 81 ++++ .../Path/Query/JsonPathBookstoreTests.cs | 2 +- 17 files changed, 735 insertions(+), 285 deletions(-) create mode 100644 src/Hyperbee.Json/Descriptors/ChildEnumerationOptions.cs create mode 100644 src/Hyperbee.Json/Extensions/ListExtensions.cs create mode 100644 test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonPathSelectEvaluator-report-jsonpath.md diff --git a/src/Hyperbee.Json/Descriptors/ChildEnumerationOptions.cs b/src/Hyperbee.Json/Descriptors/ChildEnumerationOptions.cs new file mode 100644 index 00000000..e99eddf4 --- /dev/null +++ b/src/Hyperbee.Json/Descriptors/ChildEnumerationOptions.cs @@ -0,0 +1,9 @@ +namespace Hyperbee.Json.Descriptors; + +[Flags] +public enum ChildEnumerationOptions +{ + None = 0, + ComplexTypesOnly = 1, + Reverse = 2, +} diff --git a/src/Hyperbee.Json/Descriptors/Element/ElementActions.cs b/src/Hyperbee.Json/Descriptors/Element/ElementActions.cs index 293ae663..bb1286ed 100644 --- a/src/Hyperbee.Json/Descriptors/Element/ElementActions.cs +++ b/src/Hyperbee.Json/Descriptors/Element/ElementActions.cs @@ -33,18 +33,72 @@ public bool TryGetFromPointer( in JsonElement node, JsonSegment segment, out Jso public bool DeepEquals( JsonElement left, JsonElement right ) => left.DeepEquals( right ); - public IEnumerable<(JsonElement Value, string Key)> GetChildren( in JsonElement value, bool complexTypesOnly = false ) + public IEnumerable GetChildren( JsonElement value, ChildEnumerationOptions options ) { + bool complexTypesOnly = options.HasFlag( ChildEnumerationOptions.ComplexTypesOnly ); + bool reverse = options.HasFlag( ChildEnumerationOptions.Reverse ); + + // allocating is faster than using yield return and less memory intensive. + // using a collection results in fewer overall allocations than calling + // LINQ reverse, which internally allocates, and then discards, a new array. + + List results; + + switch ( value.ValueKind ) + { + case JsonValueKind.Array: + { + var length = value.GetArrayLength(); + results = new List( length ); + + for ( var index = 0; index < length; index++ ) + { + var child = value[index]; + + if ( complexTypesOnly && child.ValueKind is not (JsonValueKind.Array or JsonValueKind.Object) ) + continue; + + results.Add( child ); + } + + return reverse ? results.EnumerateReverse() : results; + } + case JsonValueKind.Object: + { + results = new List( 8 ); + + foreach ( var child in value.EnumerateObject() ) + { + if ( complexTypesOnly && child.Value.ValueKind is not (JsonValueKind.Array or JsonValueKind.Object) ) + continue; + + results.Add( child.Value ); + } + + return reverse ? results.EnumerateReverse() : results; + } + } + + return []; + } + + public IEnumerable<(JsonElement Value, string Key)> GetChildrenWithName( in JsonElement value, ChildEnumerationOptions options ) + { + bool complexTypesOnly = options.HasFlag( ChildEnumerationOptions.ComplexTypesOnly ); + bool reverse = options.HasFlag( ChildEnumerationOptions.Reverse ); + // allocating is faster than using yield return and less memory intensive. - // using stack results in fewer overall allocations than calling reverse, - // which internally allocates, and then discards, a new array. + // using a collection results in fewer overall allocations than calling + // LINQ reverse, which internally allocates, and then discards, a new array. + + List<(JsonElement, string)> results; switch ( value.ValueKind ) { case JsonValueKind.Array: { var length = value.GetArrayLength(); - var results = new Stack<(JsonElement, string)>( length ); // stack will reverse items + results = new List<(JsonElement, string)>( length ); for ( var index = 0; index < length; index++ ) { @@ -53,27 +107,28 @@ public bool DeepEquals( JsonElement left, JsonElement right ) => if ( complexTypesOnly && child.ValueKind is not (JsonValueKind.Array or JsonValueKind.Object) ) continue; - results.Push( (child, IndexHelper.GetIndexString( index )) ); + results.Add( (child, IndexHelper.GetIndexString( index )) ); } - return results; + return reverse ? results.EnumerateReverse() : results; } case JsonValueKind.Object: { - var results = new Stack<(JsonElement, string)>(); // stack will reverse items + results = new List<(JsonElement, string)>( 8 ); foreach ( var child in value.EnumerateObject() ) { if ( complexTypesOnly && child.Value.ValueKind is not (JsonValueKind.Array or JsonValueKind.Object) ) continue; - results.Push( (child.Value, child.Name) ); + results.Add( (child.Value, child.Name) ); } - return results; + return reverse ? results.EnumerateReverse() : results; } } return []; } } + diff --git a/src/Hyperbee.Json/Descriptors/INodeActions.cs b/src/Hyperbee.Json/Descriptors/INodeActions.cs index fbd01591..cec948cc 100644 --- a/src/Hyperbee.Json/Descriptors/INodeActions.cs +++ b/src/Hyperbee.Json/Descriptors/INodeActions.cs @@ -11,5 +11,6 @@ public interface INodeActions public bool DeepEquals( TNode left, TNode right ); - public IEnumerable<(TNode Value, string Key)> GetChildren( in TNode value, bool complexTypesOnly = false ); + public IEnumerable<(TNode Value, string Key)> GetChildrenWithName( in TNode value, ChildEnumerationOptions options ); + public IEnumerable GetChildren( TNode value, ChildEnumerationOptions options ); } diff --git a/src/Hyperbee.Json/Descriptors/Node/NodeActions.cs b/src/Hyperbee.Json/Descriptors/Node/NodeActions.cs index 7f2f5358..dabe0b56 100644 --- a/src/Hyperbee.Json/Descriptors/Node/NodeActions.cs +++ b/src/Hyperbee.Json/Descriptors/Node/NodeActions.cs @@ -1,5 +1,6 @@ using System.Text.Json; using System.Text.Json.Nodes; +using Hyperbee.Json.Extensions; using Hyperbee.Json.Path; using Hyperbee.Json.Pointer; using Hyperbee.Json.Query; @@ -28,18 +29,72 @@ public bool TryGetFromPointer( in JsonNode node, JsonSegment segment, out JsonNo public bool DeepEquals( JsonNode left, JsonNode right ) => JsonNode.DeepEquals( left, right ); - public IEnumerable<(JsonNode Value, string Key)> GetChildren( in JsonNode value, bool complexTypesOnly = false ) + public IEnumerable GetChildren( JsonNode value, ChildEnumerationOptions options ) { + bool complexTypesOnly = options.HasFlag( ChildEnumerationOptions.ComplexTypesOnly ); + bool reverse = options.HasFlag( ChildEnumerationOptions.Reverse ); + // allocating is faster than using yield return and less memory intensive. - // using stack results in fewer overall allocations than calling reverse, - // which internally allocates, and then discards, a new array. + // using a collection results in fewer overall allocations than calling + // LINQ reverse, which internally allocates, and then discards, a new array. + + List results; + + switch ( value ) + { + case JsonArray jsonArray: + { + var length = jsonArray.Count; + results = new List( length ); + + for ( var index = 0; index < length; index++ ) + { + var child = value[index]; + + if ( complexTypesOnly && child is not (JsonArray or JsonObject) ) + continue; + + results.Add( child ); + } + + return reverse ? results.EnumerateReverse() : results; + } + case JsonObject jsonObject: + { + results = new List( 8 ); + + foreach ( var child in jsonObject ) + { + if ( complexTypesOnly && child.Value is not (JsonArray or JsonObject) ) + continue; + + results.Add( child.Value ); + } + + return reverse ? results.EnumerateReverse() : results; + } + } + + return []; + } + + public IEnumerable<(JsonNode Value, string Key)> GetChildrenWithName( in JsonNode value, ChildEnumerationOptions options ) + { + bool complexTypesOnly = options.HasFlag( ChildEnumerationOptions.ComplexTypesOnly ); + bool reverse = options.HasFlag( ChildEnumerationOptions.Reverse ); + + // allocating is faster than using yield return and less memory intensive. + // using a collection results in fewer overall allocations than calling + // LINQ reverse, which internally allocates, and then discards, a new array. + + List<(JsonNode, string)> results; switch ( value ) { case JsonArray jsonArray: { var length = jsonArray.Count; - var results = new Stack<(JsonNode, string)>( length ); // stack will reverse items + results = new List<(JsonNode, string)>( length ); for ( var index = 0; index < length; index++ ) { @@ -48,24 +103,24 @@ public bool DeepEquals( JsonNode left, JsonNode right ) => if ( complexTypesOnly && child is not (JsonArray or JsonObject) ) continue; - results.Push( (child, IndexHelper.GetIndexString( index )) ); + results.Add( (child, IndexHelper.GetIndexString( index )) ); } - return results; + return reverse ? results.EnumerateReverse() : results; } case JsonObject jsonObject: { - var results = new Stack<(JsonNode, string)>(); // stack will reverse items + results = new List<(JsonNode, string)>( 8 ); foreach ( var child in jsonObject ) { if ( complexTypesOnly && child.Value is not (JsonArray or JsonObject) ) continue; - results.Push( (child.Value, child.Key) ); + results.Add( (child.Value, child.Key) ); } - return results; + return reverse ? results.EnumerateReverse() : results; } } diff --git a/src/Hyperbee.Json/Extensions/ListExtensions.cs b/src/Hyperbee.Json/Extensions/ListExtensions.cs new file mode 100644 index 00000000..02e3dbf6 --- /dev/null +++ b/src/Hyperbee.Json/Extensions/ListExtensions.cs @@ -0,0 +1,10 @@ +namespace Hyperbee.Json.Extensions; + +public static class ListExtensions +{ + internal static IEnumerable EnumerateReverse( this IList list ) + { + for ( var i = list.Count - 1; i >= 0; i-- ) + yield return list[i]; + } +} diff --git a/src/Hyperbee.Json/Path/Filters/Parser/Expressions/JsonExpressionFactory.cs b/src/Hyperbee.Json/Path/Filters/Parser/Expressions/JsonExpressionFactory.cs index 8fdd3abf..8727d2e6 100644 --- a/src/Hyperbee.Json/Path/Filters/Parser/Expressions/JsonExpressionFactory.cs +++ b/src/Hyperbee.Json/Path/Filters/Parser/Expressions/JsonExpressionFactory.cs @@ -44,6 +44,7 @@ private static bool TryParseNode( INodeActions actions, ReadOnlySp private static void ConvertToDoubleQuotes( ref Span buffer, int length ) { var insideString = false; + for ( var i = 0; i < length; i++ ) { if ( buffer[i] == (byte) '\"' ) diff --git a/src/Hyperbee.Json/Path/JsonPath.cs b/src/Hyperbee.Json/Path/JsonPath.cs index 7e13c227..918cb278 100644 --- a/src/Hyperbee.Json/Path/JsonPath.cs +++ b/src/Hyperbee.Json/Path/JsonPath.cs @@ -32,6 +32,7 @@ #endregion +using System.Buffers; using System.Diagnostics; using System.Runtime.CompilerServices; using Hyperbee.Json.Core; @@ -84,6 +85,43 @@ internal static IEnumerable SelectInternal( in TNode value, in TNode root return EnumerateMatches( value, root, compiledQuery, processor ); } + private static IEnumerable EnumerateAllDescendants( TNode node, NodeProcessorDelegate processor ) + { + var kind = Descriptor.ValueAccessor.GetNodeKind( node ); + + if ( kind != NodeKind.Object && kind != NodeKind.Array ) + { + yield return node; + yield break; + } + + var stack = new Stack( 8 ); + var current = node; + + do + { + // .. + foreach ( var child in Descriptor.NodeActions.GetChildren( current, ChildEnumerationOptions.Reverse | ChildEnumerationOptions.ComplexTypesOnly ) ) + stack.Push( child ); + + // * + if ( processor == null ) + { + foreach ( var child in Descriptor.NodeActions.GetChildren( current, ChildEnumerationOptions.None ) ) + yield return child; + } + else + { + foreach ( var (child, key) in Descriptor.NodeActions.GetChildrenWithName( current, ChildEnumerationOptions.None ) ) + { + processor.Invoke( current, child, key, default ); + yield return child; + } + } + + } while ( stack.TryPop( out current ) ); + } + private static IEnumerable EnumerateMatches( in TNode value, in TNode root, JsonQuery compiledQuery, NodeProcessorDelegate processor = null ) { if ( string.IsNullOrWhiteSpace( compiledQuery.Query ) ) // invalid per the RFC ABNF @@ -92,6 +130,9 @@ private static IEnumerable EnumerateMatches( in TNode value, in TNode roo if ( compiledQuery.Query == "$" || compiledQuery.Query == "@" ) // quick out for everything return [value]; + if ( compiledQuery.Query == "$..*" ) // Fast path for $..* + return EnumerateAllDescendants( value, processor ); + var segmentNext = compiledQuery.Segments.Next; // The first segment is always the root; skip it if ( compiledQuery.Normalized ) // we can fast path this @@ -107,7 +148,7 @@ private static IEnumerable EnumerateMatches( in TNode value, in TNode roo private static IEnumerable EnumerateMatches( TNode root, NodeArgs args, NodeProcessorDelegate processor = null ) { - var stack = new NodeArgsStack(); + using var stack = new NodeArgsStack( 32 ); var accessor = Descriptor.ValueAccessor; do @@ -116,10 +157,10 @@ private static IEnumerable EnumerateMatches( TNode root, NodeArgs args, N var (parent, value, key, segmentNext, flags) = args; -ProcessArgs: -// call node processor if it exists and the `key` is not null. -// the key is null when a descent has re-pushed the descent target. -// this should be safe to skip; we will see its values later. + ProcessArgs: + // call node processor if it exists and the `key` is not null. + // the key is null when a descent has re-pushed the descent target. + // this should be safe to skip; we will see its values later. if ( key != null ) processor?.Invoke( parent, value, key, segmentNext ); @@ -136,7 +177,9 @@ private static IEnumerable EnumerateMatches( TNode root, NodeArgs args, N // reference to the next segment in the list var segmentCurrent = segmentNext; // get current segment - var (selector, selectorKind) = segmentCurrent.Selectors[0]; // first selector in segment + + var selectors = segmentCurrent.Selectors; + var (selector, selectorKind) = selectors[0]; segmentNext = segmentNext.Next; @@ -168,9 +211,8 @@ private static IEnumerable EnumerateMatches( TNode root, NodeArgs args, N // optimization: avoid immediate push pop // // replaces stack.Push( value, childValue, selector, segmentNext ); - DeconstructValues( out parent, out value, out key, out segmentNext, out flags, - (value, childValue, selector, segmentNext, NodeFlags.Default) - ); + + (parent, value, key, segmentNext, flags) = (value, childValue, selector, segmentNext, NodeFlags.Default); goto ProcessArgs; } @@ -179,152 +221,151 @@ private static IEnumerable EnumerateMatches( TNode root, NodeArgs args, N // group selector - var selectorCount = segmentCurrent.Selectors.Length; + var selectorCount = selectors.Length; for ( var i = 0; i < selectorCount; i++ ) // using 'for' for performance { if ( i > 0 ) // we already have the first selector - (selector, selectorKind) = segmentCurrent.Selectors[i]; + (selector, selectorKind) = selectors[i]; switch ( selectorKind ) { // descendant case SelectorKind.Descendant: - { - var children = Descriptor.NodeActions.GetChildren( value, complexTypesOnly: true ); - stack.PushMany( value, children, segmentCurrent, NodeFlags.AfterDescent ); + { + var children = Descriptor.NodeActions.GetChildrenWithName( value, ChildEnumerationOptions.Reverse | ChildEnumerationOptions.ComplexTypesOnly ); + stack.PushMany( value, children, segmentCurrent, NodeFlags.AfterDescent ); - // Union Processing After Descent: If a union operator immediately follows a - // descendant operator, the union should only process simple values. This is - // to prevent duplication of complex objects that would result from both the - // current node and the union processing the same items. + // Union Processing After Descent: If a union operator immediately follows a + // descendant operator, the union should only process simple values. This is + // to prevent duplication of complex objects that would result from both the + // current node and the union processing the same items. - // optimization: avoid immediate push pop - // - // this is safe because descendant only ever has one selector. - // replaces stack.Push( value, childValue, selector, segmentNext ); - DeconstructValues( out parent, out value, out key, out segmentNext, out flags, // process the current value - (parent, value, null, segmentNext, NodeFlags.AfterDescent) - ); - goto ProcessArgs; - } + // optimization: avoid immediate push pop + // + // this is safe because descendant only ever has one selector. + // replaces stack.Push( value, childValue, selector, segmentNext ); + + (parent, value, key, segmentNext, flags) = (parent, value, null, segmentNext, NodeFlags.AfterDescent); // process the current value + goto ProcessArgs; + } // wildcard case SelectorKind.Wildcard: - { - var childKind = GetSelectorKind( nodeKind ); + { + var childKind = GetSelectorKind( nodeKind ); - foreach ( var (childValue, childKey) in Descriptor.NodeActions.GetChildren( value ) ) + foreach ( var (childValue, childKey) in Descriptor.NodeActions.GetChildrenWithName( value, ChildEnumerationOptions.Reverse ) ) + { + // optimization: quicker return for final + // + // the parser will work without this check, but we would be forcing it + // to push and pop values onto the stack that we know will not be used. + if ( segmentNext.IsFinal ) { - // optimization: quicker return for final - // - // the parser will work without this check, but we would be forcing it - // to push and pop values onto the stack that we know will not be used. - if ( segmentNext.IsFinal ) - { - // we could just yield here, but we can't because we want to preserve - // the order of the results as per the RFC. so we push the current - // value onto the stack without prepending the childKey or childKind - // to set up for an immediate return on the next iteration. - stack.Push( value, childValue, childKey, segmentNext ); - continue; - } - - stack.Push( parent, value, childKey, segmentNext.Prepend( childKey, childKind ) ); // (Name | Index) + // if we didn't care about RFC order, we could just yield here. to + // order of the results as per the RFC we need to push the current + // value onto the stack without prepending the childKey or childKind + // to set up for an immediate return on the next iteration. + stack.Push( value, childValue, childKey, segmentNext ); + continue; } - continue; + stack.Push( parent, value, childKey, segmentNext.Prepend( childKey, childKind ) ); // (Name | Index) } + continue; + } + // [?exp] case SelectorKind.Filter: + { + var childKind = GetSelectorKind( nodeKind ); + + foreach ( var (childValue, childKey) in Descriptor.NodeActions.GetChildrenWithName( value, ChildEnumerationOptions.Reverse ) ) { - var childKind = GetSelectorKind( nodeKind ); + if ( !FilterRuntime.Evaluate( selector[1..], childValue, root ) ) // remove the leading '?' character + continue; - foreach ( var (childValue, childKey) in Descriptor.NodeActions.GetChildren( value ) ) + // optimization: quicker return for tail values + if ( segmentNext.IsFinal ) { - if ( !FilterRuntime.Evaluate( selector[1..], childValue, root ) ) // remove the leading '?' character - continue; - - // optimization: quicker return for tail values - if ( segmentNext.IsFinal ) - { - // yielding would not preserve the order of the results as per the RFC. - stack.Push( value, childValue, childKey, segmentNext ); - continue; - } - - stack.Push( parent, value, childKey, segmentNext.Prepend( childKey, childKind ) ); // (Name | Index) + // yielding would not preserve the order of the results as per the RFC. + stack.Push( value, childValue, childKey, segmentNext ); + continue; } - continue; + stack.Push( parent, value, childKey, segmentNext.Prepend( childKey, childKind ) ); // (Name | Index) } + continue; + } + // Array: [#,#,...] case SelectorKind.Index: - { - if ( nodeKind != NodeKind.Array ) - continue; - - if ( accessor.TryGetIndexAt( value, int.Parse( selector ), out var childValue ) ) - { - stack.Push( value, childValue, selector, segmentNext ); - } - + { + if ( nodeKind != NodeKind.Array ) continue; + + if ( accessor.TryGetIndexAt( value, int.Parse( selector ), out var childValue ) ) + { + stack.Push( value, childValue, selector, segmentNext ); } + continue; + } + // Array: [start:end:step] Python slice syntax case SelectorKind.Slice: - { - if ( nodeKind != NodeKind.Array ) - continue; + { + if ( nodeKind != NodeKind.Array ) + continue; - var (upper, lower, step) = GetSliceRange( accessor, value, selector ); + var (upper, lower, step) = GetSliceRange( accessor, value, selector ); - for ( var index = lower; step > 0 ? index < upper : index > upper; index += step ) - { - var childValue = accessor.IndexAt( value, index ); - stack.Push( value, childValue, index, segmentNext ); - } - - continue; + for ( var index = lower; step > 0 ? index < upper : index > upper; index += step ) + { + var childValue = accessor.IndexAt( value, index ); + stack.Push( value, childValue, index, segmentNext ); } + continue; + } + // Array: [name1,name2,...] Names over array case SelectorKind.Name when nodeKind == NodeKind.Array: - { - var indexSegment = segmentNext.Prepend( selector, SelectorKind.Name ); - var length = accessor.GetArrayLength( value ); - - for ( var index = length - 1; index >= 0; index-- ) - { - var childValue = accessor.IndexAt( value, index ); + { + var indexSegment = segmentNext.Prepend( selector, SelectorKind.Name ); + var length = accessor.GetArrayLength( value ); - if ( flags == NodeFlags.AfterDescent && accessor.GetNodeKind( childValue ) != NodeKind.Value ) - continue; + for ( var index = length - 1; index >= 0; index-- ) + { + var childValue = accessor.IndexAt( value, index ); - stack.Push( value, childValue, index, indexSegment ); - } + if ( flags == NodeFlags.AfterDescent && accessor.GetNodeKind( childValue ) != NodeKind.Value ) + continue; - continue; + stack.Push( value, childValue, index, indexSegment ); } + continue; + } + // Object: [name1,name2,...] Names over object case SelectorKind.Name when nodeKind == NodeKind.Object: + { + if ( accessor.TryGetProperty( value, selector, out var childValue ) ) { - if ( accessor.TryGetProperty( value, selector, out var childValue ) ) - { - stack.Push( value, childValue, selector, segmentNext ); - } - - continue; + stack.Push( value, childValue, selector, segmentNext ); } + continue; + } + default: - { - throw new NotSupportedException( $"Unsupported {nameof( SelectorKind )}." ); - } + { + throw new NotSupportedException( $"Unsupported {nameof(SelectorKind)}." ); + } } // end switch } // end for group selector @@ -332,17 +373,6 @@ private static IEnumerable EnumerateMatches( TNode root, NodeArgs args, N } while ( stack.TryPop( out args ) ); } - [MethodImpl( MethodImplOptions.AggressiveInlining )] - private static void DeconstructValues( out TNode parent, out TNode value, out string key, out JsonSegment segmentNext, out NodeFlags flags, - (TNode Parent, TNode Value, string Key, JsonSegment SegmentNext, NodeFlags Flags) values ) - { - parent = values.Parent; - value = values.Value; - key = values.Key; - segmentNext = values.SegmentNext; - flags = values.Flags; - } - private static bool TryGetChild( IValueAccessor accessor, in TNode value, NodeKind nodeKind, string childSelector, SelectorKind selectorKind, out TNode childValue ) { switch ( nodeKind ) @@ -384,43 +414,145 @@ private static SelectorKind GetSelectorKind( NodeKind nodeKind ) { NodeKind.Object => SelectorKind.Name, NodeKind.Array => SelectorKind.Index, - _ => throw new ArgumentOutOfRangeException( nameof( nodeKind ), nodeKind, $"{nameof( NodeKind )} must be an object or an array." ) + _ => throw new ArgumentOutOfRangeException( nameof(nodeKind), nodeKind, $"{nameof(NodeKind)} must be an object or an array." ) }; } - [DebuggerDisplay( "Parent = {Parent}, Value = {Value}, {Segment}" )] - private readonly record struct NodeArgs( TNode Parent, TNode Value, string Key, JsonSegment Segment, NodeFlags Flags ); + private readonly record struct NodeArgs( in TNode Parent, in TNode Value, string Key, JsonSegment Segment, NodeFlags Flags ); - [DebuggerDisplay( "{_stack}" )] - private sealed class NodeArgsStack( int capacity = 8 ) + [DebuggerDisplay( "{_array}" )] + private sealed class NodeArgsStack : IDisposable { [DebuggerBrowsable( DebuggerBrowsableState.RootHidden )] - private readonly Stack _stack = new( capacity ); + private NodeArgs[] _array; + private int _count; + private bool _disposed; - [MethodImpl( MethodImplOptions.AggressiveInlining )] - public void Push( in TNode parent, in TNode value, string key, in JsonSegment segment, NodeFlags flags = NodeFlags.Default ) + public NodeArgsStack(int capacity = 16) { - _stack.Push( new NodeArgs( parent, value, key, segment, flags ) ); + _array = ArrayPool.Shared.Rent(capacity); + _count = 0; + _disposed = false; } - [MethodImpl( MethodImplOptions.AggressiveInlining )] - public void Push( in TNode parent, in TNode value, int index, in JsonSegment segment, NodeFlags flags = NodeFlags.Default ) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Push(in TNode parent, in TNode value, string key, in JsonSegment segment, NodeFlags flags = NodeFlags.Default) + { + EnsureNotDisposed(); + + if (_count == _array.Length) + Grow(); + + _array[_count++] = new NodeArgs(parent, value, key, segment, flags); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Push(in TNode parent, in TNode value, int index, in JsonSegment segment, NodeFlags flags = NodeFlags.Default) { - _stack.Push( new NodeArgs( parent, value, IndexHelper.GetIndexString( index ), segment, flags ) ); + Push(parent, value, IndexHelper.GetIndexString(index), segment, flags); } public void PushMany( in TNode parent, in IEnumerable<(TNode Value, string Key)> items, in JsonSegment segment, NodeFlags flags = NodeFlags.Default ) { + EnsureNotDisposed(); + foreach ( var (value, key) in items ) + Push( parent, value, key, segment, flags ); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryPop( out NodeArgs args ) + { + EnsureNotDisposed(); + + if ( _count == 0 ) { - _stack.Push( new NodeArgs( parent, value, key, segment, flags ) ); + args = default; + return false; } + + var i = --_count; + + args = _array[i]; + //_array[i] = default; // clear entry + + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Grow() + { + int newSize = _array.Length * 2; + var newArray = ArrayPool.Shared.Rent(newSize); + Array.Copy(_array, newArray, _array.Length); + ArrayPool.Shared.Return(_array, clearArray: true); + _array = newArray; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void EnsureCapacity(int min) + { + if ( _array.Length >= min ) + return; + + var newSize = Math.Max(_array.Length * 2, min); + var newArray = ArrayPool.Shared.Rent(newSize); + Array.Copy(_array, newArray, _array.Length); + ArrayPool.Shared.Return(_array, clearArray: true); + _array = newArray; } [MethodImpl( MethodImplOptions.AggressiveInlining )] - public bool TryPop( out NodeArgs args ) + private void EnsureNotDisposed() { - return _stack.TryPop( out args ); + if ( !_disposed ) + return; + + throw new ObjectDisposedException(nameof(NodeArgsStack)); + } + + public void Dispose() + { + if ( _disposed ) + return; + + ArrayPool.Shared.Return(_array, clearArray: true); + _array = null; + _disposed = true; } } + + //private sealed class NodeArgsStack( int capacity = 8 ) + //{ + // [DebuggerBrowsable( DebuggerBrowsableState.RootHidden )] + // private readonly Stack _stack = new(capacity); + + // [MethodImpl( MethodImplOptions.AggressiveInlining )] + // public void Push( in TNode parent, in TNode value, string key, in JsonSegment segment, NodeFlags flags = NodeFlags.Default ) + // { + // _stack.Push( new NodeArgs( parent, value, key, segment, flags ) ); + // } + + // [MethodImpl( MethodImplOptions.AggressiveInlining )] + // public void Push( in TNode parent, in TNode value, int index, in JsonSegment segment, NodeFlags flags = NodeFlags.Default ) + // { + // _stack.Push( new NodeArgs( parent, value, IndexHelper.GetIndexString( index ), segment, flags ) ); + // } + + // public void PushMany( in TNode parent, in IEnumerable<(TNode Value, string Key)> items, in JsonSegment segment, NodeFlags flags = NodeFlags.Default ) + // { + // foreach ( var (value, key) in items ) + // { + // _stack.Push( new NodeArgs( parent, value, key, segment, flags ) ); + // } + // } + + // [MethodImpl( MethodImplOptions.AggressiveInlining )] + // public bool TryPop( out NodeArgs args ) + // { + // return _stack.TryPop( out args ); + // } + //} } + + diff --git a/src/Hyperbee.Json/Query/JsonSegment.cs b/src/Hyperbee.Json/Query/JsonSegment.cs index 126f5c7b..4e8f916a 100644 --- a/src/Hyperbee.Json/Query/JsonSegment.cs +++ b/src/Hyperbee.Json/Query/JsonSegment.cs @@ -1,5 +1,6 @@ using System.Collections; using System.Diagnostics; +using System.Runtime.CompilerServices; namespace Hyperbee.Json.Query; @@ -9,6 +10,7 @@ public record SelectorDescriptor public SelectorKind SelectorKind { get; internal set; } public string Value { get; init; } + [MethodImpl( MethodImplOptions.AggressiveInlining )] public void Deconstruct( out string value, out SelectorKind selectorKind ) { value = Value; @@ -17,36 +19,61 @@ public void Deconstruct( out string value, out SelectorKind selectorKind ) } [DebuggerTypeProxy( typeof( SegmentDebugView ) )] -[DebuggerDisplay( "First = ({Selectors?[0]}), IsSingular = {IsSingular}, Count = {Selectors?.Length}" )] +[DebuggerDisplay( "First = {Selectors.Length == 0 ? null : Selectors[0].Value}, IsSingular = {IsSingular}, Count = {Selectors.Length}" )] public class JsonSegment : IEnumerable { internal static readonly JsonSegment Final = new(); // special end node - public bool IsFinal => Next == null; + private readonly SelectorDescriptor[] _selectors; - public bool IsSingular { get; } // singular is true when the selector resolves to one and only one element + public bool IsFinal + { + [MethodImpl( MethodImplOptions.AggressiveInlining )] + get => Next == null; + } + + // singular is true when the selector resolves to one and only one element + public bool IsSingular + { + [MethodImpl( MethodImplOptions.AggressiveInlining )] + get; + } + + public JsonSegment Next + { + [MethodImpl( MethodImplOptions.AggressiveInlining )] + get; - public JsonSegment Next { get; set; } - public SelectorDescriptor[] Selectors { get; init; } + [MethodImpl( MethodImplOptions.AggressiveInlining )] + set; + } + + public SelectorDescriptor[] Selectors + { + [MethodImpl( MethodImplOptions.AggressiveInlining )] + get => _selectors ?? []; + } - private JsonSegment() { } + private JsonSegment() + { + IsSingular = false; + } public JsonSegment( JsonSegment next, string selector, SelectorKind kind ) { Next = next; - Selectors = - [ - new SelectorDescriptor { SelectorKind = kind, Value = selector } - ]; + _selectors = [new SelectorDescriptor { SelectorKind = kind, Value = selector }]; IsSingular = SetIsSingular(); } public JsonSegment( SelectorDescriptor[] selectors ) { - Selectors = selectors; + _selectors = selectors; + IsSingular = SetIsSingular(); } - + + [MethodImpl( MethodImplOptions.AggressiveInlining )] public JsonSegment Prepend( string selector, SelectorKind kind ) { return new JsonSegment( this, selector, kind ); @@ -54,6 +81,7 @@ public JsonSegment Prepend( string selector, SelectorKind kind ) public bool IsNormalized { + [MethodImpl( MethodImplOptions.AggressiveInlining )] get { var current = this; @@ -70,6 +98,7 @@ public bool IsNormalized } } + [MethodImpl( MethodImplOptions.AggressiveInlining )] private bool SetIsSingular() { // the segment is singular, when there is only one selector @@ -81,6 +110,7 @@ private bool SetIsSingular() return (Selectors[0].SelectorKind & SelectorKind.Singular) == SelectorKind.Singular; } + [MethodImpl( MethodImplOptions.AggressiveInlining )] public void Deconstruct( out bool singular, out SelectorDescriptor[] selectors ) { singular = IsSingular; diff --git a/test/Hyperbee.Json.Benchmark/Hyperbee.Json.Benchmark.csproj b/test/Hyperbee.Json.Benchmark/Hyperbee.Json.Benchmark.csproj index 7201256b..ab72340e 100644 --- a/test/Hyperbee.Json.Benchmark/Hyperbee.Json.Benchmark.csproj +++ b/test/Hyperbee.Json.Benchmark/Hyperbee.Json.Benchmark.csproj @@ -16,6 +16,7 @@ + diff --git a/test/Hyperbee.Json.Benchmark/JsonPathParseAndSelectEvaluator.cs b/test/Hyperbee.Json.Benchmark/JsonPathParseAndSelectEvaluator.cs index 1213a16b..adaff9fe 100644 --- a/test/Hyperbee.Json.Benchmark/JsonPathParseAndSelectEvaluator.cs +++ b/test/Hyperbee.Json.Benchmark/JsonPathParseAndSelectEvaluator.cs @@ -1,6 +1,7 @@ using System.Text.Json; using System.Text.Json.Nodes; using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Engines; using Hyperbee.Json.Extensions; using JsonCons.JsonPath; using Newtonsoft.Json.Linq; @@ -8,19 +9,41 @@ namespace Hyperbee.Json.Benchmark; + public class JsonPathParseAndSelectEvaluator { + //[Params( + // "$.store.book[0]", + // "$.store.book[?(@.price == 8.99)]", + // "$..price", + // "$..* ::First()", + // "$..*" + //)] [Params( - "$.store.book[0]", - "$.store.book[?(@.price == 8.99)]", - "$..price", - "$..* `First()`", - "$..*" + "$.store.book[0].title", + "$.store.book[*].author", + "$.store.book[?(@.price < 10)].title", + "$.store.bicycle.color", + "$.store.book[*]", + "$.store..price", + "$..author", + "$.store.book[?(@.price > 10 && @.price < 20)]", + "$.store.book[?(@.category == 'fiction')]", + "$.store.book[-1:]", + "$.store.book[:2]", + "$..book[0,1]", + "$..*", + "$..['bicycle','price']", + "$..[?(@.price < 10)]", + "$.store.book[?(@.author && @.title)]", + "$.store.*" )] public string Filter; public string Document; + public Consumer _consumer = new (); + [GlobalSetup] public void Setup() { @@ -67,77 +90,86 @@ public void Setup() public (string, bool) GetFilter() { - const string First = " `First()`"; + const string First = " ::First()"; return Filter.EndsWith( First ) ? (Filter[..^First.Length], true) : (Filter, false); } - [Benchmark] + private void Consume( IEnumerable select, bool takeFirst ) + { + if ( takeFirst ) + _ = select.First(); + else + select.Consume( _consumer ); + } + + [Benchmark( Description = "Hyperbee.JsonElement" )] public void Hyperbee_JsonElement() { var (filter, first) = GetFilter(); var element = JsonDocument.Parse( Document ).RootElement; + var select = element.Select( filter ); - if ( first ) - _ = element.Select( filter ).First(); - else - _ = element.Select( filter ).ToArray(); + Consume( select, first ); } - [Benchmark] - public void Hyperbee_JsonNode() - { - var (filter, first) = GetFilter(); + //[Benchmark( Description = "Hyperbee.JsonNode" )] + //public void Hyperbee_JsonNode() + //{ + // var (filter, first) = GetFilter(); - var node = JsonNode.Parse( Document )!; + // var node = JsonNode.Parse( Document )!; + // var select = node.Select( filter ); - if ( first ) - _ = node.Select( filter ).First(); - else - _ = node.Select( filter ).ToArray(); - } + // Consume( select, first ); + //} - [Benchmark] - public void Newtonsoft_JObject() - { - var (filter, first) = GetFilter(); + //[Benchmark( Description = "Newtonsoft.JObject" )] + //public void Newtonsoft_JObject() + //{ + // var (filter, first) = GetFilter(); - var jObject = JObject.Parse( Document ); + // var jObject = JObject.Parse( Document ); + // var select = jObject.SelectTokens( filter ); - if ( first ) - _ = jObject.SelectTokens( filter ).First(); - else - _ = jObject.SelectTokens( filter ).ToArray(); - } + // Consume( select, first ); + //} - [Benchmark] - public void JsonEverything_JsonNode() - { - var (filter, first) = GetFilter(); + //[Benchmark( Description = "JsonEverything.JsonNode" )] + //public void JsonEverything_JsonNode() + //{ + // var (filter, first) = GetFilter(); - var path = JsonEverything.JsonPath.Parse( filter ); - var node = JsonNode.Parse( Document )!; + // var path = JsonEverything.JsonPath.Parse( filter ); + // var node = JsonNode.Parse( Document )!; + // var select = path.Evaluate( node ).Matches!; - if ( first ) - _ = path.Evaluate( node ).Matches!.First(); - else - _ = path.Evaluate( node ).Matches!.ToArray(); - } + // Consume( select, first ); + //} + + //[Benchmark( Description = "JsonCons.JsonElement" )] + //public void JsonCons_JsonElement() + //{ + // var (filter, first) = GetFilter(); + + // var path = JsonSelector.Parse( filter )!; + // var element = JsonDocument.Parse( Document ).RootElement; + // var select = path.Select( element ); - [Benchmark] - public void JsonCons_JsonElement() + // Consume( select, first ); + //} + + [Benchmark( Description = "JsonCraft.JsonElement" )] + public void JsonCraft_JsonElement() { var (filter, first) = GetFilter(); - var path = JsonSelector.Parse( filter )!; var element = JsonDocument.Parse( Document ).RootElement; + var select = JsonCraft.JsonPath.JsonExtensions.SelectElements( element, filter ); - if ( first ) - _ = path.Select( element ).First(); - else - _ = path.Select( element ); + Consume( select, first ); } } diff --git a/test/Hyperbee.Json.Benchmark/JsonPathSelectEvaluator.cs b/test/Hyperbee.Json.Benchmark/JsonPathSelectEvaluator.cs index 0fb5d8d3..51ec4986 100644 --- a/test/Hyperbee.Json.Benchmark/JsonPathSelectEvaluator.cs +++ b/test/Hyperbee.Json.Benchmark/JsonPathSelectEvaluator.cs @@ -1,6 +1,7 @@ using System.Text.Json; using System.Text.Json.Nodes; using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Engines; using Hyperbee.Json.Extensions; using Newtonsoft.Json.Linq; @@ -27,9 +28,10 @@ public class JsonPathSelectEvaluator public JsonNode _node; public JsonElement _element; - private JObject _jObject; + private readonly Consumer _consumer = new(); + [GlobalSetup] public void Setup() { @@ -74,26 +76,34 @@ public void Setup() """; _jObject = JObject.Parse( document ); - _node = JsonNode.Parse( document )!; _element = JsonDocument.Parse( document ).RootElement; } - [Benchmark] + private void Consume( IEnumerable items ) + { + foreach ( var item in items ) + _consumer.Consume( item ); + } + + [Benchmark( Description = "Hyperbee.JsonElement" )] public void Hyperbee_JsonElement() { - var _ = _element.Select( Filter ).ToArray(); + var result = _element.Select( Filter ); + Consume( result ); } - [Benchmark] + [Benchmark( Description = "Hyperbee.JsonNode")] public void Hyperbee_JsonNode() { - var _ = _node.Select( Filter ).ToArray(); + var result = _node.Select( Filter ); + Consume( result ); } - [Benchmark] + [Benchmark( Description = "Newtonsoft.JObject" )] public void Newtonsoft_JObject() { - var _ = _jObject.SelectTokens( Filter ).ToArray(); + var result = _jObject.SelectTokens( Filter ); + Consume( result ); } } diff --git a/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.FilterExpressionParserEvaluator-report-jsonpath.md b/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.FilterExpressionParserEvaluator-report-jsonpath.md index 66517759..6cf5d55c 100644 --- a/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.FilterExpressionParserEvaluator-report-jsonpath.md +++ b/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.FilterExpressionParserEvaluator-report-jsonpath.md @@ -1,10 +1,10 @@ ``` -BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.3037) -12th Gen Intel Core i9-12900HK, 1 CPU, 20 logical and 14 physical cores -.NET SDK 9.0.103 - [Host] : .NET 9.0.2 (9.0.225.6610), X64 RyuJIT AVX2 [AttachedDebugger] - ShortRun : .NET 9.0.2 (9.0.225.6610), X64 RyuJIT AVX2 +BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.4061) +Intel Core i9-9980HK CPU 2.40GHz, 1 CPU, 16 logical and 8 physical cores +.NET SDK 9.0.203 + [Host] : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2 + ShortRun : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2 | Method | Mean | Error diff --git a/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonDiffBenchmark-report-jsonpath.md b/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonDiffBenchmark-report-jsonpath.md index c8784508..d28eb2e9 100644 --- a/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonDiffBenchmark-report-jsonpath.md +++ b/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonDiffBenchmark-report-jsonpath.md @@ -1,17 +1,17 @@ ``` -BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.3037) -12th Gen Intel Core i9-12900HK, 1 CPU, 20 logical and 14 physical cores -.NET SDK 9.0.103 - [Host] : .NET 9.0.2 (9.0.225.6610), X64 RyuJIT AVX2 [AttachedDebugger] - ShortRun : .NET 9.0.2 (9.0.225.6610), X64 RyuJIT AVX2 +BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.4061) +Intel Core i9-9980HK CPU 2.40GHz, 1 CPU, 16 logical and 8 physical cores +.NET SDK 9.0.203 + [Host] : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2 + ShortRun : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2 - | Method | Mean | Error | StdDev | Allocated - | :-------------------- | --------: | ---------: | --------: | ---------: - | JsonDiff_JsonNode | 473.9 ns | 83.44 ns | 4.57 ns | 1.2 KB - | JsonDiff_JsonElement | 595.7 ns | 283.98 ns | 15.57 ns | 1.66 KB - | | | | | - | JsonDiff_JsonNode | 591.9 ns | 262.91 ns | 14.41 ns | 1.3 KB - | JsonDiff_JsonElement | 775.5 ns | 75.61 ns | 4.14 ns | 1.93 KB + | Method | Mean | Error | StdDev | Allocated + | :-------------------- | ----------: | ----------: | ---------: | ---------: + | JsonDiff_JsonNode | 778.8 ns | 703.7 ns | 38.57 ns | 1.2 KB + | JsonDiff_JsonElement | 1,370.3 ns | 5,661.7 ns | 310.33 ns | 1.66 KB + | | | | | + | JsonDiff_JsonNode | 1,013.3 ns | 781.3 ns | 42.82 ns | 1.3 KB + | JsonDiff_JsonElement | 1,304.2 ns | 347.2 ns | 19.03 ns | 1.93 KB ``` diff --git a/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonPatchBenchmark-report-jsonpath.md b/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonPatchBenchmark-report-jsonpath.md index f8f9c316..256df84b 100644 --- a/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonPatchBenchmark-report-jsonpath.md +++ b/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonPatchBenchmark-report-jsonpath.md @@ -1,16 +1,16 @@ ``` -BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.3037) -12th Gen Intel Core i9-12900HK, 1 CPU, 20 logical and 14 physical cores -.NET SDK 9.0.103 - [Host] : .NET 9.0.2 (9.0.225.6610), X64 RyuJIT AVX2 [AttachedDebugger] - ShortRun : .NET 9.0.2 (9.0.225.6610), X64 RyuJIT AVX2 +BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.4061) +Intel Core i9-9980HK CPU 2.40GHz, 1 CPU, 16 logical and 8 physical cores +.NET SDK 9.0.203 + [Host] : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2 + ShortRun : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2 - | Method | Mean | Error | StdDev | Allocated - | :----------------------- | --------: | ---------: | -------: | ---------: - | Hyperbee_JsonNode | 139.2 ns | 26.87 ns | 1.47 ns | 520 B - | Hyperbee_JsonElement | 145.1 ns | 26.35 ns | 1.44 ns | 520 B - | JsonEverything_JsonNode | 232.5 ns | 116.18 ns | 6.37 ns | 968 B - | AspNetCore_JsonNode | 441.0 ns | 173.80 ns | 9.53 ns | 1024 B + | Method | Mean | Error | StdDev | Allocated + | :----------------------- | --------: | ---------: | --------: | ---------: + | Hyperbee_JsonElement | 240.7 ns | 95.55 ns | 5.24 ns | 616 B + | Hyperbee_JsonNode | 265.3 ns | 430.49 ns | 23.60 ns | 616 B + | JsonEverything_JsonNode | 444.3 ns | 246.87 ns | 13.53 ns | 968 B + | AspNetCore_JsonNode | 743.4 ns | 801.29 ns | 43.92 ns | 1024 B ``` diff --git a/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonPathParseAndSelectEvaluator-report-jsonpath.md b/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonPathParseAndSelectEvaluator-report-jsonpath.md index 82d7a9b0..a10aab8a 100644 --- a/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonPathParseAndSelectEvaluator-report-jsonpath.md +++ b/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonPathParseAndSelectEvaluator-report-jsonpath.md @@ -1,46 +1,79 @@ ``` -BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.3037) -12th Gen Intel Core i9-12900HK, 1 CPU, 20 logical and 14 physical cores -.NET SDK 9.0.103 - [Host] : .NET 9.0.2 (9.0.225.6610), X64 RyuJIT AVX2 [AttachedDebugger] - ShortRun : .NET 9.0.2 (9.0.225.6610), X64 RyuJIT AVX2 +BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.4202) +Intel Core i9-9980HK CPU 2.40GHz, 1 CPU, 16 logical and 8 physical cores +.NET SDK 9.0.203 + [Host] : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2 + ShortRun : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2 - | Method | Mean | Error | StdDev | Allocated - | :----------------------- | ---------: | ---------: | ---------: | ---------: - | `$..* First()` - | Hyperbee_JsonElement | 1.867 μs | 0.6072 μs | 0.0333 μs | 3.5 KB - | Hyperbee_JsonNode | 1.916 μs | 0.2202 μs | 0.0121 μs | 3.11 KB - | JsonEverything_JsonNode | 2.097 μs | 0.7484 μs | 0.0410 μs | 3.49 KB - | JsonCons_JsonElement | 3.498 μs | 0.8161 μs | 0.0447 μs | 8.48 KB - | Newtonsoft_JObject | 5.734 μs | 1.1777 μs | 0.0646 μs | 14.22 KB - | | | | | + | Method | Mean | Error | StdDev | Allocated + | :--------------------- | ---------: | ---------: | ---------: | ---------: + | `$..[?(@.price < 10)]` + | JsonCraft.JsonElement | 5.309 μs | 1.6696 μs | 0.0915 μs | 3.59 KB + | Hyperbee.JsonElement | 10.112 μs | 2.1872 μs | 0.1199 μs | 20.73 KB + | | | | | + | `$..['bicycle','price']` + | JsonCraft.JsonElement | 4.551 μs | 2.5508 μs | 0.1398 μs | 4.01 KB + | Hyperbee.JsonElement | 4.581 μs | 2.8094 μs | 0.1540 μs | 5.37 KB + | | | | | | `$..*` - | JsonCons_JsonElement | 3.525 μs | 1.0092 μs | 0.0553 μs | 8.45 KB - | Hyperbee_JsonElement | 4.288 μs | 0.4672 μs | 0.0256 μs | 8.38 KB - | Hyperbee_JsonNode | 5.744 μs | 0.8133 μs | 0.0446 μs | 11.22 KB - | Newtonsoft_JObject | 7.111 μs | 1.1213 μs | 0.0615 μs | 14.43 KB - | JsonEverything_JsonNode | 18.571 μs | 5.4711 μs | 0.2999 μs | 34.2 KB - | | | | | - | `$..price` - | Hyperbee_JsonElement | 2.599 μs | 0.2500 μs | 0.0137 μs | 4.11 KB - | JsonCons_JsonElement | 2.962 μs | 0.1946 μs | 0.0107 μs | 5.65 KB - | Hyperbee_JsonNode | 4.107 μs | 0.8757 μs | 0.0480 μs | 8.22 KB - | Newtonsoft_JObject | 6.653 μs | 1.2770 μs | 0.0700 μs | 14.26 KB - | JsonEverything_JsonNode | 13.430 μs | 4.4075 μs | 0.2416 μs | 26.46 KB - | | | | | - | `$.store.book[?(@.price == 8.99)]` - | Hyperbee_JsonElement | 2.778 μs | 0.3989 μs | 0.0219 μs | 5.41 KB - | JsonCons_JsonElement | 3.282 μs | 0.5416 μs | 0.0297 μs | 5.05 KB - | Hyperbee_JsonNode | 4.279 μs | 0.4826 μs | 0.0265 μs | 8.95 KB - | Newtonsoft_JObject | 5.986 μs | 0.7627 μs | 0.0418 μs | 15.78 KB - | JsonEverything_JsonNode | 7.135 μs | 1.1539 μs | 0.0632 μs | 15.5 KB - | | | | | - | `$.store.book[0]` - | Hyperbee_JsonElement | 1.623 μs | 0.3592 μs | 0.0197 μs | 2.27 KB - | Hyperbee_JsonNode | 1.925 μs | 0.1748 μs | 0.0096 μs | 2.86 KB - | JsonCons_JsonElement | 1.937 μs | 0.1595 μs | 0.0087 μs | 3.21 KB - | JsonEverything_JsonNode | 3.095 μs | 0.4032 μs | 0.0221 μs | 5.71 KB - | Newtonsoft_JObject | 5.690 μs | 1.3949 μs | 0.0765 μs | 14.51 KB + | JsonCraft.JsonElement | 3.541 μs | 1.5742 μs | 0.0863 μs | 2.88 KB + | Hyperbee.JsonElement | 4.132 μs | 0.8317 μs | 0.0456 μs | 5.5 KB + | | | | | + | `$..author` + | Hyperbee.JsonElement | 3.958 μs | 2.4245 μs | 0.1329 μs | 5.16 KB + | JsonCraft.JsonElement | 4.014 μs | 2.7295 μs | 0.1496 μs | 2.88 KB + | | | | | + | `$..book[0,1]` + | Hyperbee.JsonElement | 3.964 μs | 1.3351 μs | 0.0732 μs | 5.16 KB + | JsonCraft.JsonElement | 4.027 μs | 1.6110 μs | 0.0883 μs | 3.09 KB + | | | | | + | `$.store..price` + | Hyperbee.JsonElement | 3.846 μs | 0.8747 μs | 0.0479 μs | 4.8 KB + | JsonCraft.JsonElement | 4.126 μs | 0.6668 μs | 0.0365 μs | 3.13 KB + | | | | | + | `$.store.*` + | JsonCraft.JsonElement | 2.610 μs | 0.7912 μs | 0.0434 μs | 2.49 KB + | Hyperbee.JsonElement | 2.983 μs | 2.4118 μs | 0.1322 μs | 2.88 KB + | | | | | + | `$.store.bicycle.color` + | Hyperbee.JsonElement | 2.568 μs | 1.8970 μs | 0.1040 μs | 2.3 KB + | JsonCraft.JsonElement | 2.635 μs | 0.5189 μs | 0.0284 μs | 2.49 KB + | | | | | + | `$.store.book[-1:]` + | JsonCraft.JsonElement | 2.675 μs | 0.8429 μs | 0.0462 μs | 2.58 KB + | Hyperbee.JsonElement | 2.734 μs | 0.5098 μs | 0.0279 μs | 2.47 KB + | | | | | + | `$.store.book[:2]` + | Hyperbee.JsonElement | 2.782 μs | 1.4813 μs | 0.0812 μs | 2.47 KB + | JsonCraft.JsonElement | 2.794 μs | 2.5981 μs | 0.1424 μs | 2.58 KB + | | | | | + | `$.store.book[?(@.author && @.title)]` + | JsonCraft.JsonElement | 3.899 μs | 8.2799 μs | 0.4539 μs | 3.3 KB + | Hyperbee.JsonElement | 4.404 μs | 2.5578 μs | 0.1402 μs | 5.52 KB + | | | | | + | `$.store.book[?(@.category == 'fiction')]` + | JsonCraft.JsonElement | 3.522 μs | 2.0617 μs | 0.1130 μs | 3.38 KB + | Hyperbee.JsonElement | 4.316 μs | 3.5501 μs | 0.1946 μs | 5.09 KB + | | | | | + | `$.store.book[?(@.price < 10)].title` + | Hyperbee.JsonElement | 4.266 μs | 3.2809 μs | 0.1798 μs | 5.1 KB + | JsonCraft.JsonElement | 6.221 μs | 7.3531 μs | 0.4031 μs | 3.37 KB + | | | | | + | `$.store.book[?(@.price > 10 && @.price < 20)]` + | JsonCraft.JsonElement | 4.356 μs | 3.6377 μs | 0.1994 μs | 3.82 KB + | Hyperbee.JsonElement | 4.918 μs | 0.8512 μs | 0.0467 μs | 6.55 KB + | | | | | + | `$.store.book[*].author` + | JsonCraft.JsonElement | 2.994 μs | 1.5486 μs | 0.0849 μs | 2.63 KB + | Hyperbee.JsonElement | 3.377 μs | 0.6483 μs | 0.0355 μs | 3.12 KB + | | | | | + | `$.store.book[*]` + | JsonCraft.JsonElement | 2.674 μs | 0.6596 μs | 0.0362 μs | 2.48 KB + | Hyperbee.JsonElement | 2.891 μs | 1.1848 μs | 0.0649 μs | 2.71 KB + | | | | | + | `$.store.book[0].title` + | Hyperbee.JsonElement | 2.680 μs | 2.2509 μs | 0.1234 μs | 2.27 KB + | JsonCraft.JsonElement | 2.716 μs | 0.7777 μs | 0.0426 μs | 2.55 KB ``` diff --git a/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonPathSelectEvaluator-report-jsonpath.md b/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonPathSelectEvaluator-report-jsonpath.md new file mode 100644 index 00000000..84f84a07 --- /dev/null +++ b/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonPathSelectEvaluator-report-jsonpath.md @@ -0,0 +1,81 @@ +``` + +BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.4061) +Intel Core i9-9980HK CPU 2.40GHz, 1 CPU, 16 logical and 8 physical cores +.NET SDK 9.0.203 + [Host] : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2 + ShortRun : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2 + + + | Method | Mean | Error | StdDev | Allocated + | :----------------------- | -----------: | ------------: | ----------: | ---------: + | `$..*` + | 'Json.Net (Newtonsoft)' | 1,663.17 ns | 306.230 ns | 16.785 ns | 568 B + | Hyperbee.JsonNode | 4,235.65 ns | 1,165.972 ns | 63.911 ns | 5056 B + | Hyperbee.JsonElement | 4,858.12 ns | 898.871 ns | 49.270 ns | 6408 B + | | | | | + | `$..book[?@.isbn]` + | Hyperbee.JsonNode | 2,539.14 ns | 1,668.271 ns | 91.444 ns | 3832 B + | Hyperbee.JsonElement | 2,852.45 ns | 530.695 ns | 29.089 ns | 3904 B + | 'Json.Net (Newtonsoft)' | NA | NA | NA | NA + | | | | | + | `$..book[?@.price == (...)tegory == 'fiction'] [52]` + | Hyperbee.JsonNode | 4,596.58 ns | 1,453.515 ns | 79.672 ns | 6944 B + | Hyperbee.JsonElement | 4,614.84 ns | 3,365.769 ns | 184.489 ns | 6880 B + | 'Json.Net (Newtonsoft)' | NA | NA | NA | NA + | | | | | + | `$..book[0]` + | 'Json.Net (Newtonsoft)' | 1,356.94 ns | 353.834 ns | 19.395 ns | 496 B + | Hyperbee.JsonNode | 1,514.78 ns | 562.260 ns | 30.819 ns | 1952 B + | Hyperbee.JsonElement | 1,763.83 ns | 606.140 ns | 33.225 ns | 1976 B + | | | | | + | `$.store..price` + | 'Json.Net (Newtonsoft)' | 1,386.04 ns | 116.630 ns | 6.393 ns | 536 B + | Hyperbee.JsonNode | 1,432.28 ns | 131.367 ns | 7.201 ns | 1736 B + | Hyperbee.JsonElement | 1,522.55 ns | 325.344 ns | 17.833 ns | 1776 B + | | | | | + | `$.store.*` + | 'Json.Net (Newtonsoft)' | 336.70 ns | 70.197 ns | 3.848 ns | 608 B + | Hyperbee.JsonNode | 460.77 ns | 284.563 ns | 15.598 ns | 960 B + | Hyperbee.JsonElement | 505.11 ns | 111.571 ns | 6.116 ns | 1216 B + | | | | | + | `$.store.book[-1:]` + | Hyperbee.JsonNode | 396.47 ns | 167.716 ns | 9.193 ns | 704 B + | 'Json.Net (Newtonsoft)' | 402.63 ns | 169.272 ns | 9.278 ns | 688 B + | Hyperbee.JsonElement | 411.42 ns | 132.880 ns | 7.284 ns | 896 B + | | | | | + | `$.store.book[?@.price == 8.99]` + | Hyperbee.JsonElement | 1,867.53 ns | 706.136 ns | 38.706 ns | 3624 B + | Hyperbee.JsonNode | 1,963.96 ns | 159.196 ns | 8.726 ns | 3360 B + | 'Json.Net (Newtonsoft)' | NA | NA | NA | NA + | | | | | + | `$.store.book['category','author']` + | 'Json.Net (Newtonsoft)' | 441.16 ns | 67.371 ns | 3.693 ns | 1000 B + | Hyperbee.JsonNode | 965.14 ns | 201.450 ns | 11.042 ns | 1048 B + | Hyperbee.JsonElement | 1,365.77 ns | 264.766 ns | 14.513 ns | 1296 B + | | | | | + | `$.store.book[*].author` + | 'Json.Net (Newtonsoft)' | 621.68 ns | 68.455 ns | 3.752 ns | 840 B + | Hyperbee.JsonElement | 1,169.77 ns | 514.153 ns | 28.182 ns | 1752 B + | Hyperbee.JsonNode | 1,283.55 ns | 2,651.830 ns | 145.356 ns | 1496 B + | | | | | + | `$.store.book[0,1]` + | Hyperbee.JsonElement | 434.92 ns | 196.984 ns | 10.797 ns | 912 B + | Hyperbee.JsonNode | 446.30 ns | 723.205 ns | 39.641 ns | 712 B + | 'Json.Net (Newtonsoft)' | 488.25 ns | 637.440 ns | 34.940 ns | 776 B + | | | | | + | `$.store.book[0]` + | Hyperbee.JsonNode | 197.91 ns | 28.470 ns | 1.561 ns | 320 B + | Hyperbee.JsonElement | 206.25 ns | 117.243 ns | 6.427 ns | 288 B + | 'Json.Net (Newtonsoft)' | 397.65 ns | 39.882 ns | 2.186 ns | 648 B + | | | | | + | `$` + | Hyperbee.JsonElement | 51.24 ns | 6.684 ns | 0.366 ns | 160 B + | 'Json.Net (Newtonsoft)' | 59.21 ns | 30.779 ns | 1.687 ns | 136 B + | Hyperbee.JsonNode | 66.31 ns | 27.840 ns | 1.526 ns | 144 B + +Benchmarks with issues: + JsonPathSelectEvaluator.'Json.Net (Newtonsoft)': ShortRun(IterationCount=3, LaunchCount=1, WarmupCount=3) [Filter=$..book[?@.isbn]] + JsonPathSelectEvaluator.'Json.Net (Newtonsoft)': ShortRun(IterationCount=3, LaunchCount=1, WarmupCount=3) [Filter=$..book[?@.price == (...)tegory == 'fiction'] [52]] + JsonPathSelectEvaluator.'Json.Net (Newtonsoft)': ShortRun(IterationCount=3, LaunchCount=1, WarmupCount=3) [Filter=$.store.book[?@.price == 8.99]] +``` diff --git a/test/Hyperbee.Json.Tests/Path/Query/JsonPathBookstoreTests.cs b/test/Hyperbee.Json.Tests/Path/Query/JsonPathBookstoreTests.cs index 93c9ba33..36312d59 100644 --- a/test/Hyperbee.Json.Tests/Path/Query/JsonPathBookstoreTests.cs +++ b/test/Hyperbee.Json.Tests/Path/Query/JsonPathBookstoreTests.cs @@ -205,7 +205,7 @@ public void FilterAllBooksCheaperThan10( string query, Type sourceType ) public void AllMembersOfJsonStructure( string query, Type sourceType ) { var source = GetDocumentAdapter( sourceType ); - var matches = source.Select( query ); + var matches = source.Select( query ).ToArray(); var expected = new[] { source.FromJsonPathPointer( "$.store" ), From 78bd30713dc8ee0b28e4c9dddf92a9276b835298 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sun, 1 Jun 2025 23:30:38 +0000 Subject: [PATCH 2/3] Updated code formatting to match rules in .editorconfig --- .../Descriptors/Element/ElementActions.cs | 46 ++-- .../Descriptors/Node/NodeActions.cs | 46 ++-- src/Hyperbee.Json/Path/JsonPath.cs | 240 +++++++++--------- src/Hyperbee.Json/Query/JsonSegment.cs | 20 +- .../JsonPathParseAndSelectEvaluator.cs | 24 +- .../JsonPathSelectEvaluator.cs | 2 +- 6 files changed, 189 insertions(+), 189 deletions(-) diff --git a/src/Hyperbee.Json/Descriptors/Element/ElementActions.cs b/src/Hyperbee.Json/Descriptors/Element/ElementActions.cs index bb1286ed..ba9b9a1a 100644 --- a/src/Hyperbee.Json/Descriptors/Element/ElementActions.cs +++ b/src/Hyperbee.Json/Descriptors/Element/ElementActions.cs @@ -47,36 +47,36 @@ public IEnumerable GetChildren( JsonElement value, ChildEnumeration switch ( value.ValueKind ) { case JsonValueKind.Array: - { - var length = value.GetArrayLength(); - results = new List( length ); - - for ( var index = 0; index < length; index++ ) { - var child = value[index]; + var length = value.GetArrayLength(); + results = new List( length ); - if ( complexTypesOnly && child.ValueKind is not (JsonValueKind.Array or JsonValueKind.Object) ) - continue; + for ( var index = 0; index < length; index++ ) + { + var child = value[index]; - results.Add( child ); - } + if ( complexTypesOnly && child.ValueKind is not (JsonValueKind.Array or JsonValueKind.Object) ) + continue; - return reverse ? results.EnumerateReverse() : results; - } - case JsonValueKind.Object: - { - results = new List( 8 ); + results.Add( child ); + } - foreach ( var child in value.EnumerateObject() ) + return reverse ? results.EnumerateReverse() : results; + } + case JsonValueKind.Object: { - if ( complexTypesOnly && child.Value.ValueKind is not (JsonValueKind.Array or JsonValueKind.Object) ) - continue; + results = new List( 8 ); - results.Add( child.Value ); - } + foreach ( var child in value.EnumerateObject() ) + { + if ( complexTypesOnly && child.Value.ValueKind is not (JsonValueKind.Array or JsonValueKind.Object) ) + continue; - return reverse ? results.EnumerateReverse() : results; - } + results.Add( child.Value ); + } + + return reverse ? results.EnumerateReverse() : results; + } } return []; @@ -114,7 +114,7 @@ public IEnumerable GetChildren( JsonElement value, ChildEnumeration } case JsonValueKind.Object: { - results = new List<(JsonElement, string)>( 8 ); + results = new List<(JsonElement, string)>( 8 ); foreach ( var child in value.EnumerateObject() ) { diff --git a/src/Hyperbee.Json/Descriptors/Node/NodeActions.cs b/src/Hyperbee.Json/Descriptors/Node/NodeActions.cs index dabe0b56..c270c56c 100644 --- a/src/Hyperbee.Json/Descriptors/Node/NodeActions.cs +++ b/src/Hyperbee.Json/Descriptors/Node/NodeActions.cs @@ -43,36 +43,36 @@ public IEnumerable GetChildren( JsonNode value, ChildEnumerationOption switch ( value ) { case JsonArray jsonArray: - { - var length = jsonArray.Count; - results = new List( length ); - - for ( var index = 0; index < length; index++ ) { - var child = value[index]; + var length = jsonArray.Count; + results = new List( length ); - if ( complexTypesOnly && child is not (JsonArray or JsonObject) ) - continue; + for ( var index = 0; index < length; index++ ) + { + var child = value[index]; - results.Add( child ); - } + if ( complexTypesOnly && child is not (JsonArray or JsonObject) ) + continue; - return reverse ? results.EnumerateReverse() : results; - } - case JsonObject jsonObject: - { - results = new List( 8 ); + results.Add( child ); + } - foreach ( var child in jsonObject ) + return reverse ? results.EnumerateReverse() : results; + } + case JsonObject jsonObject: { - if ( complexTypesOnly && child.Value is not (JsonArray or JsonObject) ) - continue; + results = new List( 8 ); - results.Add( child.Value ); - } + foreach ( var child in jsonObject ) + { + if ( complexTypesOnly && child.Value is not (JsonArray or JsonObject) ) + continue; + + results.Add( child.Value ); + } - return reverse ? results.EnumerateReverse() : results; - } + return reverse ? results.EnumerateReverse() : results; + } } return []; @@ -94,7 +94,7 @@ public IEnumerable GetChildren( JsonNode value, ChildEnumerationOption case JsonArray jsonArray: { var length = jsonArray.Count; - results = new List<(JsonNode, string)>( length ); + results = new List<(JsonNode, string)>( length ); for ( var index = 0; index < length; index++ ) { diff --git a/src/Hyperbee.Json/Path/JsonPath.cs b/src/Hyperbee.Json/Path/JsonPath.cs index 918cb278..205e3a90 100644 --- a/src/Hyperbee.Json/Path/JsonPath.cs +++ b/src/Hyperbee.Json/Path/JsonPath.cs @@ -88,7 +88,7 @@ internal static IEnumerable SelectInternal( in TNode value, in TNode root private static IEnumerable EnumerateAllDescendants( TNode node, NodeProcessorDelegate processor ) { var kind = Descriptor.ValueAccessor.GetNodeKind( node ); - + if ( kind != NodeKind.Object && kind != NodeKind.Array ) { yield return node; @@ -114,7 +114,7 @@ private static IEnumerable EnumerateAllDescendants( TNode node, NodeProce { foreach ( var (child, key) in Descriptor.NodeActions.GetChildrenWithName( current, ChildEnumerationOptions.None ) ) { - processor.Invoke( current, child, key, default ); + processor.Invoke( current, child, key, default ); yield return child; } } @@ -157,10 +157,10 @@ private static IEnumerable EnumerateMatches( TNode root, NodeArgs args, N var (parent, value, key, segmentNext, flags) = args; - ProcessArgs: - // call node processor if it exists and the `key` is not null. - // the key is null when a descent has re-pushed the descent target. - // this should be safe to skip; we will see its values later. +ProcessArgs: +// call node processor if it exists and the `key` is not null. +// the key is null when a descent has re-pushed the descent target. +// this should be safe to skip; we will see its values later. if ( key != null ) processor?.Invoke( parent, value, key, segmentNext ); @@ -177,7 +177,7 @@ private static IEnumerable EnumerateMatches( TNode root, NodeArgs args, N // reference to the next segment in the list var segmentCurrent = segmentNext; // get current segment - + var selectors = segmentCurrent.Selectors; var (selector, selectorKind) = selectors[0]; @@ -232,140 +232,140 @@ private static IEnumerable EnumerateMatches( TNode root, NodeArgs args, N { // descendant case SelectorKind.Descendant: - { - var children = Descriptor.NodeActions.GetChildrenWithName( value, ChildEnumerationOptions.Reverse | ChildEnumerationOptions.ComplexTypesOnly ); - stack.PushMany( value, children, segmentCurrent, NodeFlags.AfterDescent ); + { + var children = Descriptor.NodeActions.GetChildrenWithName( value, ChildEnumerationOptions.Reverse | ChildEnumerationOptions.ComplexTypesOnly ); + stack.PushMany( value, children, segmentCurrent, NodeFlags.AfterDescent ); - // Union Processing After Descent: If a union operator immediately follows a - // descendant operator, the union should only process simple values. This is - // to prevent duplication of complex objects that would result from both the - // current node and the union processing the same items. + // Union Processing After Descent: If a union operator immediately follows a + // descendant operator, the union should only process simple values. This is + // to prevent duplication of complex objects that would result from both the + // current node and the union processing the same items. - // optimization: avoid immediate push pop - // - // this is safe because descendant only ever has one selector. - // replaces stack.Push( value, childValue, selector, segmentNext ); + // optimization: avoid immediate push pop + // + // this is safe because descendant only ever has one selector. + // replaces stack.Push( value, childValue, selector, segmentNext ); - (parent, value, key, segmentNext, flags) = (parent, value, null, segmentNext, NodeFlags.AfterDescent); // process the current value - goto ProcessArgs; - } + (parent, value, key, segmentNext, flags) = (parent, value, null, segmentNext, NodeFlags.AfterDescent); // process the current value + goto ProcessArgs; + } // wildcard case SelectorKind.Wildcard: - { - var childKind = GetSelectorKind( nodeKind ); - - foreach ( var (childValue, childKey) in Descriptor.NodeActions.GetChildrenWithName( value, ChildEnumerationOptions.Reverse ) ) { - // optimization: quicker return for final - // - // the parser will work without this check, but we would be forcing it - // to push and pop values onto the stack that we know will not be used. - if ( segmentNext.IsFinal ) + var childKind = GetSelectorKind( nodeKind ); + + foreach ( var (childValue, childKey) in Descriptor.NodeActions.GetChildrenWithName( value, ChildEnumerationOptions.Reverse ) ) { - // if we didn't care about RFC order, we could just yield here. to - // order of the results as per the RFC we need to push the current - // value onto the stack without prepending the childKey or childKind - // to set up for an immediate return on the next iteration. - stack.Push( value, childValue, childKey, segmentNext ); - continue; + // optimization: quicker return for final + // + // the parser will work without this check, but we would be forcing it + // to push and pop values onto the stack that we know will not be used. + if ( segmentNext.IsFinal ) + { + // if we didn't care about RFC order, we could just yield here. to + // order of the results as per the RFC we need to push the current + // value onto the stack without prepending the childKey or childKind + // to set up for an immediate return on the next iteration. + stack.Push( value, childValue, childKey, segmentNext ); + continue; + } + + stack.Push( parent, value, childKey, segmentNext.Prepend( childKey, childKind ) ); // (Name | Index) } - stack.Push( parent, value, childKey, segmentNext.Prepend( childKey, childKind ) ); // (Name | Index) + continue; } - continue; - } - // [?exp] case SelectorKind.Filter: - { - var childKind = GetSelectorKind( nodeKind ); - - foreach ( var (childValue, childKey) in Descriptor.NodeActions.GetChildrenWithName( value, ChildEnumerationOptions.Reverse ) ) { - if ( !FilterRuntime.Evaluate( selector[1..], childValue, root ) ) // remove the leading '?' character - continue; + var childKind = GetSelectorKind( nodeKind ); - // optimization: quicker return for tail values - if ( segmentNext.IsFinal ) + foreach ( var (childValue, childKey) in Descriptor.NodeActions.GetChildrenWithName( value, ChildEnumerationOptions.Reverse ) ) { - // yielding would not preserve the order of the results as per the RFC. - stack.Push( value, childValue, childKey, segmentNext ); - continue; + if ( !FilterRuntime.Evaluate( selector[1..], childValue, root ) ) // remove the leading '?' character + continue; + + // optimization: quicker return for tail values + if ( segmentNext.IsFinal ) + { + // yielding would not preserve the order of the results as per the RFC. + stack.Push( value, childValue, childKey, segmentNext ); + continue; + } + + stack.Push( parent, value, childKey, segmentNext.Prepend( childKey, childKind ) ); // (Name | Index) } - stack.Push( parent, value, childKey, segmentNext.Prepend( childKey, childKind ) ); // (Name | Index) + continue; } - continue; - } - // Array: [#,#,...] case SelectorKind.Index: - { - if ( nodeKind != NodeKind.Array ) - continue; - - if ( accessor.TryGetIndexAt( value, int.Parse( selector ), out var childValue ) ) { - stack.Push( value, childValue, selector, segmentNext ); - } + if ( nodeKind != NodeKind.Array ) + continue; - continue; - } + if ( accessor.TryGetIndexAt( value, int.Parse( selector ), out var childValue ) ) + { + stack.Push( value, childValue, selector, segmentNext ); + } + + continue; + } // Array: [start:end:step] Python slice syntax case SelectorKind.Slice: - { - if ( nodeKind != NodeKind.Array ) - continue; + { + if ( nodeKind != NodeKind.Array ) + continue; - var (upper, lower, step) = GetSliceRange( accessor, value, selector ); + var (upper, lower, step) = GetSliceRange( accessor, value, selector ); - for ( var index = lower; step > 0 ? index < upper : index > upper; index += step ) - { - var childValue = accessor.IndexAt( value, index ); - stack.Push( value, childValue, index, segmentNext ); - } + for ( var index = lower; step > 0 ? index < upper : index > upper; index += step ) + { + var childValue = accessor.IndexAt( value, index ); + stack.Push( value, childValue, index, segmentNext ); + } - continue; - } + continue; + } // Array: [name1,name2,...] Names over array case SelectorKind.Name when nodeKind == NodeKind.Array: - { - var indexSegment = segmentNext.Prepend( selector, SelectorKind.Name ); - var length = accessor.GetArrayLength( value ); - - for ( var index = length - 1; index >= 0; index-- ) { - var childValue = accessor.IndexAt( value, index ); + var indexSegment = segmentNext.Prepend( selector, SelectorKind.Name ); + var length = accessor.GetArrayLength( value ); - if ( flags == NodeFlags.AfterDescent && accessor.GetNodeKind( childValue ) != NodeKind.Value ) - continue; + for ( var index = length - 1; index >= 0; index-- ) + { + var childValue = accessor.IndexAt( value, index ); - stack.Push( value, childValue, index, indexSegment ); - } + if ( flags == NodeFlags.AfterDescent && accessor.GetNodeKind( childValue ) != NodeKind.Value ) + continue; - continue; - } + stack.Push( value, childValue, index, indexSegment ); + } + + continue; + } // Object: [name1,name2,...] Names over object case SelectorKind.Name when nodeKind == NodeKind.Object: - { - if ( accessor.TryGetProperty( value, selector, out var childValue ) ) { - stack.Push( value, childValue, selector, segmentNext ); - } + if ( accessor.TryGetProperty( value, selector, out var childValue ) ) + { + stack.Push( value, childValue, selector, segmentNext ); + } - continue; - } + continue; + } default: - { - throw new NotSupportedException( $"Unsupported {nameof(SelectorKind)}." ); - } + { + throw new NotSupportedException( $"Unsupported {nameof( SelectorKind )}." ); + } } // end switch } // end for group selector @@ -414,7 +414,7 @@ private static SelectorKind GetSelectorKind( NodeKind nodeKind ) { NodeKind.Object => SelectorKind.Name, NodeKind.Array => SelectorKind.Index, - _ => throw new ArgumentOutOfRangeException( nameof(nodeKind), nodeKind, $"{nameof(NodeKind)} must be an object or an array." ) + _ => throw new ArgumentOutOfRangeException( nameof( nodeKind ), nodeKind, $"{nameof( NodeKind )} must be an object or an array." ) }; } @@ -428,28 +428,28 @@ private sealed class NodeArgsStack : IDisposable private int _count; private bool _disposed; - public NodeArgsStack(int capacity = 16) + public NodeArgsStack( int capacity = 16 ) { - _array = ArrayPool.Shared.Rent(capacity); + _array = ArrayPool.Shared.Rent( capacity ); _count = 0; _disposed = false; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Push(in TNode parent, in TNode value, string key, in JsonSegment segment, NodeFlags flags = NodeFlags.Default) + [MethodImpl( MethodImplOptions.AggressiveInlining )] + public void Push( in TNode parent, in TNode value, string key, in JsonSegment segment, NodeFlags flags = NodeFlags.Default ) { EnsureNotDisposed(); - if (_count == _array.Length) + if ( _count == _array.Length ) Grow(); - _array[_count++] = new NodeArgs(parent, value, key, segment, flags); + _array[_count++] = new NodeArgs( parent, value, key, segment, flags ); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Push(in TNode parent, in TNode value, int index, in JsonSegment segment, NodeFlags flags = NodeFlags.Default) + [MethodImpl( MethodImplOptions.AggressiveInlining )] + public void Push( in TNode parent, in TNode value, int index, in JsonSegment segment, NodeFlags flags = NodeFlags.Default ) { - Push(parent, value, IndexHelper.GetIndexString(index), segment, flags); + Push( parent, value, IndexHelper.GetIndexString( index ), segment, flags ); } public void PushMany( in TNode parent, in IEnumerable<(TNode Value, string Key)> items, in JsonSegment segment, NodeFlags flags = NodeFlags.Default ) @@ -460,7 +460,7 @@ public void PushMany( in TNode parent, in IEnumerable<(TNode Value, string Key)> Push( parent, value, key, segment, flags ); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl( MethodImplOptions.AggressiveInlining )] public bool TryPop( out NodeArgs args ) { EnsureNotDisposed(); @@ -472,33 +472,33 @@ public bool TryPop( out NodeArgs args ) } var i = --_count; - + args = _array[i]; //_array[i] = default; // clear entry return true; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl( MethodImplOptions.AggressiveInlining )] private void Grow() { int newSize = _array.Length * 2; - var newArray = ArrayPool.Shared.Rent(newSize); - Array.Copy(_array, newArray, _array.Length); - ArrayPool.Shared.Return(_array, clearArray: true); + var newArray = ArrayPool.Shared.Rent( newSize ); + Array.Copy( _array, newArray, _array.Length ); + ArrayPool.Shared.Return( _array, clearArray: true ); _array = newArray; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void EnsureCapacity(int min) + [MethodImpl( MethodImplOptions.AggressiveInlining )] + private void EnsureCapacity( int min ) { if ( _array.Length >= min ) return; - var newSize = Math.Max(_array.Length * 2, min); - var newArray = ArrayPool.Shared.Rent(newSize); - Array.Copy(_array, newArray, _array.Length); - ArrayPool.Shared.Return(_array, clearArray: true); + var newSize = Math.Max( _array.Length * 2, min ); + var newArray = ArrayPool.Shared.Rent( newSize ); + Array.Copy( _array, newArray, _array.Length ); + ArrayPool.Shared.Return( _array, clearArray: true ); _array = newArray; } @@ -508,7 +508,7 @@ private void EnsureNotDisposed() if ( !_disposed ) return; - throw new ObjectDisposedException(nameof(NodeArgsStack)); + throw new ObjectDisposedException( nameof( NodeArgsStack ) ); } public void Dispose() @@ -516,7 +516,7 @@ public void Dispose() if ( _disposed ) return; - ArrayPool.Shared.Return(_array, clearArray: true); + ArrayPool.Shared.Return( _array, clearArray: true ); _array = null; _disposed = true; } diff --git a/src/Hyperbee.Json/Query/JsonSegment.cs b/src/Hyperbee.Json/Query/JsonSegment.cs index 4e8f916a..c03c7035 100644 --- a/src/Hyperbee.Json/Query/JsonSegment.cs +++ b/src/Hyperbee.Json/Query/JsonSegment.cs @@ -33,19 +33,19 @@ public bool IsFinal } // singular is true when the selector resolves to one and only one element - public bool IsSingular + public bool IsSingular { [MethodImpl( MethodImplOptions.AggressiveInlining )] - get; - } + get; + } - public JsonSegment Next + public JsonSegment Next { [MethodImpl( MethodImplOptions.AggressiveInlining )] get; [MethodImpl( MethodImplOptions.AggressiveInlining )] - set; + set; } public SelectorDescriptor[] Selectors @@ -54,9 +54,9 @@ public SelectorDescriptor[] Selectors get => _selectors ?? []; } - private JsonSegment() - { - IsSingular = false; + private JsonSegment() + { + IsSingular = false; } public JsonSegment( JsonSegment next, string selector, SelectorKind kind ) @@ -69,10 +69,10 @@ public JsonSegment( JsonSegment next, string selector, SelectorKind kind ) public JsonSegment( SelectorDescriptor[] selectors ) { _selectors = selectors; - + IsSingular = SetIsSingular(); } - + [MethodImpl( MethodImplOptions.AggressiveInlining )] public JsonSegment Prepend( string selector, SelectorKind kind ) { diff --git a/test/Hyperbee.Json.Benchmark/JsonPathParseAndSelectEvaluator.cs b/test/Hyperbee.Json.Benchmark/JsonPathParseAndSelectEvaluator.cs index adaff9fe..8f4d7e42 100644 --- a/test/Hyperbee.Json.Benchmark/JsonPathParseAndSelectEvaluator.cs +++ b/test/Hyperbee.Json.Benchmark/JsonPathParseAndSelectEvaluator.cs @@ -24,25 +24,25 @@ public class JsonPathParseAndSelectEvaluator "$.store.book[*].author", "$.store.book[?(@.price < 10)].title", "$.store.bicycle.color", - "$.store.book[*]", - "$.store..price", - "$..author", - "$.store.book[?(@.price > 10 && @.price < 20)]", - "$.store.book[?(@.category == 'fiction')]", + "$.store.book[*]", + "$.store..price", + "$..author", + "$.store.book[?(@.price > 10 && @.price < 20)]", + "$.store.book[?(@.category == 'fiction')]", "$.store.book[-1:]", - "$.store.book[:2]", + "$.store.book[:2]", "$..book[0,1]", - "$..*", - "$..['bicycle','price']", - "$..[?(@.price < 10)]", - "$.store.book[?(@.author && @.title)]", - "$.store.*" + "$..*", + "$..['bicycle','price']", + "$..[?(@.price < 10)]", + "$.store.book[?(@.author && @.title)]", + "$.store.*" )] public string Filter; public string Document; - public Consumer _consumer = new (); + public Consumer _consumer = new(); [GlobalSetup] public void Setup() diff --git a/test/Hyperbee.Json.Benchmark/JsonPathSelectEvaluator.cs b/test/Hyperbee.Json.Benchmark/JsonPathSelectEvaluator.cs index 51ec4986..63221b7d 100644 --- a/test/Hyperbee.Json.Benchmark/JsonPathSelectEvaluator.cs +++ b/test/Hyperbee.Json.Benchmark/JsonPathSelectEvaluator.cs @@ -93,7 +93,7 @@ public void Hyperbee_JsonElement() Consume( result ); } - [Benchmark( Description = "Hyperbee.JsonNode")] + [Benchmark( Description = "Hyperbee.JsonNode" )] public void Hyperbee_JsonNode() { var result = _node.Select( Filter ); From 8023ecc8b7f5a920e69d377bb5c484e613f0cdab Mon Sep 17 00:00:00 2001 From: "annette.findley" Date: Fri, 27 Jun 2025 11:06:02 -0400 Subject: [PATCH 3/3] Updated benchmark, moved PathSelect to JsonPathSelect. Updated read.me with benchmark data --- README.md | 237 +++++++++++++--- test/Hyperbee.Json.Benchmark/Config.cs | 2 +- .../JsonPathParseAndSelectEvaluator.cs | 94 ++++--- .../JsonPathSelectEvaluator.cs | 109 -------- ...pressionParserEvaluator-report-jsonpath.md | 10 +- ...hmark.JsonDiffBenchmark-report-jsonpath.md | 24 +- ...mark.JsonPatchBenchmark-report-jsonpath.md | 22 +- ...ParseAndSelectEvaluator-report-jsonpath.md | 254 ++++++++++++++---- ...JsonPathSelectEvaluator-report-jsonpath.md | 122 ++++----- 9 files changed, 540 insertions(+), 334 deletions(-) delete mode 100644 test/Hyperbee.Json.Benchmark/JsonPathSelectEvaluator.cs diff --git a/README.md b/README.md index f857c054..d3d2eaa4 100644 --- a/README.md +++ b/README.md @@ -96,42 +96,207 @@ Here is a performance comparison of various JSONPath queries on the standard boo } ``` - | Method | Mean | Error | StdDev | Allocated - | :----------------------- | ---------: | ----------: | ---------: | ---------: - | `$..* First()` - | Hyperbee_JsonElement | 2.874 μs | 1.6256 μs | 0.0891 μs | 3.52 KB - | Hyperbee_JsonNode | 3.173 μs | 0.7979 μs | 0.0437 μs | 3.09 KB - | JsonEverything_JsonNode | 3.199 μs | 2.4697 μs | 0.1354 μs | 3.53 KB - | JsonCons_JsonElement | 5.976 μs | 8.4042 μs | 0.4607 μs | 8.48 KB - | Newtonsoft_JObject | 9.219 μs | 2.9245 μs | 0.1603 μs | 14.22 KB - | | | | | - | `$..*` - | JsonCons_JsonElement | 5.674 μs | 3.8650 μs | 0.2119 μs | 8.45 KB - | Hyperbee_JsonElement | 7.934 μs | 3.5907 μs | 0.1968 μs | 9.13 KB - | Hyperbee_JsonNode | 10.457 μs | 7.7120 μs | 0.4227 μs | 10.91 KB - | Newtonsoft_JObject | 10.722 μs | 4.1310 μs | 0.2264 μs | 14.86 KB - | JsonEverything_JsonNode | 23.096 μs | 10.8629 μs | 0.5954 μs | 36.81 KB - | | | | | - | `$..price` - | Hyperbee_JsonElement | 4.428 μs | 4.6731 μs | 0.2561 μs | 4.2 KB - | JsonCons_JsonElement | 5.355 μs | 1.1624 μs | 0.0637 μs | 5.65 KB - | Hyperbee_JsonNode | 7.931 μs | 0.6970 μs | 0.0382 μs | 7.48 KB - | Newtonsoft_JObject | 10.334 μs | 8.2331 μs | 0.4513 μs | 14.4 KB - | JsonEverything_JsonNode | 17.000 μs | 14.9812 μs | 0.8212 μs | 27.63 KB - | | | | | - | `$.store.book[?(@.price == 8.99)]` - | Hyperbee_JsonElement | 4.153 μs | 3.6089 μs | 0.1978 μs | 5.24 KB - | JsonCons_JsonElement | 4.873 μs | 1.0395 μs | 0.0570 μs | 5.05 KB - | Hyperbee_JsonNode | 6.980 μs | 5.1007 μs | 0.2796 μs | 8 KB - | Newtonsoft_JObject | 10.629 μs | 3.9096 μs | 0.2143 μs | 15.84 KB - | JsonEverything_JsonNode | 11.133 μs | 7.2544 μs | 0.3976 μs | 15.85 KB - | | | | | - | `$.store.book[0]` - | Hyperbee_JsonElement | 2.677 μs | 2.2733 μs | 0.1246 μs | 2.27 KB - | Hyperbee_JsonNode | 3.126 μs | 3.5345 μs | 0.1937 μs | 2.77 KB - | JsonCons_JsonElement | 3.229 μs | 0.0681 μs | 0.0037 μs | 3.21 KB - | JsonEverything_JsonNode | 4.612 μs | 2.0037 μs | 0.1098 μs | 5.96 KB - | Newtonsoft_JObject | 9.627 μs | 1.1498 μs | 0.0630 μs | 14.56 KB +| Method | Mean | Error | StdDev | Gen0 | Gen1 | Allocated | +|------------------------ |----------:|----------:|----------:|-------:|-------:|----------:| +|`$..[?(@.price < 10)]` +| JsonCraft.JsonElement | 3.841 us | 0.3111 us | 0.0171 us | 0.2899 | - | 3.59 KB | +| JsonCons.JsonElement | 7.459 us | 0.6412 us | 0.0351 us | 1.0376 | 0.0076 | 12.77 KB | +| Hyperbee.JsonElement | 8.284 us | 2.5497 us | 0.1398 us | 1.6785 | 0.0153 | 20.73 KB | +| Hyperbee.JsonNode | 11.086 us | 8.0907 us | 0.4435 us | 1.8921 | - | 23.79 KB | +| Newtonsoft.JObject | 12.153 us | 2.4881 us | 0.1364 us | 2.1057 | 0.0763 | 25.86 KB | +| JsonEverything.JsonNode | 23.541 us | 1.3946 us | 0.0764 us | 3.9063 | 0.1221 | 48.15 KB | +| | | | | | | | +| `$..['bicycle','price']` +| JsonCraft.JsonElement | 3.136 us | 0.2760 us | 0.0151 us | 0.3242 | - | 4.01 KB | +| Hyperbee.JsonElement | 3.578 us | 0.4623 us | 0.0253 us | 0.4349 | 0.0038 | 5.37 KB | +| JsonCons.JsonElement | 3.948 us | 0.6099 us | 0.0334 us | 0.5798 | - | 7.18 KB | +| Hyperbee.JsonNode | 5.181 us | 1.8016 us | 0.0988 us | 0.7477 | 0.0076 | 9.23 KB | +| Newtonsoft.JObject | 7.823 us | 0.8017 us | 0.0439 us | 1.1749 | 0.0458 | 14.55 KB | +| JsonEverything.JsonNode | 16.753 us | 1.5507 us | 0.0850 us | 2.3193 | 0.0610 | 28.5 KB | +| +| ` $..*` | | | | | | | +| JsonCraft.JsonElement | 2.497 us | 0.0903 us | 0.0049 us | 0.2327 | - | 2.88 KB | +| Hyperbee.JsonElement | 3.299 us | 0.8178 us | 0.0448 us | 0.5302 | 0.0038 | 6.51 KB | +| JsonCons.JsonElement | 4.176 us | 0.5887 us | 0.0323 us | 0.6866 | - | 8.49 KB | +| Hyperbee.JsonNode | 5.330 us | 0.7684 us | 0.0421 us | 0.8392 | - | 10.3 KB | +| Newtonsoft.JObject | 7.784 us | 0.5303 us | 0.0291 us | 1.1444 | 0.0458 | 14.19 KB | +| JsonEverything.JsonNode | 21.226 us | 1.0905 us | 0.0598 us | 2.7466 | 0.0610 | 33.97 KB | +| | | | | | | | +|`$..author` +| JsonCraft.JsonElement | 2.904 us | 4.2915 us | 0.2352 us | 0.2327 | - | 2.88 KB | +| Hyperbee.JsonElement | 3.068 us | 0.3672 us | 0.0201 us | 0.4196 | 0.0038 | 5.16 KB | +| JsonCons.JsonElement | 3.381 us | 0.6294 us | 0.0345 us | 0.4501 | - | 5.55 KB | +| Hyperbee.JsonNode | 5.030 us | 0.6100 us | 0.0334 us | 0.7324 | 0.0076 | 9.02 KB | +| Newtonsoft.JObject | 7.469 us | 1.7563 us | 0.0963 us | 1.1444 | 0.0305 | 14.2 KB | +| JsonEverything.JsonNode | 15.752 us | 3.8966 us | 0.2136 us | 2.1057 | 0.0305 | 26.1 KB | +| | | | | | | | +|`$..book[?@.isbn]` +| Hyperbee.JsonElement | 3.913 us | 1.5846 us | 0.0869 us | 0.5493 | - | 6.8 KB | +| JsonCons.JsonElement | 4.359 us | 2.1655 us | 0.1187 us | 0.5875 | - | 7.21 KB | +| Hyperbee.JsonNode | 5.335 us | 0.7057 us | 0.0387 us | 0.8621 | 0.0153 | 10.62 KB | +| JsonEverything.JsonNode | 17.200 us | 1.9836 us | 0.1087 us | 2.4414 | 0.0610 | 29.98 KB | +| JsonCraft.JsonElement | NA | NA | NA | NA | NA | NA | +| Newtonsoft.JObject | NA | NA | NA | NA | NA | NA | +| | | | | | | | +|`$..book[?@.price == 8.99 && @.category == 'fiction']` +| Hyperbee.JsonElement | 5.135 us | 1.0302 us | 0.0565 us | 0.7706 | - | 9.47 KB | +| JsonCons.JsonElement | 6.105 us | 0.6309 us | 0.0346 us | 0.6943 | 0.0076 | 8.52 KB | +| Hyperbee.JsonNode | 7.002 us | 3.0008 us | 0.1645 us | 1.0910 | 0.0229 | 13.41 KB | +| JsonEverything.JsonNode | 21.845 us | 3.9271 us | 0.2153 us | 3.2043 | 0.0916 | 39.52 KB | +| JsonCraft.JsonElement | NA | NA | NA | NA | NA | NA | +| Newtonsoft.JObject | NA | NA | NA | NA | NA | NA | +| | | | | | | | +`$..book[0,1]` +| JsonCraft.JsonElement | 2.936 us | 0.6578 us | 0.0361 us | 0.2518 | - | 3.09 KB | +| Hyperbee.JsonElement | 3.157 us | 0.8302 us | 0.0455 us | 0.4196 | 0.0038 | 5.16 KB | +| JsonCons.JsonElement | 3.691 us | 0.1430 us | 0.0078 us | 0.4997 | - | 6.15 KB | +| Hyperbee.JsonNode | 4.972 us | 0.6586 us | 0.0361 us | 0.7324 | 0.0076 | 9.02 KB | +| Newtonsoft.JObject | 7.441 us | 0.5961 us | 0.0327 us | 1.1749 | 0.0458 | 14.45 KB | +| JsonEverything.JsonNode | 15.523 us | 5.4908 us | 0.3010 us | 2.1362 | 0.0610 | 26.41 KB | +| | | | | | | | +|`$..book[0]` +| JsonCraft.JsonElement | 2.825 us | 0.0934 us | 0.0051 us | 0.2441 | - | 3 KB | +| Hyperbee.JsonElement | 3.114 us | 0.4106 us | 0.0225 us | 0.4196 | 0.0038 | 5.16 KB | +| JsonCons.JsonElement | 3.451 us | 0.2921 us | 0.0160 us | 0.4578 | 0.0038 | 5.63 KB | +| Hyperbee.JsonNode | 4.691 us | 1.4048 us | 0.0770 us | 0.7324 | 0.0076 | 9.02 KB | +| Newtonsoft.JObject | 7.720 us | 0.8748 us | 0.0480 us | 1.1597 | 0.0458 | 14.33 KB | +| JsonEverything.JsonNode | 15.314 us | 3.3459 us | 0.1834 us | 2.1210 | 0.0610 | 26.02 KB | +| | | | | | | | +|`$.store..price` +| JsonCraft.JsonElement | 2.912 us | 1.3083 us | 0.0717 us | 0.2518 | - | 3.13 KB | +| Hyperbee.JsonElement | 2.993 us | 0.4534 us | 0.0249 us | 0.3891 | 0.0038 | 4.8 KB | +| JsonCons.JsonElement | 3.446 us | 0.9305 us | 0.0510 us | 0.4578 | - | 5.62 KB | +| Hyperbee.JsonNode | 4.721 us | 0.9516 us | 0.0522 us | 0.7095 | 0.0076 | 8.7 KB | +| Newtonsoft.JObject | 7.723 us | 1.1978 us | 0.0657 us | 1.1597 | 0.0305 | 14.34 KB | +| JsonEverything.JsonNode | 15.966 us | 1.1138 us | 0.0610 us | 2.1362 | 0.0610 | 26.63 KB | +| | | | | | | | +|`$.store.*` +| JsonCraft.JsonElement | 1.882 us | 0.5586 us | 0.0306 us | 0.2022 | - | 2.49 KB | +| JsonCons.JsonElement | 2.151 us | 0.1067 us | 0.0058 us | 0.2670 | - | 3.31 KB | +| Hyperbee.JsonElement | 2.167 us | 0.6457 us | 0.0354 us | 0.2327 | - | 2.88 KB | +| Hyperbee.JsonNode | 2.435 us | 0.9690 us | 0.0531 us | 0.2403 | - | 2.95 KB | +| JsonEverything.JsonNode | 3.047 us | 0.4674 us | 0.0256 us | 0.3891 | - | 4.8 KB | +| Newtonsoft.JObject | 6.576 us | 0.8290 us | 0.0454 us | 1.1749 | 0.0458 | 14.43 KB | +| | | | | | | | +|`$.store.bicycle.color` +| Hyperbee.JsonElement | 1.920 us | 0.6179 us | 0.0339 us | 0.1869 | - | 2.3 KB | +| JsonCraft.JsonElement | 2.032 us | 0.7770 us | 0.0426 us | 0.2022 | - | 2.49 KB | +| JsonCons.JsonElement | 2.216 us | 0.1473 us | 0.0081 us | 0.2670 | - | 3.27 KB | +| Hyperbee.JsonNode | 2.383 us | 1.2149 us | 0.0666 us | 0.2327 | - | 2.88 KB | +| JsonEverything.JsonNode | 3.556 us | 0.5152 us | 0.0282 us | 0.4654 | 0.0038 | 5.74 KB | +| Newtonsoft.JObject | 6.700 us | 1.6404 us | 0.0899 us | 1.1826 | 0.0381 | 14.49 KB | +| | | | | | | | +|`$.store.book[-1:]` +| JsonCraft.JsonElement | 2.004 us | 0.3158 us | 0.0173 us | 0.2098 | - | 2.58 KB | +| Hyperbee.JsonElement | 2.087 us | 0.1721 us | 0.0094 us | 0.1984 | - | 2.47 KB | +| JsonCons.JsonElement | 2.399 us | 0.3533 us | 0.0194 us | 0.2899 | - | 3.56 KB | +| Hyperbee.JsonNode | 2.467 us | 0.9814 us | 0.0538 us | 0.2403 | - | 2.97 KB | +| JsonEverything.JsonNode | 3.679 us | 0.4354 us | 0.0239 us | 0.4654 | 0.0038 | 5.72 KB | +| Newtonsoft.JObject | 6.472 us | 1.5636 us | 0.0857 us | 1.1826 | 0.0534 | 14.52 KB | +| | | | | | | | +|`$.store.book[:2]` +| JsonCraft.JsonElement | 2.010 us | 0.1463 us | 0.0080 us | 0.2098 | - | 2.58 KB | +| Hyperbee.JsonElement | 2.128 us | 0.4884 us | 0.0268 us | 0.1984 | - | 2.47 KB | +| JsonCons.JsonElement | 2.382 us | 0.0585 us | 0.0032 us | 0.2899 | - | 3.59 KB | +| Hyperbee.JsonNode | 2.450 us | 0.1728 us | 0.0095 us | 0.2403 | - | 2.97 KB | +| JsonEverything.JsonNode | 4.019 us | 2.1096 us | 0.1156 us | 0.4883 | 0.0038 | 6.02 KB | +| Newtonsoft.JObject | 6.899 us | 0.6775 us | 0.0371 us | 1.1826 | 0.0305 | 14.51 KB | +| | | | | | | | +|`$.store.book[?(@.author && @.title)]` +| JsonCraft.JsonElement | 2.567 us | 0.2239 us | 0.0123 us | 0.2670 | - | 3.3 KB | +| Hyperbee.JsonElement | 3.362 us | 0.2369 us | 0.0130 us | 0.4501 | - | 5.52 KB | +| JsonCons.JsonElement | 3.805 us | 0.8769 us | 0.0481 us | 0.4578 | 0.0038 | 5.63 KB | +| Hyperbee.JsonNode | 5.128 us | 1.1406 us | 0.0625 us | 0.7477 | 0.0076 | 9.23 KB | +| Newtonsoft.JObject | 7.514 us | 1.6892 us | 0.0926 us | 1.3199 | 0.0458 | 16.18 KB | +| JsonEverything.JsonNode | 9.261 us | 3.4741 us | 0.1904 us | 1.4801 | 0.0305 | 18.32 KB | +| | | | | | | | +|`$.store.book[?(@.category == 'fiction')]` +| JsonCraft.JsonElement | 2.734 us | 0.3242 us | 0.0178 us | 0.2747 | - | 3.38 KB | +| Hyperbee.JsonElement | 3.315 us | 0.3024 us | 0.0166 us | 0.4120 | - | 5.09 KB | +| JsonCons.JsonElement | 3.426 us | 1.0773 us | 0.0590 us | 0.4120 | - | 5.05 KB | +| Hyperbee.JsonNode | 5.003 us | 0.8363 us | 0.0458 us | 0.7248 | 0.0153 | 8.89 KB | +| Newtonsoft.JObject | 7.213 us | 1.1931 us | 0.0654 us | 1.2817 | 0.0458 | 15.74 KB | +| JsonEverything.JsonNode | 8.898 us | 1.8821 us | 0.1032 us | 1.3428 | 0.0305 | 16.49 KB | +| | | | | | | | +|`$.store.book[?(@.price < 10)].title` +| JsonCraft.JsonElement | 3.108 us | 1.8864 us | 0.1034 us | 0.2747 | - | 3.37 KB | +| Hyperbee.JsonElement | 3.353 us | 0.4814 us | 0.0264 us | 0.4158 | - | 5.1 KB | +| JsonCons.JsonElement | 4.008 us | 0.5005 us | 0.0274 us | 0.4272 | - | 5.27 KB | +| Hyperbee.JsonNode | 5.285 us | 0.7662 us | 0.0420 us | 0.7019 | - | 8.78 KB | +| Newtonsoft.JObject | 7.687 us | 1.0056 us | 0.0551 us | 1.2817 | 0.0458 | 15.89 KB | +| JsonEverything.JsonNode | 9.513 us | 6.3528 us | 0.3482 us | 1.4038 | - | 17.38 KB | +| | | | | | | | +|`$.store.book[?(@.price > 10 && @.price < 20)]` +| JsonCraft.JsonElement | 3.475 us | 0.0797 us | 0.0044 us | 0.3090 | - | 3.82 KB | +| Hyperbee.JsonElement | 4.010 us | 0.6169 us | 0.0338 us | 0.5341 | - | 6.55 KB | +| JsonCons.JsonElement | 5.102 us | 1.1449 us | 0.0628 us | 0.5112 | - | 6.28 KB | +| Hyperbee.JsonNode | 6.086 us | 1.3252 us | 0.0726 us | 0.8316 | 0.0153 | 10.27 KB | +| Newtonsoft.JObject | 8.050 us | 0.6497 us | 0.0356 us | 1.3580 | 0.0458 | 16.69 KB | +| JsonEverything.JsonNode | 11.612 us | 0.2688 us | 0.0147 us | 1.8158 | 0.0458 | 22.27 KB | +| | | | | | | | +|`$.store.book[?@.price == 8.99]` +| Hyperbee.JsonElement | 3.162 us | 0.9502 us | 0.0521 us | 0.3967 | - | 4.9 KB | +| JsonCons.JsonElement | 3.822 us | 0.0869 us | 0.0048 us | 0.4044 | - | 5.02 KB | +| Hyperbee.JsonNode | 4.889 us | 1.5106 us | 0.0828 us | 0.6943 | 0.0076 | 8.58 KB | +| JsonEverything.JsonNode | 8.310 us | 2.0101 us | 0.1102 us | 1.2512 | 0.0305 | 15.47 KB | +| JsonCraft.JsonElement | NA | NA | NA | NA | NA | NA | +| Newtonsoft.JObject | NA | NA | NA | NA | NA | NA | +| | | | | | | | +|`$.store.book['category','author']` +| JsonCraft.JsonElement | 2.084 us | 0.4663 us | 0.0256 us | 0.2403 | - | 2.95 KB | +| JsonCons.JsonElement | 2.480 us | 0.0770 us | 0.0042 us | 0.2937 | - | 3.61 KB | +| Hyperbee.JsonElement | 2.569 us | 1.1453 us | 0.0628 us | 0.2174 | - | 2.67 KB | +| JsonEverything.JsonNode | 3.309 us | 0.2755 us | 0.0151 us | 0.4387 | 0.0038 | 5.41 KB | +| Hyperbee.JsonNode | 4.142 us | 1.4670 us | 0.0804 us | 0.5188 | 0.0076 | 6.42 KB | +| Newtonsoft.JObject | 6.737 us | 0.5860 us | 0.0321 us | 1.2054 | 0.0534 | 14.85 KB | +| | | | | | | | +|`$.store.book[*].author` +| JsonCraft.JsonElement | 2.256 us | 1.4487 us | 0.0794 us | 0.2136 | - | 2.63 KB | +| Hyperbee.JsonElement | 2.469 us | 0.7492 us | 0.0411 us | 0.2518 | - | 3.12 KB | +| JsonCons.JsonElement | 2.496 us | 0.3427 us | 0.0188 us | 0.2899 | - | 3.59 KB | +| Hyperbee.JsonNode | 4.088 us | 0.0348 us | 0.0019 us | 0.5569 | 0.0076 | 6.83 KB | +| Newtonsoft.JObject | 7.169 us | 1.8980 us | 0.1040 us | 1.1902 | 0.0534 | 14.64 KB | +| JsonEverything.JsonNode | 7.511 us | 0.2592 us | 0.0142 us | 1.0071 | 0.0153 | 12.45 KB | +| | | | | | | | +|`$.store.book[*]` +| JsonCraft.JsonElement | 2.048 us | 1.8693 us | 0.1025 us | 0.2022 | - | 2.48 KB | +| Hyperbee.JsonElement | 2.179 us | 0.6664 us | 0.0365 us | 0.2213 | - | 2.71 KB | +| JsonCons.JsonElement | 2.246 us | 0.1811 us | 0.0099 us | 0.2747 | - | 3.4 KB | +| Hyperbee.JsonNode | 2.672 us | 0.2099 us | 0.0115 us | 0.2556 | - | 3.17 KB | +| JsonEverything.JsonNode | 4.315 us | 0.0253 us | 0.0014 us | 0.5341 | - | 6.61 KB | +| Newtonsoft.JObject | 6.632 us | 1.6876 us | 0.0925 us | 1.1826 | 0.0381 | 14.49 KB | +| | | | | | | | +|`$.store.book[0,1]` +| Hyperbee.JsonElement | 2.049 us | 0.0126 us | 0.0007 us | 0.1984 | - | 2.47 KB | +| JsonCraft.JsonElement | 2.056 us | 0.2169 us | 0.0119 us | 0.2136 | - | 2.64 KB | +| Hyperbee.JsonNode | 2.435 us | 0.5524 us | 0.0303 us | 0.2403 | - | 2.97 KB | +| JsonCons.JsonElement | 2.554 us | 1.3052 us | 0.0715 us | 0.3052 | - | 3.77 KB | +| JsonEverything.JsonNode | 3.987 us | 0.4523 us | 0.0248 us | 0.4883 | - | 6.07 KB | +| Newtonsoft.JObject | 6.648 us | 0.7321 us | 0.0401 us | 1.1902 | 0.0534 | 14.59 KB | +| | | | | | | | +|`$.store.book[0].title` +| Hyperbee.JsonElement | 1.897 us | 0.1620 us | 0.0089 us | 0.1831 | - | 2.27 KB | +| JsonCraft.JsonElement | 2.055 us | 0.2876 us | 0.0158 us | 0.2060 | - | 2.55 KB | +| JsonCons.JsonElement | 2.318 us | 0.3233 us | 0.0177 us | 0.2708 | - | 3.35 KB | +| Hyperbee.JsonNode | 2.575 us | 0.1096 us | 0.0060 us | 0.2937 | - | 3.63 KB | +| JsonEverything.JsonNode | 4.534 us | 1.5492 us | 0.0849 us | 0.5951 | 0.0076 | 7.38 KB | +| Newtonsoft.JObject | 6.734 us | 2.0745 us | 0.1137 us | 1.1902 | 0.0458 | 14.62 KB | +| | | | | | | | +|`$.store.book[0]` +| Hyperbee.JsonElement | 1.931 us | 0.1500 us | 0.0082 us | 0.1831 | - | 2.27 KB | +| JsonCraft.JsonElement | 1.996 us | 0.1804 us | 0.0099 us | 0.1984 | - | 2.48 KB | +| Hyperbee.JsonNode | 2.204 us | 0.1747 us | 0.0096 us | 0.2327 | - | 2.86 KB | +| JsonCons.JsonElement | 2.221 us | 0.1388 us | 0.0076 us | 0.2632 | - | 3.26 KB | +| JsonEverything.JsonNode | 3.592 us | 0.3654 us | 0.0200 us | 0.4616 | 0.0038 | 5.68 KB | +| Newtonsoft.JObject | 6.892 us | 2.7132 us | 0.1487 us | 1.1749 | 0.0381 | 14.48 KB | +| | | | | | | | +|`$` +| JsonCraft.JsonElement | 1.732 us | 0.0529 us | 0.0029 us | 0.1831 | - | 2.26 KB | +| Hyperbee.JsonElement | 1.751 us | 0.0628 us | 0.0034 us | 0.1850 | - | 2.27 KB | +| JsonEverything.JsonNode | 1.767 us | 0.0675 us | 0.0037 us | 0.1526 | - | 1.88 KB | +| Hyperbee.JsonNode | 1.790 us | 0.1222 us | 0.0067 us | 0.1450 | - | 1.78 KB | +| JsonCons.JsonElement | 1.929 us | 0.1539 us | 0.0084 us | 0.2422 | - | 2.98 KB | +| Newtonsoft.JObject | 6.587 us | 0.7309 us | 0.0401 us | 1.1368 | 0.0381 | 14.01 KB | ## Credits diff --git a/test/Hyperbee.Json.Benchmark/Config.cs b/test/Hyperbee.Json.Benchmark/Config.cs index 113affcc..433ac8af 100644 --- a/test/Hyperbee.Json.Benchmark/Config.cs +++ b/test/Hyperbee.Json.Benchmark/Config.cs @@ -24,7 +24,7 @@ public Config() ); // Customize the summary style to prevent truncation - WithSummaryStyle( SummaryStyle.Default.WithMaxParameterColumnWidth( 50 ) ); + WithSummaryStyle( SummaryStyle.Default.WithMaxParameterColumnWidth( 100 ) ); // Add the custom exporter with specified visible columns AddExporter( new JsonPathMarkdownExporter diff --git a/test/Hyperbee.Json.Benchmark/JsonPathParseAndSelectEvaluator.cs b/test/Hyperbee.Json.Benchmark/JsonPathParseAndSelectEvaluator.cs index 8f4d7e42..875d3767 100644 --- a/test/Hyperbee.Json.Benchmark/JsonPathParseAndSelectEvaluator.cs +++ b/test/Hyperbee.Json.Benchmark/JsonPathParseAndSelectEvaluator.cs @@ -12,13 +12,6 @@ namespace Hyperbee.Json.Benchmark; public class JsonPathParseAndSelectEvaluator { - //[Params( - // "$.store.book[0]", - // "$.store.book[?(@.price == 8.99)]", - // "$..price", - // "$..* ::First()", - // "$..*" - //)] [Params( "$.store.book[0].title", "$.store.book[*].author", @@ -36,11 +29,23 @@ public class JsonPathParseAndSelectEvaluator "$..['bicycle','price']", "$..[?(@.price < 10)]", "$.store.book[?(@.author && @.title)]", - "$.store.*" + "$.store.*", + "$", + "$.store.book[0]", + "$..book[0]", + "$.store.book[0,1]", + "$.store.book['category','author']", + "$..book[?@.isbn]", + "$.store.book[?@.price == 8.99]", + "$..book[?@.price == 8.99 && @.category == 'fiction']" )] public string Filter; public string Document; + public JsonNode _node; + public JsonElement _element; + private JObject _jObject; + public Consumer _consumer = new(); @@ -86,6 +91,11 @@ public void Setup() } } """; + + + _jObject = JObject.Parse( Document ); + _node = JsonNode.Parse( Document )!; + _element = JsonDocument.Parse( Document ).RootElement; } public (string, bool) GetFilter() @@ -116,51 +126,51 @@ public void Hyperbee_JsonElement() Consume( select, first ); } - //[Benchmark( Description = "Hyperbee.JsonNode" )] - //public void Hyperbee_JsonNode() - //{ - // var (filter, first) = GetFilter(); + [Benchmark( Description = "Hyperbee.JsonNode" )] + public void Hyperbee_JsonNode() + { + var (filter, first) = GetFilter(); - // var node = JsonNode.Parse( Document )!; - // var select = node.Select( filter ); + var node = JsonNode.Parse( Document )!; + var select = node.Select( filter ); - // Consume( select, first ); - //} + Consume( select, first ); + } - //[Benchmark( Description = "Newtonsoft.JObject" )] - //public void Newtonsoft_JObject() - //{ - // var (filter, first) = GetFilter(); + [Benchmark( Description = "Newtonsoft.JObject" )] + public void Newtonsoft_JObject() + { + var (filter, first) = GetFilter(); - // var jObject = JObject.Parse( Document ); - // var select = jObject.SelectTokens( filter ); + var jObject = JObject.Parse( Document ); + var select = jObject.SelectTokens( filter ); - // Consume( select, first ); - //} + Consume( select, first ); + } - //[Benchmark( Description = "JsonEverything.JsonNode" )] - //public void JsonEverything_JsonNode() - //{ - // var (filter, first) = GetFilter(); + [Benchmark( Description = "JsonEverything.JsonNode" )] + public void JsonEverything_JsonNode() + { + var (filter, first) = GetFilter(); - // var path = JsonEverything.JsonPath.Parse( filter ); - // var node = JsonNode.Parse( Document )!; - // var select = path.Evaluate( node ).Matches!; + var path = JsonEverything.JsonPath.Parse( filter ); + var node = JsonNode.Parse( Document )!; + var select = path.Evaluate( node ).Matches!; - // Consume( select, first ); - //} + Consume( select, first ); + } - //[Benchmark( Description = "JsonCons.JsonElement" )] - //public void JsonCons_JsonElement() - //{ - // var (filter, first) = GetFilter(); + [Benchmark( Description = "JsonCons.JsonElement" )] + public void JsonCons_JsonElement() + { + var (filter, first) = GetFilter(); - // var path = JsonSelector.Parse( filter )!; - // var element = JsonDocument.Parse( Document ).RootElement; - // var select = path.Select( element ); + var path = JsonSelector.Parse( filter )!; + var element = JsonDocument.Parse( Document ).RootElement; + var select = path.Select( element ); - // Consume( select, first ); - //} + Consume( select, first ); + } [Benchmark( Description = "JsonCraft.JsonElement" )] public void JsonCraft_JsonElement() diff --git a/test/Hyperbee.Json.Benchmark/JsonPathSelectEvaluator.cs b/test/Hyperbee.Json.Benchmark/JsonPathSelectEvaluator.cs deleted file mode 100644 index 63221b7d..00000000 --- a/test/Hyperbee.Json.Benchmark/JsonPathSelectEvaluator.cs +++ /dev/null @@ -1,109 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Nodes; -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Engines; -using Hyperbee.Json.Extensions; -using Newtonsoft.Json.Linq; - -namespace Hyperbee.Json.Benchmark; - -public class JsonPathSelectEvaluator -{ - [Params( - "$", - "$.store.book[0]", - "$.store..price", - "$..book[0]", - "$.store.*", - "$.store.book[*].author", - "$.store.book[-1:]", - "$.store.book[0,1]", - "$.store.book['category','author']", - "$..book[?@.isbn]", - "$.store.book[?@.price == 8.99]", - "$..*", - "$..book[?@.price == 8.99 && @.category == 'fiction']" - )] - public string Filter; - - public JsonNode _node; - public JsonElement _element; - private JObject _jObject; - - private readonly Consumer _consumer = new(); - - [GlobalSetup] - public void Setup() - { - const string document = - """ - { - "store": { - "book": [ - { - "category": "reference", - "author": "Nigel Rees", - "title": "Sayings of the Century", - "price": 8.95 - }, - { - "category": "fiction", - "author": "Evelyn Waugh", - "title": "Sword of Honour", - "price": 12.99 - }, - { - "category": "fiction", - "author": "Herman Melville", - "title": "Moby Dick", - "isbn": "0-553-21311-3", - "price": 8.99 - }, - { - "category": "fiction", - "author": "J. R. R. Tolkien", - "title": "The Lord of the Rings", - "isbn": "0-395-19395-8", - "price": 22.99 - } - ], - "bicycle": { - "color": "red", - "price": 19.95 - } - } - } - """; - - _jObject = JObject.Parse( document ); - _node = JsonNode.Parse( document )!; - _element = JsonDocument.Parse( document ).RootElement; - } - - private void Consume( IEnumerable items ) - { - foreach ( var item in items ) - _consumer.Consume( item ); - } - - [Benchmark( Description = "Hyperbee.JsonElement" )] - public void Hyperbee_JsonElement() - { - var result = _element.Select( Filter ); - Consume( result ); - } - - [Benchmark( Description = "Hyperbee.JsonNode" )] - public void Hyperbee_JsonNode() - { - var result = _node.Select( Filter ); - Consume( result ); - } - - [Benchmark( Description = "Newtonsoft.JObject" )] - public void Newtonsoft_JObject() - { - var result = _jObject.SelectTokens( Filter ); - Consume( result ); - } -} diff --git a/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.FilterExpressionParserEvaluator-report-jsonpath.md b/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.FilterExpressionParserEvaluator-report-jsonpath.md index 6cf5d55c..afa7bc1a 100644 --- a/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.FilterExpressionParserEvaluator-report-jsonpath.md +++ b/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.FilterExpressionParserEvaluator-report-jsonpath.md @@ -1,10 +1,10 @@ ``` -BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.4061) -Intel Core i9-9980HK CPU 2.40GHz, 1 CPU, 16 logical and 8 physical cores -.NET SDK 9.0.203 - [Host] : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2 - ShortRun : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2 +BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.4351) +12th Gen Intel Core i9-12900HK, 1 CPU, 20 logical and 14 physical cores +.NET SDK 9.0.301 + [Host] : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2 [AttachedDebugger] + ShortRun : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2 | Method | Mean | Error diff --git a/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonDiffBenchmark-report-jsonpath.md b/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonDiffBenchmark-report-jsonpath.md index d28eb2e9..008be474 100644 --- a/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonDiffBenchmark-report-jsonpath.md +++ b/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonDiffBenchmark-report-jsonpath.md @@ -1,17 +1,17 @@ ``` -BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.4061) -Intel Core i9-9980HK CPU 2.40GHz, 1 CPU, 16 logical and 8 physical cores -.NET SDK 9.0.203 - [Host] : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2 - ShortRun : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2 +BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.4351) +12th Gen Intel Core i9-12900HK, 1 CPU, 20 logical and 14 physical cores +.NET SDK 9.0.301 + [Host] : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2 [AttachedDebugger] + ShortRun : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2 - | Method | Mean | Error | StdDev | Allocated - | :-------------------- | ----------: | ----------: | ---------: | ---------: - | JsonDiff_JsonNode | 778.8 ns | 703.7 ns | 38.57 ns | 1.2 KB - | JsonDiff_JsonElement | 1,370.3 ns | 5,661.7 ns | 310.33 ns | 1.66 KB - | | | | | - | JsonDiff_JsonNode | 1,013.3 ns | 781.3 ns | 42.82 ns | 1.3 KB - | JsonDiff_JsonElement | 1,304.2 ns | 347.2 ns | 19.03 ns | 1.93 KB + | Method | Mean | Error | StdDev | Allocated + | :-------------------- | --------: | ---------: | --------: | ---------: + | JsonDiff_JsonNode | 572.1 ns | 164.76 ns | 9.03 ns | 1.2 KB + | JsonDiff_JsonElement | 704.5 ns | 219.53 ns | 12.03 ns | 1.66 KB + | | | | | + | JsonDiff_JsonNode | 704.4 ns | 267.47 ns | 14.66 ns | 1.3 KB + | JsonDiff_JsonElement | 905.6 ns | 73.54 ns | 4.03 ns | 1.93 KB ``` diff --git a/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonPatchBenchmark-report-jsonpath.md b/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonPatchBenchmark-report-jsonpath.md index 256df84b..585b90ed 100644 --- a/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonPatchBenchmark-report-jsonpath.md +++ b/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonPatchBenchmark-report-jsonpath.md @@ -1,16 +1,16 @@ ``` -BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.4061) -Intel Core i9-9980HK CPU 2.40GHz, 1 CPU, 16 logical and 8 physical cores -.NET SDK 9.0.203 - [Host] : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2 - ShortRun : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2 +BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.4351) +12th Gen Intel Core i9-12900HK, 1 CPU, 20 logical and 14 physical cores +.NET SDK 9.0.301 + [Host] : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2 [AttachedDebugger] + ShortRun : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2 - | Method | Mean | Error | StdDev | Allocated - | :----------------------- | --------: | ---------: | --------: | ---------: - | Hyperbee_JsonElement | 240.7 ns | 95.55 ns | 5.24 ns | 616 B - | Hyperbee_JsonNode | 265.3 ns | 430.49 ns | 23.60 ns | 616 B - | JsonEverything_JsonNode | 444.3 ns | 246.87 ns | 13.53 ns | 968 B - | AspNetCore_JsonNode | 743.4 ns | 801.29 ns | 43.92 ns | 1024 B + | Method | Mean | Error | StdDev | Allocated + | :----------------------- | --------: | ---------: | -------: | ---------: + | Hyperbee_JsonNode | 172.9 ns | 44.86 ns | 2.46 ns | 520 B + | Hyperbee_JsonElement | 178.8 ns | 76.47 ns | 4.19 ns | 520 B + | JsonEverything_JsonNode | 289.7 ns | 108.59 ns | 5.95 ns | 968 B + | AspNetCore_JsonNode | 516.7 ns | 44.74 ns | 2.45 ns | 1024 B ``` diff --git a/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonPathParseAndSelectEvaluator-report-jsonpath.md b/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonPathParseAndSelectEvaluator-report-jsonpath.md index a10aab8a..12a72cb0 100644 --- a/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonPathParseAndSelectEvaluator-report-jsonpath.md +++ b/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonPathParseAndSelectEvaluator-report-jsonpath.md @@ -1,79 +1,219 @@ ``` -BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.4202) -Intel Core i9-9980HK CPU 2.40GHz, 1 CPU, 16 logical and 8 physical cores -.NET SDK 9.0.203 - [Host] : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2 - ShortRun : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2 +BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.4351) +12th Gen Intel Core i9-12900HK, 1 CPU, 20 logical and 14 physical cores +.NET SDK 9.0.301 + [Host] : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2 [AttachedDebugger] + ShortRun : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2 - | Method | Mean | Error | StdDev | Allocated - | :--------------------- | ---------: | ---------: | ---------: | ---------: + | Method | Mean | Error | StdDev | Allocated + | :----------------------- | ---------: | ---------: | ---------: | ---------: | `$..[?(@.price < 10)]` - | JsonCraft.JsonElement | 5.309 μs | 1.6696 μs | 0.0915 μs | 3.59 KB - | Hyperbee.JsonElement | 10.112 μs | 2.1872 μs | 0.1199 μs | 20.73 KB - | | | | | + | JsonCraft.JsonElement | 3.841 μs | 0.3111 μs | 0.0171 μs | 3.59 KB + | JsonCons.JsonElement | 7.459 μs | 0.6412 μs | 0.0351 μs | 12.77 KB + | Hyperbee.JsonElement | 8.284 μs | 2.5497 μs | 0.1398 μs | 20.73 KB + | Hyperbee.JsonNode | 11.086 μs | 8.0907 μs | 0.4435 μs | 23.79 KB + | Newtonsoft.JObject | 12.153 μs | 2.4881 μs | 0.1364 μs | 25.86 KB + | JsonEverything.JsonNode | 23.541 μs | 1.3946 μs | 0.0764 μs | 48.15 KB + | | | | | | `$..['bicycle','price']` - | JsonCraft.JsonElement | 4.551 μs | 2.5508 μs | 0.1398 μs | 4.01 KB - | Hyperbee.JsonElement | 4.581 μs | 2.8094 μs | 0.1540 μs | 5.37 KB - | | | | | + | JsonCraft.JsonElement | 3.136 μs | 0.2760 μs | 0.0151 μs | 4.01 KB + | Hyperbee.JsonElement | 3.578 μs | 0.4623 μs | 0.0253 μs | 5.37 KB + | JsonCons.JsonElement | 3.948 μs | 0.6099 μs | 0.0334 μs | 7.18 KB + | Hyperbee.JsonNode | 5.181 μs | 1.8016 μs | 0.0988 μs | 9.23 KB + | Newtonsoft.JObject | 7.823 μs | 0.8017 μs | 0.0439 μs | 14.55 KB + | JsonEverything.JsonNode | 16.753 μs | 1.5507 μs | 0.0850 μs | 28.5 KB + | | | | | | `$..*` - | JsonCraft.JsonElement | 3.541 μs | 1.5742 μs | 0.0863 μs | 2.88 KB - | Hyperbee.JsonElement | 4.132 μs | 0.8317 μs | 0.0456 μs | 5.5 KB - | | | | | + | JsonCraft.JsonElement | 2.497 μs | 0.0903 μs | 0.0049 μs | 2.88 KB + | Hyperbee.JsonElement | 3.299 μs | 0.8178 μs | 0.0448 μs | 6.51 KB + | JsonCons.JsonElement | 4.176 μs | 0.5887 μs | 0.0323 μs | 8.49 KB + | Hyperbee.JsonNode | 5.330 μs | 0.7684 μs | 0.0421 μs | 10.3 KB + | Newtonsoft.JObject | 7.784 μs | 0.5303 μs | 0.0291 μs | 14.19 KB + | JsonEverything.JsonNode | 21.226 μs | 1.0905 μs | 0.0598 μs | 33.97 KB + | | | | | | `$..author` - | Hyperbee.JsonElement | 3.958 μs | 2.4245 μs | 0.1329 μs | 5.16 KB - | JsonCraft.JsonElement | 4.014 μs | 2.7295 μs | 0.1496 μs | 2.88 KB - | | | | | + | JsonCraft.JsonElement | 2.904 μs | 4.2915 μs | 0.2352 μs | 2.88 KB + | Hyperbee.JsonElement | 3.068 μs | 0.3672 μs | 0.0201 μs | 5.16 KB + | JsonCons.JsonElement | 3.381 μs | 0.6294 μs | 0.0345 μs | 5.55 KB + | Hyperbee.JsonNode | 5.030 μs | 0.6100 μs | 0.0334 μs | 9.02 KB + | Newtonsoft.JObject | 7.469 μs | 1.7563 μs | 0.0963 μs | 14.2 KB + | JsonEverything.JsonNode | 15.752 μs | 3.8966 μs | 0.2136 μs | 26.1 KB + | | | | | + | `$..book[?@.isbn]` + | Hyperbee.JsonElement | 3.913 μs | 1.5846 μs | 0.0869 μs | 6.8 KB + | JsonCons.JsonElement | 4.359 μs | 2.1655 μs | 0.1187 μs | 7.21 KB + | Hyperbee.JsonNode | 5.335 μs | 0.7057 μs | 0.0387 μs | 10.62 KB + | JsonEverything.JsonNode | 17.200 μs | 1.9836 μs | 0.1087 μs | 29.98 KB + | JsonCraft.JsonElement | NA | NA | NA | NA + | Newtonsoft.JObject | NA | NA | NA | NA + | | | | | + | `$..book[?@.price == 8.99 && @.category == 'fiction']` + | Hyperbee.JsonElement | 5.135 μs | 1.0302 μs | 0.0565 μs | 9.47 KB + | JsonCons.JsonElement | 6.105 μs | 0.6309 μs | 0.0346 μs | 8.52 KB + | Hyperbee.JsonNode | 7.002 μs | 3.0008 μs | 0.1645 μs | 13.41 KB + | JsonEverything.JsonNode | 21.845 μs | 3.9271 μs | 0.2153 μs | 39.52 KB + | JsonCraft.JsonElement | NA | NA | NA | NA + | Newtonsoft.JObject | NA | NA | NA | NA + | | | | | | `$..book[0,1]` - | Hyperbee.JsonElement | 3.964 μs | 1.3351 μs | 0.0732 μs | 5.16 KB - | JsonCraft.JsonElement | 4.027 μs | 1.6110 μs | 0.0883 μs | 3.09 KB - | | | | | + | JsonCraft.JsonElement | 2.936 μs | 0.6578 μs | 0.0361 μs | 3.09 KB + | Hyperbee.JsonElement | 3.157 μs | 0.8302 μs | 0.0455 μs | 5.16 KB + | JsonCons.JsonElement | 3.691 μs | 0.1430 μs | 0.0078 μs | 6.15 KB + | Hyperbee.JsonNode | 4.972 μs | 0.6586 μs | 0.0361 μs | 9.02 KB + | Newtonsoft.JObject | 7.441 μs | 0.5961 μs | 0.0327 μs | 14.45 KB + | JsonEverything.JsonNode | 15.523 μs | 5.4908 μs | 0.3010 μs | 26.41 KB + | | | | | + | `$..book[0]` + | JsonCraft.JsonElement | 2.825 μs | 0.0934 μs | 0.0051 μs | 3 KB + | Hyperbee.JsonElement | 3.114 μs | 0.4106 μs | 0.0225 μs | 5.16 KB + | JsonCons.JsonElement | 3.451 μs | 0.2921 μs | 0.0160 μs | 5.63 KB + | Hyperbee.JsonNode | 4.691 μs | 1.4048 μs | 0.0770 μs | 9.02 KB + | Newtonsoft.JObject | 7.720 μs | 0.8748 μs | 0.0480 μs | 14.33 KB + | JsonEverything.JsonNode | 15.314 μs | 3.3459 μs | 0.1834 μs | 26.02 KB + | | | | | | `$.store..price` - | Hyperbee.JsonElement | 3.846 μs | 0.8747 μs | 0.0479 μs | 4.8 KB - | JsonCraft.JsonElement | 4.126 μs | 0.6668 μs | 0.0365 μs | 3.13 KB - | | | | | + | JsonCraft.JsonElement | 2.912 μs | 1.3083 μs | 0.0717 μs | 3.13 KB + | Hyperbee.JsonElement | 2.993 μs | 0.4534 μs | 0.0249 μs | 4.8 KB + | JsonCons.JsonElement | 3.446 μs | 0.9305 μs | 0.0510 μs | 5.62 KB + | Hyperbee.JsonNode | 4.721 μs | 0.9516 μs | 0.0522 μs | 8.7 KB + | Newtonsoft.JObject | 7.723 μs | 1.1978 μs | 0.0657 μs | 14.34 KB + | JsonEverything.JsonNode | 15.966 μs | 1.1138 μs | 0.0610 μs | 26.63 KB + | | | | | | `$.store.*` - | JsonCraft.JsonElement | 2.610 μs | 0.7912 μs | 0.0434 μs | 2.49 KB - | Hyperbee.JsonElement | 2.983 μs | 2.4118 μs | 0.1322 μs | 2.88 KB - | | | | | + | JsonCraft.JsonElement | 1.882 μs | 0.5586 μs | 0.0306 μs | 2.49 KB + | JsonCons.JsonElement | 2.151 μs | 0.1067 μs | 0.0058 μs | 3.31 KB + | Hyperbee.JsonElement | 2.167 μs | 0.6457 μs | 0.0354 μs | 2.88 KB + | Hyperbee.JsonNode | 2.435 μs | 0.9690 μs | 0.0531 μs | 2.95 KB + | JsonEverything.JsonNode | 3.047 μs | 0.4674 μs | 0.0256 μs | 4.8 KB + | Newtonsoft.JObject | 6.576 μs | 0.8290 μs | 0.0454 μs | 14.43 KB + | | | | | | `$.store.bicycle.color` - | Hyperbee.JsonElement | 2.568 μs | 1.8970 μs | 0.1040 μs | 2.3 KB - | JsonCraft.JsonElement | 2.635 μs | 0.5189 μs | 0.0284 μs | 2.49 KB - | | | | | + | Hyperbee.JsonElement | 1.920 μs | 0.6179 μs | 0.0339 μs | 2.3 KB + | JsonCraft.JsonElement | 2.032 μs | 0.7770 μs | 0.0426 μs | 2.49 KB + | JsonCons.JsonElement | 2.216 μs | 0.1473 μs | 0.0081 μs | 3.27 KB + | Hyperbee.JsonNode | 2.383 μs | 1.2149 μs | 0.0666 μs | 2.88 KB + | JsonEverything.JsonNode | 3.556 μs | 0.5152 μs | 0.0282 μs | 5.74 KB + | Newtonsoft.JObject | 6.700 μs | 1.6404 μs | 0.0899 μs | 14.49 KB + | | | | | | `$.store.book[-1:]` - | JsonCraft.JsonElement | 2.675 μs | 0.8429 μs | 0.0462 μs | 2.58 KB - | Hyperbee.JsonElement | 2.734 μs | 0.5098 μs | 0.0279 μs | 2.47 KB - | | | | | + | JsonCraft.JsonElement | 2.004 μs | 0.3158 μs | 0.0173 μs | 2.58 KB + | Hyperbee.JsonElement | 2.087 μs | 0.1721 μs | 0.0094 μs | 2.47 KB + | JsonCons.JsonElement | 2.399 μs | 0.3533 μs | 0.0194 μs | 3.56 KB + | Hyperbee.JsonNode | 2.467 μs | 0.9814 μs | 0.0538 μs | 2.97 KB + | JsonEverything.JsonNode | 3.679 μs | 0.4354 μs | 0.0239 μs | 5.72 KB + | Newtonsoft.JObject | 6.472 μs | 1.5636 μs | 0.0857 μs | 14.52 KB + | | | | | | `$.store.book[:2]` - | Hyperbee.JsonElement | 2.782 μs | 1.4813 μs | 0.0812 μs | 2.47 KB - | JsonCraft.JsonElement | 2.794 μs | 2.5981 μs | 0.1424 μs | 2.58 KB - | | | | | + | JsonCraft.JsonElement | 2.010 μs | 0.1463 μs | 0.0080 μs | 2.58 KB + | Hyperbee.JsonElement | 2.128 μs | 0.4884 μs | 0.0268 μs | 2.47 KB + | JsonCons.JsonElement | 2.382 μs | 0.0585 μs | 0.0032 μs | 3.59 KB + | Hyperbee.JsonNode | 2.450 μs | 0.1728 μs | 0.0095 μs | 2.97 KB + | JsonEverything.JsonNode | 4.019 μs | 2.1096 μs | 0.1156 μs | 6.02 KB + | Newtonsoft.JObject | 6.899 μs | 0.6775 μs | 0.0371 μs | 14.51 KB + | | | | | | `$.store.book[?(@.author && @.title)]` - | JsonCraft.JsonElement | 3.899 μs | 8.2799 μs | 0.4539 μs | 3.3 KB - | Hyperbee.JsonElement | 4.404 μs | 2.5578 μs | 0.1402 μs | 5.52 KB - | | | | | + | JsonCraft.JsonElement | 2.567 μs | 0.2239 μs | 0.0123 μs | 3.3 KB + | Hyperbee.JsonElement | 3.362 μs | 0.2369 μs | 0.0130 μs | 5.52 KB + | JsonCons.JsonElement | 3.805 μs | 0.8769 μs | 0.0481 μs | 5.63 KB + | Hyperbee.JsonNode | 5.128 μs | 1.1406 μs | 0.0625 μs | 9.23 KB + | Newtonsoft.JObject | 7.514 μs | 1.6892 μs | 0.0926 μs | 16.18 KB + | JsonEverything.JsonNode | 9.261 μs | 3.4741 μs | 0.1904 μs | 18.32 KB + | | | | | | `$.store.book[?(@.category == 'fiction')]` - | JsonCraft.JsonElement | 3.522 μs | 2.0617 μs | 0.1130 μs | 3.38 KB - | Hyperbee.JsonElement | 4.316 μs | 3.5501 μs | 0.1946 μs | 5.09 KB - | | | | | + | JsonCraft.JsonElement | 2.734 μs | 0.3242 μs | 0.0178 μs | 3.38 KB + | Hyperbee.JsonElement | 3.315 μs | 0.3024 μs | 0.0166 μs | 5.09 KB + | JsonCons.JsonElement | 3.426 μs | 1.0773 μs | 0.0590 μs | 5.05 KB + | Hyperbee.JsonNode | 5.003 μs | 0.8363 μs | 0.0458 μs | 8.89 KB + | Newtonsoft.JObject | 7.213 μs | 1.1931 μs | 0.0654 μs | 15.74 KB + | JsonEverything.JsonNode | 8.898 μs | 1.8821 μs | 0.1032 μs | 16.49 KB + | | | | | | `$.store.book[?(@.price < 10)].title` - | Hyperbee.JsonElement | 4.266 μs | 3.2809 μs | 0.1798 μs | 5.1 KB - | JsonCraft.JsonElement | 6.221 μs | 7.3531 μs | 0.4031 μs | 3.37 KB - | | | | | + | JsonCraft.JsonElement | 3.108 μs | 1.8864 μs | 0.1034 μs | 3.37 KB + | Hyperbee.JsonElement | 3.353 μs | 0.4814 μs | 0.0264 μs | 5.1 KB + | JsonCons.JsonElement | 4.008 μs | 0.5005 μs | 0.0274 μs | 5.27 KB + | Hyperbee.JsonNode | 5.285 μs | 0.7662 μs | 0.0420 μs | 8.78 KB + | Newtonsoft.JObject | 7.687 μs | 1.0056 μs | 0.0551 μs | 15.89 KB + | JsonEverything.JsonNode | 9.513 μs | 6.3528 μs | 0.3482 μs | 17.38 KB + | | | | | | `$.store.book[?(@.price > 10 && @.price < 20)]` - | JsonCraft.JsonElement | 4.356 μs | 3.6377 μs | 0.1994 μs | 3.82 KB - | Hyperbee.JsonElement | 4.918 μs | 0.8512 μs | 0.0467 μs | 6.55 KB - | | | | | + | JsonCraft.JsonElement | 3.475 μs | 0.0797 μs | 0.0044 μs | 3.82 KB + | Hyperbee.JsonElement | 4.010 μs | 0.6169 μs | 0.0338 μs | 6.55 KB + | JsonCons.JsonElement | 5.102 μs | 1.1449 μs | 0.0628 μs | 6.28 KB + | Hyperbee.JsonNode | 6.086 μs | 1.3252 μs | 0.0726 μs | 10.27 KB + | Newtonsoft.JObject | 8.050 μs | 0.6497 μs | 0.0356 μs | 16.69 KB + | JsonEverything.JsonNode | 11.612 μs | 0.2688 μs | 0.0147 μs | 22.27 KB + | | | | | + | `$.store.book[?@.price == 8.99]` + | Hyperbee.JsonElement | 3.162 μs | 0.9502 μs | 0.0521 μs | 4.9 KB + | JsonCons.JsonElement | 3.822 μs | 0.0869 μs | 0.0048 μs | 5.02 KB + | Hyperbee.JsonNode | 4.889 μs | 1.5106 μs | 0.0828 μs | 8.58 KB + | JsonEverything.JsonNode | 8.310 μs | 2.0101 μs | 0.1102 μs | 15.47 KB + | JsonCraft.JsonElement | NA | NA | NA | NA + | Newtonsoft.JObject | NA | NA | NA | NA + | | | | | + | `$.store.book['category','author']` + | JsonCraft.JsonElement | 2.084 μs | 0.4663 μs | 0.0256 μs | 2.95 KB + | JsonCons.JsonElement | 2.480 μs | 0.0770 μs | 0.0042 μs | 3.61 KB + | Hyperbee.JsonElement | 2.569 μs | 1.1453 μs | 0.0628 μs | 2.67 KB + | JsonEverything.JsonNode | 3.309 μs | 0.2755 μs | 0.0151 μs | 5.41 KB + | Hyperbee.JsonNode | 4.142 μs | 1.4670 μs | 0.0804 μs | 6.42 KB + | Newtonsoft.JObject | 6.737 μs | 0.5860 μs | 0.0321 μs | 14.85 KB + | | | | | | `$.store.book[*].author` - | JsonCraft.JsonElement | 2.994 μs | 1.5486 μs | 0.0849 μs | 2.63 KB - | Hyperbee.JsonElement | 3.377 μs | 0.6483 μs | 0.0355 μs | 3.12 KB - | | | | | + | JsonCraft.JsonElement | 2.256 μs | 1.4487 μs | 0.0794 μs | 2.63 KB + | Hyperbee.JsonElement | 2.469 μs | 0.7492 μs | 0.0411 μs | 3.12 KB + | JsonCons.JsonElement | 2.496 μs | 0.3427 μs | 0.0188 μs | 3.59 KB + | Hyperbee.JsonNode | 4.088 μs | 0.0348 μs | 0.0019 μs | 6.83 KB + | Newtonsoft.JObject | 7.169 μs | 1.8980 μs | 0.1040 μs | 14.64 KB + | JsonEverything.JsonNode | 7.511 μs | 0.2592 μs | 0.0142 μs | 12.45 KB + | | | | | | `$.store.book[*]` - | JsonCraft.JsonElement | 2.674 μs | 0.6596 μs | 0.0362 μs | 2.48 KB - | Hyperbee.JsonElement | 2.891 μs | 1.1848 μs | 0.0649 μs | 2.71 KB - | | | | | + | JsonCraft.JsonElement | 2.048 μs | 1.8693 μs | 0.1025 μs | 2.48 KB + | Hyperbee.JsonElement | 2.179 μs | 0.6664 μs | 0.0365 μs | 2.71 KB + | JsonCons.JsonElement | 2.246 μs | 0.1811 μs | 0.0099 μs | 3.4 KB + | Hyperbee.JsonNode | 2.672 μs | 0.2099 μs | 0.0115 μs | 3.17 KB + | JsonEverything.JsonNode | 4.315 μs | 0.0253 μs | 0.0014 μs | 6.61 KB + | Newtonsoft.JObject | 6.632 μs | 1.6876 μs | 0.0925 μs | 14.49 KB + | | | | | + | `$.store.book[0,1]` + | Hyperbee.JsonElement | 2.049 μs | 0.0126 μs | 0.0007 μs | 2.47 KB + | JsonCraft.JsonElement | 2.056 μs | 0.2169 μs | 0.0119 μs | 2.64 KB + | Hyperbee.JsonNode | 2.435 μs | 0.5524 μs | 0.0303 μs | 2.97 KB + | JsonCons.JsonElement | 2.554 μs | 1.3052 μs | 0.0715 μs | 3.77 KB + | JsonEverything.JsonNode | 3.987 μs | 0.4523 μs | 0.0248 μs | 6.07 KB + | Newtonsoft.JObject | 6.648 μs | 0.7321 μs | 0.0401 μs | 14.59 KB + | | | | | | `$.store.book[0].title` - | Hyperbee.JsonElement | 2.680 μs | 2.2509 μs | 0.1234 μs | 2.27 KB - | JsonCraft.JsonElement | 2.716 μs | 0.7777 μs | 0.0426 μs | 2.55 KB + | Hyperbee.JsonElement | 1.897 μs | 0.1620 μs | 0.0089 μs | 2.27 KB + | JsonCraft.JsonElement | 2.055 μs | 0.2876 μs | 0.0158 μs | 2.55 KB + | JsonCons.JsonElement | 2.318 μs | 0.3233 μs | 0.0177 μs | 3.35 KB + | Hyperbee.JsonNode | 2.575 μs | 0.1096 μs | 0.0060 μs | 3.63 KB + | JsonEverything.JsonNode | 4.534 μs | 1.5492 μs | 0.0849 μs | 7.38 KB + | Newtonsoft.JObject | 6.734 μs | 2.0745 μs | 0.1137 μs | 14.62 KB + | | | | | + | `$.store.book[0]` + | Hyperbee.JsonElement | 1.931 μs | 0.1500 μs | 0.0082 μs | 2.27 KB + | JsonCraft.JsonElement | 1.996 μs | 0.1804 μs | 0.0099 μs | 2.48 KB + | Hyperbee.JsonNode | 2.204 μs | 0.1747 μs | 0.0096 μs | 2.86 KB + | JsonCons.JsonElement | 2.221 μs | 0.1388 μs | 0.0076 μs | 3.26 KB + | JsonEverything.JsonNode | 3.592 μs | 0.3654 μs | 0.0200 μs | 5.68 KB + | Newtonsoft.JObject | 6.892 μs | 2.7132 μs | 0.1487 μs | 14.48 KB + | | | | | + | `$` + | JsonCraft.JsonElement | 1.732 μs | 0.0529 μs | 0.0029 μs | 2.26 KB + | Hyperbee.JsonElement | 1.751 μs | 0.0628 μs | 0.0034 μs | 2.27 KB + | JsonEverything.JsonNode | 1.767 μs | 0.0675 μs | 0.0037 μs | 1.88 KB + | Hyperbee.JsonNode | 1.790 μs | 0.1222 μs | 0.0067 μs | 1.78 KB + | JsonCons.JsonElement | 1.929 μs | 0.1539 μs | 0.0084 μs | 2.98 KB + | Newtonsoft.JObject | 6.587 μs | 0.7309 μs | 0.0401 μs | 14.01 KB + +Benchmarks with issues: + JsonPathParseAndSelectEvaluator.JsonCraft.JsonElement: ShortRun(IterationCount=3, LaunchCount=1, WarmupCount=3) [Filter=$..book[?@.isbn]] + JsonPathParseAndSelectEvaluator.Newtonsoft.JObject: ShortRun(IterationCount=3, LaunchCount=1, WarmupCount=3) [Filter=$..book[?@.isbn]] + JsonPathParseAndSelectEvaluator.JsonCraft.JsonElement: ShortRun(IterationCount=3, LaunchCount=1, WarmupCount=3) [Filter=$..book[?@.price == 8.99 && @.category == 'fiction']] + JsonPathParseAndSelectEvaluator.Newtonsoft.JObject: ShortRun(IterationCount=3, LaunchCount=1, WarmupCount=3) [Filter=$..book[?@.price == 8.99 && @.category == 'fiction']] + JsonPathParseAndSelectEvaluator.JsonCraft.JsonElement: ShortRun(IterationCount=3, LaunchCount=1, WarmupCount=3) [Filter=$.store.book[?@.price == 8.99]] + JsonPathParseAndSelectEvaluator.Newtonsoft.JObject: ShortRun(IterationCount=3, LaunchCount=1, WarmupCount=3) [Filter=$.store.book[?@.price == 8.99]] ``` diff --git a/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonPathSelectEvaluator-report-jsonpath.md b/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonPathSelectEvaluator-report-jsonpath.md index 84f84a07..5c451802 100644 --- a/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonPathSelectEvaluator-report-jsonpath.md +++ b/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonPathSelectEvaluator-report-jsonpath.md @@ -1,81 +1,81 @@ ``` -BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.4061) -Intel Core i9-9980HK CPU 2.40GHz, 1 CPU, 16 logical and 8 physical cores -.NET SDK 9.0.203 - [Host] : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2 - ShortRun : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2 +BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.4351) +12th Gen Intel Core i9-12900HK, 1 CPU, 20 logical and 14 physical cores +.NET SDK 9.0.301 + [Host] : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2 [AttachedDebugger] + ShortRun : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2 - | Method | Mean | Error | StdDev | Allocated - | :----------------------- | -----------: | ------------: | ----------: | ---------: + | Method | Mean | Error | StdDev | Allocated + | :-------------------- | -----------: | ------------: | ---------: | ---------: | `$..*` - | 'Json.Net (Newtonsoft)' | 1,663.17 ns | 306.230 ns | 16.785 ns | 568 B - | Hyperbee.JsonNode | 4,235.65 ns | 1,165.972 ns | 63.911 ns | 5056 B - | Hyperbee.JsonElement | 4,858.12 ns | 898.871 ns | 49.270 ns | 6408 B - | | | | | + | Newtonsoft.JObject | 1,129.64 ns | 200.111 ns | 10.969 ns | 320 B + | Hyperbee.JsonElement | 1,435.16 ns | 270.030 ns | 14.801 ns | 4496 B + | Hyperbee.JsonNode | 1,635.74 ns | 212.133 ns | 11.628 ns | 4120 B + | | | | | | `$..book[?@.isbn]` - | Hyperbee.JsonNode | 2,539.14 ns | 1,668.271 ns | 91.444 ns | 3832 B - | Hyperbee.JsonElement | 2,852.45 ns | 530.695 ns | 29.089 ns | 3904 B - | 'Json.Net (Newtonsoft)' | NA | NA | NA | NA - | | | | | + | Hyperbee.JsonNode | 1,751.53 ns | 67.009 ns | 3.673 ns | 4440 B + | Hyperbee.JsonElement | 1,939.70 ns | 1,281.085 ns | 70.221 ns | 4792 B + | Newtonsoft.JObject | NA | NA | NA | NA + | | | | | | `$..book[?@.price == (...)tegory == 'fiction'] [52]` - | Hyperbee.JsonNode | 4,596.58 ns | 1,453.515 ns | 79.672 ns | 6944 B - | Hyperbee.JsonElement | 4,614.84 ns | 3,365.769 ns | 184.489 ns | 6880 B - | 'Json.Net (Newtonsoft)' | NA | NA | NA | NA - | | | | | + | Hyperbee.JsonElement | 3,053.63 ns | 138.091 ns | 7.569 ns | 7528 B + | Hyperbee.JsonNode | 3,202.07 ns | 626.290 ns | 34.329 ns | 7304 B + | Newtonsoft.JObject | NA | NA | NA | NA + | | | | | | `$..book[0]` - | 'Json.Net (Newtonsoft)' | 1,356.94 ns | 353.834 ns | 19.395 ns | 496 B - | Hyperbee.JsonNode | 1,514.78 ns | 562.260 ns | 30.819 ns | 1952 B - | Hyperbee.JsonElement | 1,763.83 ns | 606.140 ns | 33.225 ns | 1976 B - | | | | | + | Newtonsoft.JObject | 952.67 ns | 80.154 ns | 4.394 ns | 464 B + | Hyperbee.JsonNode | 1,066.23 ns | 199.175 ns | 10.917 ns | 2808 B + | Hyperbee.JsonElement | 1,126.19 ns | 60.415 ns | 3.312 ns | 3120 B + | | | | | | `$.store..price` - | 'Json.Net (Newtonsoft)' | 1,386.04 ns | 116.630 ns | 6.393 ns | 536 B - | Hyperbee.JsonNode | 1,432.28 ns | 131.367 ns | 7.201 ns | 1736 B - | Hyperbee.JsonElement | 1,522.55 ns | 325.344 ns | 17.833 ns | 1776 B - | | | | | + | Newtonsoft.JObject | 1,002.20 ns | 80.903 ns | 4.435 ns | 472 B + | Hyperbee.JsonNode | 1,016.07 ns | 17.480 ns | 0.958 ns | 2480 B + | Hyperbee.JsonElement | 1,019.12 ns | 269.498 ns | 14.772 ns | 2744 B + | | | | | | `$.store.*` - | 'Json.Net (Newtonsoft)' | 336.70 ns | 70.197 ns | 3.848 ns | 608 B - | Hyperbee.JsonNode | 460.77 ns | 284.563 ns | 15.598 ns | 960 B - | Hyperbee.JsonElement | 505.11 ns | 111.571 ns | 6.116 ns | 1216 B - | | | | | + | Newtonsoft.JObject | 197.98 ns | 45.002 ns | 2.467 ns | 568 B + | Hyperbee.JsonNode | 284.19 ns | 87.546 ns | 4.799 ns | 632 B + | Hyperbee.JsonElement | 320.20 ns | 82.718 ns | 4.534 ns | 776 B + | | | | | | `$.store.book[-1:]` - | Hyperbee.JsonNode | 396.47 ns | 167.716 ns | 9.193 ns | 704 B - | 'Json.Net (Newtonsoft)' | 402.63 ns | 169.272 ns | 9.278 ns | 688 B - | Hyperbee.JsonElement | 411.42 ns | 132.880 ns | 7.284 ns | 896 B - | | | | | + | Hyperbee.JsonNode | 206.03 ns | 60.986 ns | 3.343 ns | 304 B + | Hyperbee.JsonElement | 229.31 ns | 20.912 ns | 1.146 ns | 360 B + | Newtonsoft.JObject | 237.86 ns | 48.031 ns | 2.633 ns | 656 B + | | | | | | `$.store.book[?@.price == 8.99]` - | Hyperbee.JsonElement | 1,867.53 ns | 706.136 ns | 38.706 ns | 3624 B - | Hyperbee.JsonNode | 1,963.96 ns | 159.196 ns | 8.726 ns | 3360 B - | 'Json.Net (Newtonsoft)' | NA | NA | NA | NA - | | | | | + | Hyperbee.JsonElement | 1,238.46 ns | 117.376 ns | 6.434 ns | 2848 B + | Hyperbee.JsonNode | 1,321.87 ns | 540.536 ns | 29.629 ns | 2720 B + | Newtonsoft.JObject | NA | NA | NA | NA + | | | | | | `$.store.book['category','author']` - | 'Json.Net (Newtonsoft)' | 441.16 ns | 67.371 ns | 3.693 ns | 1000 B - | Hyperbee.JsonNode | 965.14 ns | 201.450 ns | 11.042 ns | 1048 B - | Hyperbee.JsonElement | 1,365.77 ns | 264.766 ns | 14.513 ns | 1296 B - | | | | | + | Newtonsoft.JObject | 293.04 ns | 156.371 ns | 8.571 ns | 1000 B + | Hyperbee.JsonNode | 553.55 ns | 28.365 ns | 1.555 ns | 512 B + | Hyperbee.JsonElement | 680.40 ns | 61.553 ns | 3.374 ns | 568 B + | | | | | | `$.store.book[*].author` - | 'Json.Net (Newtonsoft)' | 621.68 ns | 68.455 ns | 3.752 ns | 840 B - | Hyperbee.JsonElement | 1,169.77 ns | 514.153 ns | 28.182 ns | 1752 B - | Hyperbee.JsonNode | 1,283.55 ns | 2,651.830 ns | 145.356 ns | 1496 B - | | | | | + | Newtonsoft.JObject | 379.88 ns | 30.004 ns | 1.645 ns | 784 B + | Hyperbee.JsonNode | 541.65 ns | 59.901 ns | 3.283 ns | 928 B + | Hyperbee.JsonElement | 652.23 ns | 124.684 ns | 6.834 ns | 1024 B + | | | | | | `$.store.book[0,1]` - | Hyperbee.JsonElement | 434.92 ns | 196.984 ns | 10.797 ns | 912 B - | Hyperbee.JsonNode | 446.30 ns | 723.205 ns | 39.641 ns | 712 B - | 'Json.Net (Newtonsoft)' | 488.25 ns | 637.440 ns | 34.940 ns | 776 B - | | | | | + | Hyperbee.JsonNode | 215.48 ns | 13.220 ns | 0.725 ns | 304 B + | Hyperbee.JsonElement | 245.08 ns | 17.875 ns | 0.980 ns | 360 B + | Newtonsoft.JObject | 279.02 ns | 168.868 ns | 9.256 ns | 736 B + | | | | | | `$.store.book[0]` - | Hyperbee.JsonNode | 197.91 ns | 28.470 ns | 1.561 ns | 320 B - | Hyperbee.JsonElement | 206.25 ns | 117.243 ns | 6.427 ns | 288 B - | 'Json.Net (Newtonsoft)' | 397.65 ns | 39.882 ns | 2.186 ns | 648 B - | | | | | + | Hyperbee.JsonElement | 101.05 ns | 4.460 ns | 0.244 ns | 160 B + | Hyperbee.JsonNode | 101.29 ns | 11.959 ns | 0.655 ns | 192 B + | Newtonsoft.JObject | 236.41 ns | 17.471 ns | 0.958 ns | 616 B + | | | | | | `$` - | Hyperbee.JsonElement | 51.24 ns | 6.684 ns | 0.366 ns | 160 B - | 'Json.Net (Newtonsoft)' | 59.21 ns | 30.779 ns | 1.687 ns | 136 B - | Hyperbee.JsonNode | 66.31 ns | 27.840 ns | 1.526 ns | 144 B + | Newtonsoft.JObject | 31.69 ns | 9.529 ns | 0.522 ns | 136 B + | Hyperbee.JsonNode | 36.08 ns | 9.450 ns | 0.518 ns | 144 B + | Hyperbee.JsonElement | 39.98 ns | 11.650 ns | 0.639 ns | 160 B Benchmarks with issues: - JsonPathSelectEvaluator.'Json.Net (Newtonsoft)': ShortRun(IterationCount=3, LaunchCount=1, WarmupCount=3) [Filter=$..book[?@.isbn]] - JsonPathSelectEvaluator.'Json.Net (Newtonsoft)': ShortRun(IterationCount=3, LaunchCount=1, WarmupCount=3) [Filter=$..book[?@.price == (...)tegory == 'fiction'] [52]] - JsonPathSelectEvaluator.'Json.Net (Newtonsoft)': ShortRun(IterationCount=3, LaunchCount=1, WarmupCount=3) [Filter=$.store.book[?@.price == 8.99]] + JsonPathSelectEvaluator.Newtonsoft.JObject: ShortRun(IterationCount=3, LaunchCount=1, WarmupCount=3) [Filter=$..book[?@.isbn]] + JsonPathSelectEvaluator.Newtonsoft.JObject: ShortRun(IterationCount=3, LaunchCount=1, WarmupCount=3) [Filter=$..book[?@.price == (...)tegory == 'fiction'] [52]] + JsonPathSelectEvaluator.Newtonsoft.JObject: ShortRun(IterationCount=3, LaunchCount=1, WarmupCount=3) [Filter=$.store.book[?@.price == 8.99]] ```