-
Notifications
You must be signed in to change notification settings - Fork 4.9k
Improve data contract serializers performance in dictionary scenario #7608
Improve data contract serializers performance in dictionary scenario #7608
Conversation
@@ -68,14 +68,16 @@ public T Value | |||
|
|||
[DataContract(Namespace = "http://schemas.microsoft.com/2003/10/Serialization/Arrays")] | |||
#if USE_REFEMIT | |||
public struct KeyValue<K, V> | |||
public class KeyValue<K, V> | |||
#else |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why a change from struct? (not saying it's wrong, I just don't follow)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current issue with dictionary is that they are handled as collection of KeyValue internally and as of now, KeyValue don't have default constructor so they're not created by the faster code path. I added a default constructor but since struct is not allowed to have explicit parameterless constructor, I've changed this type to class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm concerned on this as it diverges from desktop .NET. We'd better avoid it unless absolutely necessary. How much performance gain would this change give us?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change and the caching of the map indicate type has a default constructor or not give 1.75x improvement for the dictionary scenario. Any specific concern on this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the perf gain without changing from struct to class?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is part of the significant improvement in this PR and benefits mainly the dictionary scenario. Other are the change to use encoding.GetBytes which improve just a few percentage, and caching instance of the GetUninitializedObject to avoid repeated reflection which already improves a lot, I recall close to 50%. With this change the performance is at the same bar or better than Desktop in dictionary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm concerned on this as it diverges from desktop .NET.
If the change proves to be beneficial to desktop, we should port the fix to desktop too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree with @zhenlan. I'm curious about the perf difference between desktop and .NET core after this change were made on both. There may be some hotspots not yet identified. LGTM.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I forgot to mention that this change is for NetCore only since this is the part where implementation on Desktop and NetCore diverges. In Desktop we use RuntimeServices.GetUninitlaizedObject to create new object instance whereas in NetCore that API is not available. We opt for getting that method through reflection and this perf issue is incurred.
8e670d4
to
773af28
Compare
Test Innerloop Windows_NT Debug Build and Test please |
@dotnet-bot test this please |
@dotnet-bot test Innerloop Windows_NT Debug Build and Test please |
@@ -30,6 +30,19 @@ public sealed class XmlFormatReaderGenerator | |||
internal sealed class XmlFormatReaderGenerator | |||
#endif | |||
{ | |||
private static MethodInfo _getUninitializedObjectMethodInfo; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s_getUninitializedObjectMethodInfo?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes that's the right name for private static.
773af28
to
042ed27
Compare
#else | ||
internal struct KeyValue<K, V> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changing KeyValue from a struct to a class will have a significant performance hit which you won't see in a micro benchmark. This could place a lot of stress on the heap and cause long GC pauses some time later. It will could also cause significantly slower memory accesses as an array of structs are next to each other in memory. An array of classes will require an extra dereference which will be slower, and you won't benefit from cache locality and on high core machines under sufficient load to context switch, this could be a significant issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. I've reverted the change of KeyValue back to struct to avoid potential GC impact. I tried a few alternatives to this and realized that Activator.CreateInstance on a struct type uses a special IL for struct and so just calling Activator.CreateInstance will work. I get the numbers for this change and they are as good as the previous change for dictionary scenario.
148c29a
to
243289a
Compare
if (type.GetTypeInfo().IsValueType) | ||
{ | ||
return Activator.CreateInstance(type); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: New line after }
243289a
to
905308b
Compare
private static MethodInfo s_getUninitializedObjectMethodInfo; | ||
private static Dictionary<Type, bool> s_typeHasDefaultConstructorMap; | ||
|
||
static XmlFormatReaderGenerator() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This explicit static cctor causes the C# compiler to not mark the type as beforefieldinit, which in turn means that the backend compiler needs to add checks to static methods to ensure that the type has been initialized. See #6016 for details.
To avoid this, consider removing the static cctor and initializing the static fields directly, e.g.:
private static readonly MethodInfo s_getUninitializedObjectMethodInfo =
typeof(string)
.GetTypeInfo()
.Assembly
.GetType("System.Runtime.Serialization.FormatterServices")
?.GetMethod("GetUninitializedObject", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
private static readonly Dictionary<Type, bool> s_typeHasDefaultConstructorMap = new Dictionary<Type, bool>();
Also, these static fields can be readonly
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for pointing this out! I have removed the static constructor.
905308b
to
91df870
Compare
@dotnet-bot test this please |
@dotnet-bot test Innerloop Ubuntu Release Build and Test please |
@dotnet-bot test Innerloop OSX Debug Build and Test please |
Did you try replacing the MethodInfo.Invoke call with creating a delegate and calling that instead? |
Yes I did. I was experimenting to get the perf numbers for different changes and wasn't sure how to add that change. I've now updated to include it as well. |
@@ -30,6 +30,17 @@ public sealed class XmlFormatReaderGenerator | |||
internal sealed class XmlFormatReaderGenerator | |||
#endif | |||
{ | |||
private static readonly MethodInfo s_getUninitializedObjectMethodInfo = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this field be removed now, replaced by the delegate field?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes it can. I was thinking the line may get too long but actually it looks Ok.
25e8738
to
fcc71d2
Compare
{ | ||
obj = methodInfo.Invoke(null, new object[] { type }); | ||
} | ||
obj = s_getUninitializedObjectDelegate(type); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for not pointing this out earlier, but this whole method could be simplified as:
static internal object TryGetUninitializedObjectWithFormatterServices(Type type) =>
s_getUninitializedObjectDelegate?.Invoke(type);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice. Updated as suggestion.
fcc71d2
to
025779f
Compare
Test Innerloop CentOS7.1 Debug Build and Test please |
Test Innerloop OSX Debug Build and Test please |
…on_perf_dcs_dictionary Improve data contract serializers performance in dictionary scenario Commit migrated from dotnet/corefx@0444706
Internally dictionaries are handled as collection of KeyValues. I've updated this type to have default constructor so we can use Activator.CreateInstance to create new instance of it.Update:
cc: @shmao @SGuyGe @zhenlan @mconnew
Fix #7578 and #7577