Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Added reflection emit member accessor#38233

Merged
steveharter merged 2 commits intodotnet:masterfrom
YohDeadfall:json-member-access
Jun 25, 2019
Merged

Added reflection emit member accessor#38233
steveharter merged 2 commits intodotnet:masterfrom
YohDeadfall:json-member-access

Conversation

@YohDeadfall
Copy link
Copy Markdown
Contributor

This is prerequisite for #36505 and opened as a separate PR because it can be viewed as an optimization of existing code. The feature will come after merging this one.

/cc @ahsonkhan @steveharter

namespace System.Text.Json.Serialization
{
internal static class MemberAccessor
internal abstract class MemberAccessor
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this indirection via an abstract class cause some perf regression?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it affects class info construction only. A constructed class info is cached in the associated JsonSerializerOptions and reused later. The class materializer is implemented the same way, so I'm going to merge them.

Speaking about performance, there should be an improvement because the inbox build uses reflection emit to generate stubs to unbox an object instance and to pass it to the actual getter or setter. This eliminates one delegate call per action.

Comment thread src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs Outdated
@YohDeadfall
Copy link
Copy Markdown
Contributor Author

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 4 pipeline(s).

Comment thread src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs Outdated
@steveharter steveharter requested a review from layomia June 6, 2019 15:05
}
else
{
generator.Emit(OpCodes.Castclass, classType);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Originally was using emit for getters\setters, then switched to direct delegates (just as fast).

That emit approach was different - I passed in typeof(object) to the DynamicMethod ctor instead of objectType and then OpCodes.Ldarg_0 (object) + OpCodes.Ldarg_1 (property value) + Callvirt + Ret.

I wonder if that was faster than using Castclass?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You know the answer, right? (: Castclass is just a safety check which I added because of discussion in #36506. Remove that operation?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's good although a micro benchmark would be useful.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If safety isn't so important Castclass should be removed.

BenchmarkDotNet=v0.11.5, OS=Windows 10.0.18895
Intel Xeon CPU E5-2687W 0 3.10GHz, 2 CPU, 8 logical and 8 physical cores
.NET Core SDK=3.0.100-preview5-011568
  [Host]     : .NET Core 3.0.0-preview5-27626-15 (CoreCLR 4.6.27622.75, CoreFX 4.700.19.22408), 64bit RyuJIT
  DefaultJob : .NET Core 3.0.0-preview5-27626-15 (CoreCLR 4.6.27622.75, CoreFX 4.700.19.22408), 64bit RyuJIT
Method Mean Error StdDev Op/s
WithCastCall 9.751 ns 0.3378 ns 0.9854 ns 102,552,670.5
WithoutCastCall 8.346 ns 0.3414 ns 1.0068 ns 119,813,101.1
public class ValueHolder
{
    public int Value { get; set; }
}

private static readonly Func<object, int> s_WithCastCall = CreatePropertyGetter<ValueHolder, int>(typeof(ValueHolder).GetProperty("Value"), true);
private static readonly Func<object, int> s_WithoutCastCall = CreatePropertyGetter<ValueHolder, int>(typeof(ValueHolder).GetProperty("Value"), false);
private object _holder = new ValueHolder { Value = 42 };

[Benchmark]
public int WithCastCall()
{
    return s_WithCastCall(_holder);
}

[Benchmark]
public int WithoutCastCall()
{
    return s_WithoutCastCall(_holder);
}


generator.Emit(OpCodes.Ret);

return dynamicMethod.CreateDelegate(typeof(Action<,>).MakeGenericType(objectType, propertyInfo.PropertyType));
Copy link
Copy Markdown
Contributor

@steveharter steveharter Jun 6, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For performance, it is possible to remove MakeGenericType if this method takes <TProperty> generic and binds to a delegate on JsonPropertyInfo<TProperty> (or elsewhere).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's an initialization only method, so the performance isn't so critical here. At the same time if TClass or TProperty is a value type, the JIT will produce a separate codegens of it which will be used only once and will occupy memory.

With the current code the generic stub method CreatePropertySetter<TClass, TProperty> is 16 bytes long and (if my memory serves me right, @AndyAyersMS knows it) therefore it will be inlined. The jitted code of non-generic CreatePropertySetter is shared in that case.

Correct me if I'm wrong.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I was writing about optimizations I completely forgot that the method is virtual and cannot be inlined. I think with static interface members (dotnet/csharplang#1711) it would be possible to inline generics.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Virtual methods can be inlined, if the jit can determine that they're called.

New generic instantiations always take up some space, whether or not their methods ever get called or inlined; the runtime has to keep track of them somehow. Action is relatively pay-for-play though as instantiating it does not trigger instantiation of other types.

}
else
{
generator.Emit(OpCodes.Castclass, classType);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is Castclass better\faster than Ldarg_0?. Also see similar comment below for setter.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moreover the perf could be improved by removing castclass but at the cost of type safety. I've posted the requested benchmark results above.

@YohDeadfall
Copy link
Copy Markdown
Contributor Author

I have a question about an empty line at the end of file. What style is preferred? In some files I see no empty lines, but in other they are.

Copy link
Copy Markdown
Contributor

@layomia layomia left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@steveharter the immutable CreateRange delegate changes look okay to me.

@steveharter steveharter self-requested a review June 7, 2019 19:35
@steveharter
Copy link
Copy Markdown
Contributor

I have a question about an empty line at the end of file. What style is preferred? In some files I see no empty lines, but in other they are.

Just make sure there's a linefeed\CR on the last line - no extra empty line is desired AFAIK.

Copy link
Copy Markdown
Contributor

@steveharter steveharter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall this looks really good and sets us up for fields. Thanks!

@YohDeadfall
Copy link
Copy Markdown
Contributor Author

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 4 pipeline(s).

@YohDeadfall
Copy link
Copy Markdown
Contributor Author

@steveharter Could you take the final review?

@steveharter
Copy link
Copy Markdown
Contributor

The tests are failing -- IL causing InvalidProgramException?

System.Text.Json.Serialization.Tests.ExtensionDataTests.ExtensionPropertyObjectValue_RoundTrip

Unhandled Exception of Type System.InvalidProgramException
Message :
System.InvalidProgramException : Common Language Runtime detected an invalid program.
Stack Trace :
   at get_Prop(Object )
   at System.Text.Json.JsonPropertyInfoNotNullable`3.Write(WriteStackFrame& current, Utf8JsonWriter writer)
   at System.Text.Json.JsonSerializer.HandleObject(JsonSerializerOptions options, Utf8JsonWriter writer, WriteStack& state)
   at System.Text.Json.JsonSerializer.WriteObject(JsonSerializerOptions options, Utf8JsonWriter writer, WriteStack& state)
   at System.Text.Json.JsonSerializer.Write(Utf8JsonWriter writer, Int32 flushThreshold, JsonSerializerOptions options, WriteStack& state)
   at System.Text.Json.JsonSerializer.WriteCore(PooledByteBufferWriter output, Object value, Type type, JsonSerializerOptions options)
   at System.Text.Json.JsonSerializer.WriteCoreString(Object value, Type type, JsonSerializerOptions options)
   at System.Text.Json.JsonSerializer.ToStringInternal(Object value, Type type, JsonSerializerOptions options)
   at System.Text.Json.JsonSerializer.ToString[TValue](TValue value, JsonSerializerOptions options)
   at System.Text.Json.Serialization.Tests.ExtensionDataTests.ExtensionPropertyObjectValue_RoundTrip()

Copy link
Copy Markdown
Contributor

@steveharter steveharter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should prevent boxing for structs in the getter\setter, like the previous code (pending benchmarks).

Copy link
Copy Markdown
Contributor Author

@YohDeadfall YohDeadfall left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't changed materialization in this pull request. Just added reflection emit for property accessors.


if (classType.IsValueType)
{
generator.Emit(OpCodes.Unbox_Any, classType);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid boxing for perf?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note the previous code (before changed due to supporting struct) used a direct delegate which did not box on get\set.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, but to do that I need to multiply existing property info classes by two to support reference and value types. This will lead to unmaintable code which I'm trying to avoid. By the way ReadStackFrame stores a property value on the heap always, and only a json property info uses member accessors to write or to read some value. The property info is type safe. So I think that there is nothing to worry. The thing you are requesting can be addressed in the future, and at the same time we probably could optimize the stack frame type to store a value directly in it.

Copy link
Copy Markdown
Contributor

@steveharter steveharter Jun 15, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ReadStackFrame only stores the "root" value that is going to be returned (JsonSerialier.Parse<int>("1")), not all property values on a POCO object for example.

I'm concerned that boxing on every stuct-based POCO property (int, DateTime, etc), based on previous benchmarks from initial prototyping.


if (classType.IsValueType)
{
generator.Emit(OpCodes.Unbox_Any, classType);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid boxing for perf?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't hurt performance because before my changes there were casts in the C# code and all values stored as objects in a read stack.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is not true. The only object stored on a read stack is a root object being returned, not POCO properties.

@YohDeadfall
Copy link
Copy Markdown
Contributor Author

YohDeadfall commented Jun 14, 2019

The tests are failing -- IL causing InvalidProgramException?

How can I view the previous pipeline run results? It seems that it was more successful, but may be wrong.

@steveharter
Copy link
Copy Markdown
Contributor

How can I view the previous pipeline run results? It seems that it was more successful, but may be wrong.

Usually they are available in the commit history, but I am not sure how long they remain there.

@YohDeadfall
Copy link
Copy Markdown
Contributor Author

The previous pipeline wasn't fully ran, so there was more green than red. I fixed the bug (used unbox_any instead of unbox) and rebased the PR.

@YohDeadfall
Copy link
Copy Markdown
Contributor Author

Now there is only one failed test and it's unrelated to this PR:

System.ComponentModel.Win32Exception : Access is denied.
   at System.Diagnostics.EventLogInternal.InternalWriteEvent(UInt32 eventID, UInt16 category, EventLogEntryType type, String[] strings, Byte[] rawData, String currentMachineName) in /_/src/System.Diagnostics.EventLog/src/System/Diagnostics/EventLogInternal.cs:line 1441
   at System.Diagnostics.EventLogInternal.WriteEvent(EventInstance instance, Byte[] data, Object[] values) in /_/src/System.Diagnostics.EventLog/src/System/Diagnostics/EventLogInternal.cs:line 1382
   at System.Diagnostics.EventLog.WriteEvent(EventInstance instance, Byte[] data, Object[] values) in /_/src/System.Diagnostics.EventLog/src/System/Diagnostics/EventLog.cs:line 1003
   at System.Diagnostics.EventLog.WriteEvent(EventInstance instance, Object[] values) in /_/src/System.Diagnostics.EventLog/src/System/Diagnostics/EventLog.cs:line 998
   at System.Diagnostics.EventLogTraceListener.TraceEvent(TraceEventCache eventCache, String source, TraceEventType severity, Int32 id, String message) in /_/src/System.Diagnostics.EventLog/src/System/Diagnostics/EventLogTraceListener.cs:line 116
   at System.Diagnostics.Tests.EventLogTraceListenerTests.TraceEventTest(TraceEventType eventType, EventLogEntryType expectedType, Int32 id, Int32 expectedId) in /_/src/System.Diagnostics.EventLog/tests/EventLogTraceListenerTests.cs:line 131

@steveharter
Copy link
Copy Markdown
Contributor

@YohDeadfall I think we need to pause for a couple days until we can review this and ensure boxing is not affecting perf.

Also, with the support for custom converters it is possible to write a converter for a struct, including a standard struct scenario where there is a constructor that takes the arguments and does not have setters for any properties.

What are the viable scenarios where a large struct with several properties is desired over a class (and no converter is desired)?

@YohDeadfall
Copy link
Copy Markdown
Contributor Author

@steveharter My pull request introduces no boxing. It just adds code generation which does the same thing as two hops with delegates. The previous one didn't that too. The only thing that I changed is moved casting of an object to TClass into generated setters/getters.

Improving performance via removing boxing is possible and I see to ways to do that:

  1. Change the frame type to be a class and store instances as linked list nodes. In that case you will have a base frame class and generic one. Because all pointers takes the same size of memory it makes sense to have a generic frame class for structures and another one for classes to store objects. Frames could be rented from types pools.

  2. Use a simple class to store boxed values on the heap, but that generic class should be rentable as in the previous approach. The same trick we use in Npgsql to work with value types in async methods, but without pooling now (see the type and a setter for example).

internal sealed class ByReference<T>
{
    public T Value;
}

Also, with the support for custom converters it is possible to write a converter for a struct, including a standard struct scenario where there is a constructor that takes the arguments and does not have setters for any properties.

Recommended this way as a temporary solution to the ASP.NET Core team, but as I know they chose another simpler way. Probably you should ask @rynowak or someone else working in that area. But as a customer I wouldn't like to write a custom converter too.

What are the viable scenarios where a large struct with several properties is desired over a class (and no converter is desired)?

Not large, but small. For example, typified identifiers, coordinates and similar cases. No one would like to store them on the heap.

@steveharter
Copy link
Copy Markdown
Contributor

@YohDeadfall changes here need to be vetted through benchmarks to ensure no regressions. This is a sensitive area where a lot of effort that went into it to make it performant.

If you can provide before\after benchmark results on a POCO object with a few primitives (int, DateTime, etc) then we can compare and go from there. Thanks

The benchmarks are at https://github.com/dotnet/performance/tree/master/src/benchmarks/micro/Serializers. I can run them for you.

@YohDeadfall
Copy link
Copy Markdown
Contributor Author

YohDeadfall commented Jun 19, 2019

I can run them for you.

I will be able to run benchmarks on Friday, so it would be nice to see results earlier.

@ahsonkhan
Copy link
Copy Markdown

@YohDeadfall, @steveharter - any updates on this PR? Looks like this change is waiting on perf numbers (particularly around boxing/allocations)

@YohDeadfall
Copy link
Copy Markdown
Contributor Author

I'm working on it, but getting errors mentioned in dotnet/performance#560 (comment).

@ahsonkhan
Copy link
Copy Markdown

I'm working on it, but getting errors mentioned in dotnet/performance#560 (comment).

What version of the sdk are you using (can you share dotnet --version)? Did the fix from that issue (using benchmarks_ci.py) help?

Can you try running the benchmarks following these instructions (pass in path to corerun):
https://github.com/dotnet/performance/blob/master/docs/benchmarking-workflow-corefx.md#corefx-prerequisites

@steveharter
Copy link
Copy Markdown
Contributor

This is works for me:

  • Install latest SDK from https://github.com/dotnet/core-sdk or by the .py file
  • Build corefx with -c release
  • From performance repro, run the before benchmarks something like:
    dotnet run -c Release -f netcoreapp3.0 --coreRun "d:\git\corefx\artifacts\bin\runtime\netcoreapp-Windows_NT-Release-x64\CoreRun.exe" --filter System.Text.Json.Serialization.Tests* --artifacts "C:\results\before"
  • Run the benchmarks again, but with the "after" code. Be sure to compile the code in release mode (dotnet msbuild src /p:ConfigurationGroup=Release) and ensure the release dll is properly copied (to D:\git\corefx\artifacts\bin\runtime\netcoreapp-Windows_NT-Release-x64)
  • Run comparison utility (optional)
    D:\git\performance\src\tools\ResultsComparer>dotnet run --base "C:\results\before" --diff "C:\results\after"

@YohDeadfall
Copy link
Copy Markdown
Contributor Author

What version of the sdk are you using (can you share dotnet --version)?

.NET Core 3 Preview 6

Did the fix from that issue (using benchmarks_ci.py) help?

Haven't tried yet. Found the issue in the last moment,

This is works for me:

Thank you! Will take a try.

@YohDeadfall
Copy link
Copy Markdown
Contributor Author

YohDeadfall commented Jun 25, 2019

BenchmarkDotNet=v0.11.3.1003-nightly, OS=Windows 10.0.18895
Intel Xeon CPU E5-2687W 0 3.10GHz, 2 CPU, 8 logical and 8 physical cores
.NET Core SDK=3.0.100-preview7-012598
  [Host]     : .NET Core 3.0.0-preview7-27824-03 (CoreCLR 4.700.19.32302, CoreFX 4.700.19.32001), 64bit RyuJIT
  Job-DAJFDT : .NET Core ? (CoreCLR 4.700.19.32003, CoreFX 4.700.19.32201), 64bit RyuJIT

Runtime=Core  Toolchain=CoreRun  IterationTime=250.0000 ms  
MaxIterationCount=20  MinIterationCount=15  WarmupCount=1  
dotnet run --base "D:\Repos\corefxperf\before" --diff "D:\Repos\corefxperf\after" --threshold 5%
Slower diff/base Base Median (ns) Diff Median (ns) Modality
System.Text.Json.Serialization.Tests.WriteJson.SerializeToStream 1.13 2123.43 2393.33 bimodal
System.Text.Json.Serialization.Tests.WriteJson.SerializeToStream 1.09 12348.02 13430.81 several?

No Faster results for the provided threshold = 5% and noise filter = 0.3ns.

ReadJson

MyEventsListerViewModel

Before

Method Mean Error StdDev Median Min Max Gen 0/1k Op Gen 1/1k Op Gen 2/1k Op Allocated Memory/Op
DeserializeFromString 1.883 ms 0.1103 ms 0.1270 ms 1.906 ms 1.609 ms 2.082 ms 14.9254 - - 153.84 KB
DeserializeFromUtf8Bytes 1.765 ms 0.1280 ms 0.1474 ms 1.789 ms 1.488 ms 2.000 ms 7.0423 - - 74.46 KB
DeserializeFromStream 2.157 ms 0.1368 ms 0.1575 ms 2.193 ms 1.700 ms 2.399 ms - - - 74.53 KB

After

Method Mean Error StdDev Median Min Max Gen 0/1k Op Gen 1/1k Op Gen 2/1k Op Allocated Memory/Op
DeserializeFromString 1.953 ms 0.1005 ms 0.1158 ms 1.969 ms 1.662 ms 2.171 ms 8.0000 - - 154.34 KB
DeserializeFromUtf8Bytes 1.751 ms 0.1312 ms 0.1511 ms 1.791 ms 1.417 ms 1.938 ms 6.8966 - - 74.46 KB
DeserializeFromStream 2.126 ms 0.1238 ms 0.1425 ms 2.142 ms 1.824 ms 2.390 ms - - - 74.53 KB

LoginViewModel

Before

Method Mean Error StdDev Median Min Max Gen 0/1k Op Gen 1/1k Op Gen 2/1k Op Allocated Memory/Op
DeserializeFromString 2.770 us 0.1055 us 0.1215 us 2.741 us 2.593 us 2.982 us 0.0219 - - 280 B
DeserializeFromUtf8Bytes 2.589 us 0.1252 us 0.1441 us 2.581 us 2.228 us 2.828 us 0.0100 - - 168 B
DeserializeFromStream 3.959 us 0.1305 us 0.1502 us 3.939 us 3.667 us 4.327 us 0.0130 - - 240 B

After

Method Mean Error StdDev Median Min Max Gen 0/1k Op Gen 1/1k Op Gen 2/1k Op Allocated Memory/Op
DeserializeFromString 2.608 us 0.1647 us 0.1830 us 2.673 us 2.118 us 2.858 us 0.0194 - - 280 B
DeserializeFromUtf8Bytes 2.578 us 0.0873 us 0.1005 us 2.598 us 2.406 us 2.719 us 0.0104 - - 168 B
DeserializeFromStream 3.980 us 0.3105 us 0.3576 us 4.008 us 3.293 us 4.562 us 0.0145 - - 240 B

Location

Before

Method Mean Error StdDev Median Min Max Gen 0/1k Op Gen 1/1k Op Gen 2/1k Op Allocated Memory/Op
DeserializeFromString 7.656 us 0.3909 us 0.4502 us 7.638 us 6.516 us 8.280 us 0.0627 - - 680 B
DeserializeFromUtf8Bytes 6.873 us 0.5833 us 0.6718 us 7.078 us 4.969 us 7.506 us 0.0279 - - 448 B
DeserializeFromStream 9.401 us 0.7543 us 0.8686 us 9.679 us 7.583 us 10.806 us 0.0374 - - 520 B

After

Method Mean Error StdDev Median Min Max Gen 0/1k Op Gen 1/1k Op Gen 2/1k Op Allocated Memory/Op
DeserializeFromString 7.765 us 0.4897 us 0.5640 us 7.804 us 6.521 us 8.576 us 0.0645 - - 680 B
DeserializeFromUtf8Bytes 7.218 us 0.3794 us 0.4370 us 7.204 us 6.107 us 7.782 us 0.0251 - - 448 B
DeserializeFromStream 9.810 us 0.5751 us 0.6623 us 9.874 us 8.186 us 10.929 us 0.0372 - - 520 B

IndexViewModel

Before

Method Mean Error StdDev Median Min Max Gen 0/1k Op Gen 1/1k Op Gen 2/1k Op Allocated Memory/Op
DeserializeFromString 200.6 us 21.36 us 24.60 us 206.9 us 141.1 us 230.9 us 3.2600 - - 34.82 KB
DeserializeFromUtf8Bytes 192.9 us 11.36 us 12.63 us 195.0 us 167.9 us 211.0 us 1.6393 - - 21.91 KB
DeserializeFromStream 194.5 us 13.81 us 15.91 us 196.0 us 156.9 us 220.5 us 1.7316 - - 21.98 KB

After

Method Mean Error StdDev Median Min Max Gen 0/1k Op Gen 1/1k Op Gen 2/1k Op Allocated Memory/Op
DeserializeFromString 190.3 us 10.54 us 12.14 us 195.6 us 167.5 us 204.5 us 3.0604 - - 34.8 KB
DeserializeFromUtf8Bytes 195.7 us 10.00 us 11.52 us 195.2 us 171.3 us 211.5 us 1.5601 - - 21.91 KB
DeserializeFromStream 199.2 us 12.52 us 14.41 us 201.3 us 161.4 us 222.7 us 1.6103 - - 21.98 KB

BinaryData

Before

Method Mean Error StdDev Median Min Max Gen 0/1k Op Gen 1/1k Op Gen 2/1k Op Allocated Memory/Op
DeserializeFromString 24.30 us 2.618 us 3.015 us 25.32 us 18.25 us 27.92 us 0.2067 - - 2.63 KB
DeserializeFromUtf8Bytes 23.70 us 1.575 us 1.813 us 23.93 us 20.10 us 26.66 us 0.0818 - - 1.05 KB
DeserializeFromStream 25.81 us 1.419 us 1.577 us 25.95 us 22.34 us 28.30 us 0.0903 - - 1.12 KB

After

Method Mean Error StdDev Median Min Max Gen 0/1k Op Gen 1/1k Op Gen 2/1k Op Allocated Memory/Op
DeserializeFromString 26.27 us 1.235 us 1.423 us 26.28 us 22.30 us 28.88 us 0.2016 - - 2.63 KB
DeserializeFromUtf8Bytes 24.30 us 1.439 us 1.658 us 24.57 us 21.14 us 27.10 us 0.1011 - - 1.05 KB
DeserializeFromStream 27.46 us 1.017 us 1.171 us 27.30 us 25.48 us 30.21 us 0.1038 - - 1.12 KB

WriteJson

MyEventsListerViewModel

Before

Method Mean Error StdDev Median Min Max Gen 0/1k Op Gen 1/1k Op Gen 2/1k Op Allocated Memory/Op
SerializeToString 3.006 ms 0.2388 ms 0.2654 ms 3.018 ms 2.468 ms 3.530 ms 41.6667 41.6667 41.6667 392.11 KB
SerializeToUtf8Bytes 2.924 ms 0.1559 ms 0.1600 ms 2.958 ms 2.495 ms 3.095 ms 20.8333 - - 312.69 KB
SerializeToStream 2.888 ms 0.2319 ms 0.2577 ms 2.957 ms 2.386 ms 3.323 ms 20.8333 - - 232.84 KB

After

Method Mean Error StdDev Median Min Max Gen 0/1k Op Gen 1/1k Op Gen 2/1k Op Allocated Memory/Op
SerializeToString 3.251 ms 0.1841 ms 0.1808 ms 3.328 ms 2.655 ms 3.364 ms 41.6667 41.6667 41.6667 392.58 KB
SerializeToUtf8Bytes 2.997 ms 0.1781 ms 0.1980 ms 2.982 ms 2.680 ms 3.398 ms 20.8333 - - 312.73 KB
SerializeToStream 2.908 ms 0.1667 ms 0.1712 ms 2.981 ms 2.418 ms 3.086 ms 20.8333 - - 232.84 KB

LoginViewModel

Before

Method Mean Error StdDev Median Min Max Gen 0/1k Op Gen 1/1k Op Gen 2/1k Op Allocated Memory/Op
SerializeToString 1.914 us 0.1726 us 0.1988 us 1.985 us 1.387 us 2.184 us 0.0244 - - 336 B
SerializeToUtf8Bytes 1.742 us 0.1123 us 0.1293 us 1.751 us 1.391 us 1.956 us 0.0214 - - 256 B
SerializeToStream 2.077 us 0.1623 us 0.1869 us 2.123 us 1.709 us 2.383 us 0.0086 - - 144 B

After

Method Mean Error StdDev Median Min Max Gen 0/1k Op Gen 1/1k Op Gen 2/1k Op Allocated Memory/Op
SerializeToString 1.949 us 0.1079 us 0.1243 us 1.961 us 1.703 us 2.148 us 0.0274 - - 336 B
SerializeToUtf8Bytes 1.860 us 0.0996 us 0.1147 us 1.857 us 1.626 us 2.053 us 0.0218 - - 256 B
SerializeToStream 2.405 us 0.0935 us 0.1077 us 2.393 us 2.229 us 2.613 us 0.0084 - - 144 B

Location

Before

Method Mean Error StdDev Median Min Max Gen 0/1k Op Gen 1/1k Op Gen 2/1k Op Allocated Memory/Op
SerializeToString 4.523 us 0.2303 us 0.2464 us 4.618 us 4.006 us 4.769 us 0.0547 - - 584 B
SerializeToUtf8Bytes 4.262 us 0.2753 us 0.3060 us 4.365 us 3.379 us 4.653 us 0.0342 - - 376 B
SerializeToStream 4.649 us 0.3536 us 0.4072 us 4.755 us 3.679 us 5.253 us - - - 144 B

After

Method Mean Error StdDev Median Min Max Gen 0/1k Op Gen 1/1k Op Gen 2/1k Op Allocated Memory/Op
SerializeToString 4.541 us 0.2534 us 0.2919 us 4.557 us 3.963 us 5.085 us 0.0542 - - 584 B
SerializeToUtf8Bytes 4.468 us 0.2303 us 0.2652 us 4.549 us 3.813 us 4.845 us 0.0336 - - 376 B
SerializeToStream 4.863 us 0.2772 us 0.3192 us 4.874 us 4.181 us 5.418 us - - - 144 B

IndexViewModel

Before

Method Mean Error StdDev Median Min Max Gen 0/1k Op Gen 1/1k Op Gen 2/1k Op Allocated Memory/Op
SerializeToString 135.3 us 12.414 us 14.297 us 137.3 us 111.3 us 156.6 us 2.1186 - - 26856 B
SerializeToUtf8Bytes 137.9 us 7.082 us 8.156 us 137.8 us 125.7 us 152.7 us 1.1468 - - 13680 B
SerializeToStream 138.8 us 11.805 us 13.594 us 141.8 us 114.7 us 156.9 us - - - 464 B

After

Method Mean Error StdDev Median Min Max Gen 0/1k Op Gen 1/1k Op Gen 2/1k Op Allocated Memory/Op
SerializeToString 146.1 us 7.805 us 8.988 us 147.2 us 127.08 us 160.7 us 2.1552 - - 26856 B
SerializeToUtf8Bytes 145.3 us 5.916 us 6.576 us 145.1 us 124.27 us 154.9 us 1.1792 - - 13680 B
SerializeToStream 135.4 us 13.636 us 15.703 us 140.9 us 98.52 us 150.6 us - - - 464 B

BinaryData

Before

Method Mean Error StdDev Median Min Max Gen 0/1k Op Gen 1/1k Op Gen 2/1k Op Allocated Memory/Op
SerializeToString 13.11 us 0.9075 us 1.0451 us 13.24 us 10.177 us 14.46 us 0.2729 - - 3368 B
SerializeToUtf8Bytes 12.39 us 0.8534 us 0.9828 us 12.61 us 10.537 us 13.88 us 0.1511 - - 1768 B
SerializeToStream 11.76 us 1.2537 us 1.4437 us 12.35 us 8.092 us 13.57 us - - - 144 B

After

Method Mean Error StdDev Median Min Max Gen 0/1k Op Gen 1/1k Op Gen 2/1k Op Allocated Memory/Op
SerializeToString 13.61 us 0.6727 us 0.7746 us 13.75 us 11.74 us 14.62 us 0.2732 - - 3368 B
SerializeToUtf8Bytes 12.86 us 0.6981 us 0.8039 us 12.88 us 10.43 us 14.16 us 0.1303 - - 1768 B
SerializeToStream 13.14 us 0.9132 us 1.0516 us 13.43 us 10.55 us 14.47 us - - - 144 B

@steveharter
Copy link
Copy Markdown
Contributor

An anomaly in Location and IndexViewModel - one considerable faster before and one considerably slower before. Otherwise the performance is within the margin of error.

@steveharter steveharter self-requested a review June 25, 2019 14:14
@steveharter
Copy link
Copy Markdown
Contributor

steveharter commented Jun 25, 2019

Test failure in Linux x64_Debug is unrelated (System.Net.HttpListener.Tests).

@steveharter steveharter merged commit 25374d3 into dotnet:master Jun 25, 2019
@YohDeadfall YohDeadfall deleted the json-member-access branch June 25, 2019 14:29
@YohDeadfall
Copy link
Copy Markdown
Contributor Author

An anomaly in Location and IndexViewModel - one considerable faster before and one considerably slower before.

Messed up results. Fixed now.

@ahsonkhan
Copy link
Copy Markdown

One thing to note here, the existing benchmarks don't have any user-defined structs. So, any allocation increase due to boxing won't show up in the benchmarks. We may want to consider adding a struct based test in the benchmarks and verify the allocations aren't increasing before/after.

@YohDeadfall YohDeadfall restored the json-member-access branch July 6, 2019 18:53
@YohDeadfall YohDeadfall deleted the json-member-access branch July 6, 2019 18:54
@karelz karelz added this to the 3.0 milestone Jul 16, 2019
return factory(propertyGetter);
}
else
if (createRangeMethod == null)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@YohDeadfall - we are annotating System.Text.Json with nullability and it looks like this branch is not covered by any tests . When can createRangeMethod be null? And if this is a valid branch, we should add a test. Using Debug.Fail doesn't work in release.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not mine code, but @layomia's (: The comment says that this shouldn't happen. Probably it would be better to throw exception directly from FindImmutableCreateRangeMethod.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not mine code

Yes, just realized that when looking at the commit/history more closely (this PR moved the code). Sorry about the ping :)

This is the PR I am interested in: #37710

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries, feel free to ping me for any question (:

By the way, is it the right time to move the field support to the new repo? Have you finished optimizations?

picenka21 pushed a commit to picenka21/runtime that referenced this pull request Feb 18, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants