From ff3bfaf51b14db121e2a404eef0af9024033ece6 Mon Sep 17 00:00:00 2001 From: Jon Hanna Date: Sun, 8 Jan 2017 16:32:04 +0000 Subject: [PATCH] Use singleton for zero-element arrays. Create an easily-inlinable method that accesses the array used by Enumerable.Empty (and hence not just shared within this project but likely with the client assembly or other libraries it uses) and use it anywhere a zero-element array is used, reducing allocations. --- Src/Newtonsoft.Json/JsonReader.cs | 2 +- Src/Newtonsoft.Json/JsonTextReader.cs | 2 +- .../Linq/JsonPath/QueryExpression.cs | 2 +- .../JsonSerializerInternalReader.cs | 2 +- .../JsonSerializerInternalWriter.cs | 2 +- .../Utilities/CollectionUtils.cs | 27 +++++++- Src/Newtonsoft.Json/Utilities/DynamicProxy.cs | 2 +- .../Utilities/DynamicProxyMetaObject.cs | 2 +- Src/Newtonsoft.Json/Utilities/DynamicUtils.cs | 2 +- .../ExpressionReflectionDelegateFactory.cs | 63 ++++++++++--------- .../Utilities/ReflectionUtils.cs | 2 +- 11 files changed, 69 insertions(+), 39 deletions(-) diff --git a/Src/Newtonsoft.Json/JsonReader.cs b/Src/Newtonsoft.Json/JsonReader.cs index d271d8a7..3f2b8aa4 100644 --- a/Src/Newtonsoft.Json/JsonReader.cs +++ b/Src/Newtonsoft.Json/JsonReader.cs @@ -544,7 +544,7 @@ namespace Newtonsoft.Json Guid g; if (s.Length == 0) { - data = new byte[0]; + data = CollectionUtils.ArrayEmpty(); } else if (ConvertUtils.TryConvertGuid(s, out g)) { diff --git a/Src/Newtonsoft.Json/JsonTextReader.cs b/Src/Newtonsoft.Json/JsonTextReader.cs index 7db4820a..56e0ee72 100644 --- a/Src/Newtonsoft.Json/JsonTextReader.cs +++ b/Src/Newtonsoft.Json/JsonTextReader.cs @@ -146,7 +146,7 @@ namespace Newtonsoft.Json byte[] data; if (_stringReference.Length == 0) { - data = new byte[0]; + data = CollectionUtils.ArrayEmpty(); } else if (_stringReference.Length == 36 && ConvertUtils.TryConvertGuid(_stringReference.ToString(), out g)) { diff --git a/Src/Newtonsoft.Json/Linq/JsonPath/QueryExpression.cs b/Src/Newtonsoft.Json/Linq/JsonPath/QueryExpression.cs index 1139d559..a01ed4c1 100644 --- a/Src/Newtonsoft.Json/Linq/JsonPath/QueryExpression.cs +++ b/Src/Newtonsoft.Json/Linq/JsonPath/QueryExpression.cs @@ -83,7 +83,7 @@ namespace Newtonsoft.Json.Linq.JsonPath return JPath.Evaluate(pathFilters, root, t, false); } - return new JToken[0]; + return CollectionUtils.ArrayEmpty(); } public override bool IsMatch(JToken root, JToken t) diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs index 440936e8..ed0fd015 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs @@ -2274,7 +2274,7 @@ namespace Newtonsoft.Json.Serialization return CreateObjectUsingCreatorWithParameters(reader, objectContract, containerMember, objectContract.OverrideCreator, id); } - newObject = objectContract.OverrideCreator(new object[0]); + newObject = objectContract.OverrideCreator(CollectionUtils.ArrayEmpty()); } else if (objectContract.DefaultCreator != null && (!objectContract.DefaultCreatorNonPublic || Serializer._constructorHandling == ConstructorHandling.AllowNonPublicDefaultConstructor || objectContract.ParameterizedCreator == null)) diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs index b947a567..fa73fdb8 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs @@ -745,7 +745,7 @@ namespace Newtonsoft.Json.Serialization bool hasWrittenMetadataObject = WriteStartArray(writer, values, contract, member, collectionContract, containerProperty); - SerializeMultidimensionalArray(writer, values, contract, member, writer.Top, new int[0]); + SerializeMultidimensionalArray(writer, values, contract, member, writer.Top, CollectionUtils.ArrayEmpty()); if (hasWrittenMetadataObject) { diff --git a/Src/Newtonsoft.Json/Utilities/CollectionUtils.cs b/Src/Newtonsoft.Json/Utilities/CollectionUtils.cs index 06b86df0..6bfef174 100644 --- a/Src/Newtonsoft.Json/Utilities/CollectionUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/CollectionUtils.cs @@ -29,12 +29,16 @@ using System.Collections.ObjectModel; using System.Reflection; using System.Text; using System.Collections; +using System.Diagnostics; #if NET20 using Newtonsoft.Json.Utilities.LinqBridge; #else using System.Linq; #endif using System.Globalization; +#if !(NET20 || NET35 || NET40 || PORTABLE40) +using System.Runtime.CompilerServices; +#endif using Newtonsoft.Json.Serialization; namespace Newtonsoft.Json.Utilities @@ -340,9 +344,30 @@ namespace Newtonsoft.Json.Utilities } Array multidimensionalArray = Array.CreateInstance(type, dimensions.ToArray()); - CopyFromJaggedToMultidimensionalArray(values, multidimensionalArray, new int[0]); + CopyFromJaggedToMultidimensionalArray(values, multidimensionalArray, ArrayEmpty()); return multidimensionalArray; } + + // 4.6 has Array.Empty to return a cached empty array. Lacking that in other + // frameworks, Enumerable.Empty happens to be implemented as a cached empty + // array in all versions (in .NET Core the same instance as Array.Empty). + // This includes the internal Linq bridge for 2.0. + // Since this method is simple and only 11 bytes long in a release build it's + // pretty much guaranteed to be inlined, giving us fast access of that cached + // array. With 4.5 and up we use AggressiveInlining just to be sure, so it's + // effectively the same as calling Array.Empty even when not available. +#if !(NET20 || NET35 || NET40 || PORTABLE40) + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public static T[] ArrayEmpty() + { + T[] array = Enumerable.Empty() as T[]; + Debug.Assert(array != null); + // Defensively guard against a version of Linq where Enumerable.Empty doesn't + // return an array, but throw in debug versions so a better strategy can be + // used if that ever happens. + return array ?? new T[0]; + } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Utilities/DynamicProxy.cs b/Src/Newtonsoft.Json/Utilities/DynamicProxy.cs index 7fb0100d..56edbcb7 100644 --- a/Src/Newtonsoft.Json/Utilities/DynamicProxy.cs +++ b/Src/Newtonsoft.Json/Utilities/DynamicProxy.cs @@ -37,7 +37,7 @@ namespace Newtonsoft.Json.Utilities { public virtual IEnumerable GetDynamicMemberNames(T instance) { - return new string[0]; + return CollectionUtils.ArrayEmpty(); } public virtual bool TryBinaryOperation(T instance, BinaryOperationBinder binder, object arg, out object result) diff --git a/Src/Newtonsoft.Json/Utilities/DynamicProxyMetaObject.cs b/Src/Newtonsoft.Json/Utilities/DynamicProxyMetaObject.cs index 28321d55..1f935113 100644 --- a/Src/Newtonsoft.Json/Utilities/DynamicProxyMetaObject.cs +++ b/Src/Newtonsoft.Json/Utilities/DynamicProxyMetaObject.cs @@ -176,7 +176,7 @@ namespace Newtonsoft.Json.Utilities private delegate DynamicMetaObject Fallback(DynamicMetaObject errorSuggestion); - private static readonly Expression[] NoArgs = new Expression[0]; + private static Expression[] NoArgs => CollectionUtils.ArrayEmpty(); private static Expression[] GetArgs(params DynamicMetaObject[] args) { diff --git a/Src/Newtonsoft.Json/Utilities/DynamicUtils.cs b/Src/Newtonsoft.Json/Utilities/DynamicUtils.cs index a83d311b..e61f9178 100644 --- a/Src/Newtonsoft.Json/Utilities/DynamicUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/DynamicUtils.cs @@ -158,7 +158,7 @@ namespace Newtonsoft.Json.Utilities public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject errorSuggestion) { - DynamicMetaObject retMetaObject = _innerBinder.Bind(target, new DynamicMetaObject[] { }); + DynamicMetaObject retMetaObject = _innerBinder.Bind(target, CollectionUtils.ArrayEmpty()); NoThrowExpressionVisitor noThrowVisitor = new NoThrowExpressionVisitor(); Expression resultExpression = noThrowVisitor.Visit(retMetaObject.Expression); diff --git a/Src/Newtonsoft.Json/Utilities/ExpressionReflectionDelegateFactory.cs b/Src/Newtonsoft.Json/Utilities/ExpressionReflectionDelegateFactory.cs index 32581bcf..de2fa1b9 100644 --- a/Src/Newtonsoft.Json/Utilities/ExpressionReflectionDelegateFactory.cs +++ b/Src/Newtonsoft.Json/Utilities/ExpressionReflectionDelegateFactory.cs @@ -87,40 +87,45 @@ namespace Newtonsoft.Json.Utilities { ParameterInfo[] parametersInfo = method.GetParameters(); - Expression[] argsExpression = new Expression[parametersInfo.Length]; - IList refParameterMap = new List(); - - for (int i = 0; i < parametersInfo.Length; i++) + Expression[] argsExpression; + IList refParameterMap; + if (parametersInfo.Length == 0) { - ParameterInfo parameter = parametersInfo[i]; - Type parameterType = parameter.ParameterType; - bool isByRef = false; - if (parameterType.IsByRef) + argsExpression = CollectionUtils.ArrayEmpty(); + refParameterMap = CollectionUtils.ArrayEmpty(); + } + else + { + argsExpression = new Expression[parametersInfo.Length]; + refParameterMap = new List(); + + for (int i = 0; i < parametersInfo.Length; i++) { - parameterType = parameterType.GetElementType(); - isByRef = true; - } - - Expression indexExpression = Expression.Constant(i); - - Expression paramAccessorExpression = Expression.ArrayIndex(argsParameterExpression, indexExpression); - - Expression argExpression = EnsureCastExpression(paramAccessorExpression, parameterType, !isByRef); - - if (isByRef) - { - ParameterExpression variable = Expression.Variable(parameterType); - refParameterMap.Add(new ByRefParameter + ParameterInfo parameter = parametersInfo[i]; + Type parameterType = parameter.ParameterType; + bool isByRef = false; + if (parameterType.IsByRef) { - Value = argExpression, - Variable = variable, - IsOut = parameter.IsOut - }); + parameterType = parameterType.GetElementType(); + isByRef = true; + } - argExpression = variable; + Expression indexExpression = Expression.Constant(i); + + Expression paramAccessorExpression = Expression.ArrayIndex(argsParameterExpression, indexExpression); + + Expression argExpression = EnsureCastExpression(paramAccessorExpression, parameterType, !isByRef); + + if (isByRef) + { + ParameterExpression variable = Expression.Variable(parameterType); + refParameterMap.Add(new ByRefParameter {Value = argExpression, Variable = variable, IsOut = parameter.IsOut}); + + argExpression = variable; + } + + argsExpression[i] = argExpression; } - - argsExpression[i] = argExpression; } Expression callExpression; diff --git a/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs b/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs index 43e3c47b..34242961 100644 --- a/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs @@ -90,7 +90,7 @@ namespace Newtonsoft.Json.Utilities #if !(PORTABLE40 || PORTABLE) EmptyTypes = Type.EmptyTypes; #else - EmptyTypes = new Type[0]; + EmptyTypes = CollectionUtils.ArrayEmpty(); #endif }