From 363c8fab2ca1c903fba1f8dcce54e60be618c9d8 Mon Sep 17 00:00:00 2001 From: Stephan Date: Sat, 20 Jan 2018 15:10:22 +0100 Subject: [PATCH] Cleanup --- .../Collections/CompositeTypeTypeKey.cs | 52 +++ src/Umbraco.Core/ObjectExtensions.cs | 355 +++++++++--------- src/Umbraco.Core/TypePair.cs | 54 --- src/Umbraco.Core/Umbraco.Core.csproj | 2 +- .../BulkInsertBenchmarks.cs | 2 +- ...ttribute.cs => QuickRunConfigAttribute.cs} | 16 +- .../QuickRunWithMemoryDiagnoserAttribute.cs | 17 - ...ckRunWithMemoryDiagnoserConfigAttribute.cs | 20 + .../StringReplaceManyBenchmarks.cs | 159 +++++--- .../Umbraco.Tests.Benchmarks.csproj | 4 +- src/Umbraco.Tests.Benchmarks/XmlBenchmarks.cs | 2 +- .../XmlPublishedContentInitBenchmarks.cs | 2 +- 12 files changed, 358 insertions(+), 327 deletions(-) create mode 100644 src/Umbraco.Core/Collections/CompositeTypeTypeKey.cs delete mode 100644 src/Umbraco.Core/TypePair.cs rename src/Umbraco.Tests.Benchmarks/Config/{QuickRunAttribute.cs => QuickRunConfigAttribute.cs} (58%) delete mode 100644 src/Umbraco.Tests.Benchmarks/Config/QuickRunWithMemoryDiagnoserAttribute.cs create mode 100644 src/Umbraco.Tests.Benchmarks/Config/QuickRunWithMemoryDiagnoserConfigAttribute.cs diff --git a/src/Umbraco.Core/Collections/CompositeTypeTypeKey.cs b/src/Umbraco.Core/Collections/CompositeTypeTypeKey.cs new file mode 100644 index 0000000000..07c9a8ded2 --- /dev/null +++ b/src/Umbraco.Core/Collections/CompositeTypeTypeKey.cs @@ -0,0 +1,52 @@ +using System; + +namespace Umbraco.Core.Collections +{ + /// + /// Represents a composite key of (Type, Type) for fast dictionaries. + /// + internal struct CompositeTypeTypeKey : IEquatable + { + /// + /// Initializes a new instance of the struct. + /// + public CompositeTypeTypeKey(Type type1, Type type2) + { + Type1 = type1; + Type2 = type2; + } + + /// + /// Gets the first type. + /// + public Type Type1 { get; } + + /// + /// Gets the second type. + /// + public Type Type2 { get; } + + /// + public bool Equals(CompositeTypeTypeKey other) + => Type1 == other.Type1 && Type2 == other.Type2; + + /// + public override bool Equals(object obj) + => obj is CompositeTypeTypeKey other && Type1 == other.Type1 && Type2 == other.Type2; + + public static bool operator ==(CompositeTypeTypeKey key1, CompositeTypeTypeKey key2) + => key1.Type1 == key2.Type1 && key1.Type2 == key2.Type2; + + public static bool operator !=(CompositeTypeTypeKey key1, CompositeTypeTypeKey key2) + => key1.Type1 != key2.Type1 || key1.Type2 != key2.Type2; + + /// + public override int GetHashCode() + { + unchecked + { + return (Type1.GetHashCode() * 397) ^ Type2.GetHashCode(); + } + } + } +} diff --git a/src/Umbraco.Core/ObjectExtensions.cs b/src/Umbraco.Core/ObjectExtensions.cs index 9754cb7943..e875f5942f 100644 --- a/src/Umbraco.Core/ObjectExtensions.cs +++ b/src/Umbraco.Core/ObjectExtensions.cs @@ -5,8 +5,9 @@ using System.ComponentModel; using System.Linq; using System.Linq.Expressions; using System.Reflection; -using System.Runtime.CompilerServices; +using System.Runtime.CompilerServices; using System.Xml; +using Umbraco.Core.Collections; namespace Umbraco.Core { @@ -14,19 +15,19 @@ namespace Umbraco.Core /// Provides object extension methods. /// public static class ObjectExtensions - { - // Used for caching the various type lookups + { + // Cache the various type lookups private static readonly Dictionary NullableGenericCache = new Dictionary(); - private static readonly Dictionary InputTypeConverterCache = new Dictionary(); - private static readonly Dictionary DestinationTypeConverterCache = new Dictionary(); - private static readonly Dictionary AssignableTypeCache = new Dictionary(); + private static readonly Dictionary InputTypeConverterCache = new Dictionary(); + private static readonly Dictionary DestinationTypeConverterCache = new Dictionary(); + private static readonly Dictionary AssignableTypeCache = new Dictionary(); private static readonly Dictionary BoolConvertCache = new Dictionary(); private static readonly char[] NumberDecimalSeparatorsToNormalize = { '.', ',' }; - private static readonly CustomBooleanTypeConverter CustomBooleanTypeConverter = new CustomBooleanTypeConverter(); - - //private static readonly ConcurrentDictionary> ObjectFactoryCache = new ConcurrentDictionary>(); - + private static readonly CustomBooleanTypeConverter CustomBooleanTypeConverter = new CustomBooleanTypeConverter(); + + //private static readonly ConcurrentDictionary> ObjectFactoryCache = new ConcurrentDictionary>(); + /// /// /// @@ -71,62 +72,60 @@ namespace Umbraco.Core /// The public static Attempt TryConvertTo(this object input) { - Attempt result = TryConvertTo(input, typeof(T)); - if (!result.Success) - { - // Just try a straight up conversion - try - { - var converted = (T)input; - return Attempt.Succeed(converted); - } - catch (Exception e) - { - return Attempt.Fail(e); - } - } + var result = TryConvertTo(input, typeof(T)); + + if (result.Success) + return Attempt.Succeed((T) result.Result); + + // just try to cast + try + { + return Attempt.Succeed((T) input); + } + catch (Exception e) + { + return Attempt.Fail(e); + } + } - return Attempt.Succeed((T)result.Result); - } - /// /// Attempts to convert the input object to the output type. /// /// This code is an optimized version of the original Umbraco method /// The input. - /// The type to convert to + /// The type to convert to /// The - public static Attempt TryConvertTo(this object input, Type destinationType) + public static Attempt TryConvertTo(this object input, Type target) { + if (target == null) + { + return Attempt.Fail(); + } + try { - if (destinationType == null) - { - return Attempt.Fail(); - } - if (input == null) { // Nullable is ok - if (destinationType.IsGenericType && GetCachedGenericNullableType(destinationType) != null) + if (target.IsGenericType && GetCachedGenericNullableType(target) != null) { return Attempt.Succeed(null); } // Reference types are ok - return Attempt.SucceedIf(!destinationType.IsValueType, null); + return Attempt.SucceedIf(target.IsValueType == false, null); } - Type inputType = input.GetType(); + var inputType = input.GetType(); // Easy - if (destinationType == typeof(object) || inputType == destinationType) + if (target == typeof(object) || inputType == target) { return Attempt.Succeed(input); } // Check for string so that overloaders of ToString() can take advantage of the conversion. - if (destinationType == typeof(string)) + if (target == typeof(string)) { return Attempt.Succeed(input.ToString()); } @@ -134,42 +133,44 @@ namespace Umbraco.Core // If we've got a nullable of something, we try to convert directly to that thing. // We cache the destination type and underlying nullable types // Any other generic types need to fall through - if (destinationType.IsGenericType) + if (target.IsGenericType) { - Type underlyingGenericType = GetCachedGenericNullableType(destinationType); - if (underlyingGenericType != null) + var underlying = GetCachedGenericNullableType(target); + if (underlying != null) { // Special case for empty strings for bools/dates which should return null if an empty string. - if (input is string asString) + if (input is string inputString) { - if (string.IsNullOrEmpty(asString) && (underlyingGenericType == typeof(DateTime) || underlyingGenericType == typeof(bool))) + if (string.IsNullOrEmpty(inputString) && (underlying == typeof(DateTime) || underlying == typeof(bool))) { return Attempt.Succeed(null); } } // Recursively call into this method with the inner (not-nullable) type and handle the outcome - Attempt nonNullable = input.TryConvertTo(underlyingGenericType); + var inner = input.TryConvertTo(underlying); // And if sucessful, fall on through to rewrap in a nullable; if failed, pass on the exception - if (nonNullable.Success) + if (inner.Success) { - input = nonNullable.Result; // Now fall on through... + input = inner.Result; // Now fall on through... } else { - return Attempt.Fail(nonNullable.Exception); + return Attempt.Fail(inner.Exception); } } } else - { - if (input is string asString) + { + // target is not a generic type + + if (input is string inputString) { // Try convert from string, returns an Attempt if the string could be // processed (either succeeded or failed), else null if we need to try // other methods - Attempt? result = TryConvertToFromString(asString, destinationType); + var result = TryConvertToFromString(inputString, target); if (result.HasValue) { return result.Value; @@ -178,14 +179,14 @@ namespace Umbraco.Core // TODO: Do a check for destination type being IEnumerable and source type implementing IEnumerable with // the same 'T', then we'd have to find the extension method for the type AsEnumerable() and execute it. - IConvertible convertible = GetCachedAssignableConvertibleResult(input, inputType, destinationType); + var convertible = GetCachedAssignableConvertibleResult(input, inputType, target); if (convertible != null) { - return Attempt.Succeed(Convert.ChangeType(convertible, destinationType)); + return Attempt.Succeed(Convert.ChangeType(convertible, target)); } } - if (destinationType == typeof(bool)) + if (target == typeof(bool)) { if (GetCanConvertToBooleanResult(inputType)) { @@ -193,13 +194,13 @@ namespace Umbraco.Core } } - TypeConverter inputConverter = GetCachedInputTypeConverter(inputType, destinationType); + var inputConverter = GetCachedSourceTypeConverter(inputType, target); if (inputConverter != null) { - return Attempt.Succeed(inputConverter.ConvertTo(input, destinationType)); + return Attempt.Succeed(inputConverter.ConvertTo(input, target)); } - TypeConverter outputConverter = GetCachedDestinationTypeConverter(inputType, destinationType); + var outputConverter = GetCachedTargetTypeConverter(inputType, target); if (outputConverter != null) { return Attempt.Succeed(outputConverter.ConvertFrom(input)); @@ -208,7 +209,7 @@ namespace Umbraco.Core // Re-check convertables since we altered the input through recursion if (input is IConvertible convertible2) { - return Attempt.Succeed(Convert.ChangeType(convertible2, destinationType)); + return Attempt.Succeed(Convert.ChangeType(convertible2, target)); } } catch (Exception e) @@ -217,19 +218,19 @@ namespace Umbraco.Core } return Attempt.Fail(); - } - + } + /// /// Attempts to convert the input string to the output type. /// /// This code is an optimized version of the original Umbraco method /// The input. - /// The type to convert to + /// The type to convert to /// The - private static Attempt? TryConvertToFromString(this string input, Type destinationType) + private static Attempt? TryConvertToFromString(this string input, Type target) { // Easy - if (destinationType == typeof(string)) + if (target == typeof(string)) { return Attempt.Succeed(input); } @@ -237,13 +238,13 @@ namespace Umbraco.Core // Null, empty, whitespaces if (string.IsNullOrWhiteSpace(input)) { - if (destinationType == typeof(bool)) + if (target == typeof(bool)) { // null/empty = bool false return Attempt.Succeed(false); } - if (destinationType == typeof(DateTime)) + if (target == typeof(DateTime)) { // null/empty = min DateTime value return Attempt.Succeed(DateTime.MinValue); @@ -260,37 +261,38 @@ namespace Umbraco.Core // By using a mixture of ordered if statements and switches we can optimize both for // fast conditional checking for most frequently used types and the branching // that does not depend on previous values available to switch statements. - if (destinationType.IsPrimitive) + if (target.IsPrimitive) { - if (destinationType == typeof(int)) + if (target == typeof(int)) { - if (int.TryParse(input, out int value)) + if (int.TryParse(input, out var value)) { return Attempt.Succeed(value); } // Because decimal 100.01m will happily convert to integer 100, it // makes sense that string "100.01" *also* converts to integer 100. - string input2 = NormalizeNumberDecimalSeparator(input); - return Attempt.SucceedIf(decimal.TryParse(input2, out decimal value2), Convert.ToInt32(value2)); + var input2 = NormalizeNumberDecimalSeparator(input); + return Attempt.SucceedIf(decimal.TryParse(input2, out var value2), Convert.ToInt32(value2)); } - if (destinationType == typeof(long)) + if (target == typeof(long)) { - if (long.TryParse(input, out long value)) + if (long.TryParse(input, out var value)) { return Attempt.Succeed(value); } // Same as int - string input2 = NormalizeNumberDecimalSeparator(input); - return Attempt.SucceedIf(decimal.TryParse(input2, out decimal value2), Convert.ToInt64(value2)); + var input2 = NormalizeNumberDecimalSeparator(input); + return Attempt.SucceedIf(decimal.TryParse(input2, out var value2), Convert.ToInt64(value2)); } - // TODO: Should we do the decimal trick for short, byte, unsigned? - if (destinationType == typeof(bool)) + // TODO: Should we do the decimal trick for short, byte, unsigned? + + if (target == typeof(bool)) { - if (bool.TryParse(input, out bool value)) + if (bool.TryParse(input, out var value)) { return Attempt.Succeed(value); } @@ -300,45 +302,45 @@ namespace Umbraco.Core } // Calling this method directly is faster than any attempt to cache it. - switch (Type.GetTypeCode(destinationType)) + switch (Type.GetTypeCode(target)) { case TypeCode.Int16: - return Attempt.SucceedIf(short.TryParse(input, out short value), value); + return Attempt.SucceedIf(short.TryParse(input, out var value), value); case TypeCode.Double: - string input2 = NormalizeNumberDecimalSeparator(input); - return Attempt.SucceedIf(double.TryParse(input2, out double valueD), valueD); + var input2 = NormalizeNumberDecimalSeparator(input); + return Attempt.SucceedIf(double.TryParse(input2, out var valueD), valueD); case TypeCode.Single: - string input3 = NormalizeNumberDecimalSeparator(input); - return Attempt.SucceedIf(float.TryParse(input3, out float valueF), valueF); + var input3 = NormalizeNumberDecimalSeparator(input); + return Attempt.SucceedIf(float.TryParse(input3, out var valueF), valueF); case TypeCode.Char: - return Attempt.SucceedIf(char.TryParse(input, out char valueC), valueC); + return Attempt.SucceedIf(char.TryParse(input, out var valueC), valueC); case TypeCode.Byte: - return Attempt.SucceedIf(byte.TryParse(input, out byte valueB), valueB); + return Attempt.SucceedIf(byte.TryParse(input, out var valueB), valueB); case TypeCode.SByte: - return Attempt.SucceedIf(sbyte.TryParse(input, out sbyte valueSb), valueSb); + return Attempt.SucceedIf(sbyte.TryParse(input, out var valueSb), valueSb); case TypeCode.UInt32: - return Attempt.SucceedIf(uint.TryParse(input, out uint valueU), valueU); + return Attempt.SucceedIf(uint.TryParse(input, out var valueU), valueU); case TypeCode.UInt16: - return Attempt.SucceedIf(ushort.TryParse(input, out ushort valueUs), valueUs); + return Attempt.SucceedIf(ushort.TryParse(input, out var valueUs), valueUs); case TypeCode.UInt64: - return Attempt.SucceedIf(ulong.TryParse(input, out ulong valueUl), valueUl); + return Attempt.SucceedIf(ulong.TryParse(input, out var valueUl), valueUl); } } - else if (destinationType == typeof(Guid)) + else if (target == typeof(Guid)) { - return Attempt.SucceedIf(Guid.TryParse(input, out Guid value), value); + return Attempt.SucceedIf(Guid.TryParse(input, out var value), value); } - else if (destinationType == typeof(DateTime)) + else if (target == typeof(DateTime)) { - if (DateTime.TryParse(input, out DateTime value)) + if (DateTime.TryParse(input, out var value)) { switch (value.Kind) { @@ -356,27 +358,27 @@ namespace Umbraco.Core return Attempt.Fail(); } - else if (destinationType == typeof(DateTimeOffset)) + else if (target == typeof(DateTimeOffset)) { - return Attempt.SucceedIf(DateTimeOffset.TryParse(input, out DateTimeOffset value), value); + return Attempt.SucceedIf(DateTimeOffset.TryParse(input, out var value), value); } - else if (destinationType == typeof(TimeSpan)) + else if (target == typeof(TimeSpan)) { - return Attempt.SucceedIf(TimeSpan.TryParse(input, out TimeSpan value), value); + return Attempt.SucceedIf(TimeSpan.TryParse(input, out var value), value); } - else if (destinationType == typeof(decimal)) + else if (target == typeof(decimal)) { - string input2 = NormalizeNumberDecimalSeparator(input); - return Attempt.SucceedIf(decimal.TryParse(input2, out decimal value), value); + var input2 = NormalizeNumberDecimalSeparator(input); + return Attempt.SucceedIf(decimal.TryParse(input2, out var value), value); } - else if (input != null && destinationType == typeof(Version)) + else if (input != null && target == typeof(Version)) { - return Attempt.SucceedIf(Version.TryParse(input, out Version value), value); + return Attempt.SucceedIf(Version.TryParse(input, out var value), value); } // E_NOTIMPL IPAddress, BigInteger return null; // We can't decide... - } + } internal static void CheckThrowObjectDisposed(this IDisposable disposable, bool isDisposed, string objectname) { //TODO: Localise this exception @@ -641,115 +643,100 @@ namespace Umbraco.Core internal static Guid AsGuid(this object value) { return value is Guid ? (Guid) value : Guid.Empty; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static string NormalizeNumberDecimalSeparator(string s) - { + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static string NormalizeNumberDecimalSeparator(string s) + { var normalized = System.Threading.Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator[0]; - return s.ReplaceMany(NumberDecimalSeparatorsToNormalize, normalized); - } + return s.ReplaceMany(NumberDecimalSeparatorsToNormalize, normalized); + } + // gets a converter for source, that can convert to target, or null if none exists [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static TypeConverter GetCachedInputTypeConverter(Type inputType, Type destinationType) + private static TypeConverter GetCachedSourceTypeConverter(Type source, Type target) { - var key = new TypePair(inputType, destinationType); - if (!InputTypeConverterCache.ContainsKey(key)) + var key = new CompositeTypeTypeKey(source, target); + if (InputTypeConverterCache.TryGetValue(key, out var cached)) + return cached; + + var converter = TypeDescriptor.GetConverter(source); + if (converter.CanConvertTo(target)) { - TypeConverter converter = TypeDescriptor.GetConverter(inputType); - if (converter.CanConvertTo(destinationType)) - { - InputTypeConverterCache[key] = converter; - return converter; - } - else - { - InputTypeConverterCache[key] = null; - return null; - } + InputTypeConverterCache[key] = converter; + return converter; } - return InputTypeConverterCache[key]; + InputTypeConverterCache[key] = null; + return null; } - + + // gets a converter for target, that can convert from source, or null if none exists [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static TypeConverter GetCachedDestinationTypeConverter(Type inputType, Type destinationType) + private static TypeConverter GetCachedTargetTypeConverter(Type source, Type target) { - var key = new TypePair(inputType, destinationType); - if (!DestinationTypeConverterCache.ContainsKey(key)) + var key = new CompositeTypeTypeKey(source, target); + if (DestinationTypeConverterCache.TryGetValue(key, out var cached)) + return cached; + + var converter = TypeDescriptor.GetConverter(target); + if (converter.CanConvertFrom(source)) { - TypeConverter converter = TypeDescriptor.GetConverter(destinationType); - if (converter.CanConvertFrom(inputType)) - { - DestinationTypeConverterCache[key] = converter; - return converter; - } - else - { - DestinationTypeConverterCache[key] = null; - return null; - } + DestinationTypeConverterCache[key] = converter; + return converter; } - return DestinationTypeConverterCache[key]; + DestinationTypeConverterCache[key] = null; + return null; } - + + // gets the underlying type of a nullable type, or null if the type is not nullable [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Type GetCachedGenericNullableType(Type destinationType) + private static Type GetCachedGenericNullableType(Type type) { - if (!NullableGenericCache.ContainsKey(destinationType)) + if (NullableGenericCache.TryGetValue(type, out var cached)) + return cached; + + if (type.GetGenericTypeDefinition() == typeof(Nullable<>)) { - if (destinationType.GetGenericTypeDefinition() == typeof(Nullable<>)) - { - Type underlying = Nullable.GetUnderlyingType(destinationType); - NullableGenericCache[destinationType] = underlying; - return underlying; - } - else - { - NullableGenericCache[destinationType] = null; - return null; - } + var underlying = Nullable.GetUnderlyingType(type); + NullableGenericCache[type] = underlying; + return underlying; } - return NullableGenericCache[destinationType]; + NullableGenericCache[type] = null; + return null; + } - + + // gets an IConvertible from source to target type, or null if none exists [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static IConvertible GetCachedAssignableConvertibleResult(object input, Type inputType, Type destinationType) + private static IConvertible GetCachedAssignableConvertibleResult(object input, Type source, Type target) { - var key = new TypePair(inputType, destinationType); - if (!AssignableTypeCache.ContainsKey(key)) + var key = new CompositeTypeTypeKey(source, target); + if (AssignableTypeCache.TryGetValue(key, out var cached)) + return cached; + + if (target.IsAssignableFrom(source) && input is IConvertible convertible) { - if (destinationType.IsAssignableFrom(inputType)) - { - if (input is IConvertible convertable) - { - AssignableTypeCache[key] = convertable; - return convertable; - } - } - else - { - AssignableTypeCache[key] = null; - return null; - } + AssignableTypeCache[key] = convertible; + return convertible; } - return AssignableTypeCache[key]; + AssignableTypeCache[key] = null; + return null; } - + + // determines whether a type can be converted to boolean [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool GetCanConvertToBooleanResult(Type inputType) + private static bool GetCanConvertToBooleanResult(Type type) { - if (!BoolConvertCache.ContainsKey(inputType)) - { - bool canConvert = CustomBooleanTypeConverter.CanConvertFrom(inputType); - BoolConvertCache[inputType] = canConvert; - return canConvert; - } + if (BoolConvertCache.TryGetValue(type, out var cached)) + return cached; - return BoolConvertCache[inputType]; - } + var canConvert = CustomBooleanTypeConverter.CanConvertFrom(type); + BoolConvertCache[type] = canConvert; + return canConvert; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/TypePair.cs b/src/Umbraco.Core/TypePair.cs deleted file mode 100644 index 48568c1f94..0000000000 --- a/src/Umbraco.Core/TypePair.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; - -namespace Umbraco.Core -{ - /// - /// A lightweight struct for storing a pair of types for caching. - /// - internal struct TypePair : IEquatable - { - /// - /// Initializes a new instance of the struct. - /// - /// The first type - /// The second type - public TypePair(Type type1, Type type2) - { - this.Type1 = type1; - this.Type2 = type2; - } - - /// - /// Gets the first type - /// - public Type Type1 { get; } - - /// - /// Gets the second type - /// - public Type Type2 { get; } - - /// - public bool Equals(TypePair other) - { - return this.Type1 == other.Type1 && this.Type2 == other.Type2; - } - - /// - public override bool Equals(object obj) - { - return (obj is TypePair) && this.Equals((TypePair)obj); - } - - /// - public override int GetHashCode() - { - unchecked - { - int hashCode = this.Type1.GetHashCode(); - hashCode = (hashCode * 397) ^ this.Type2.GetHashCode(); - return hashCode; - } - } - } -} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 548788965e..38e5837378 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -1531,7 +1531,7 @@ - + diff --git a/src/Umbraco.Tests.Benchmarks/BulkInsertBenchmarks.cs b/src/Umbraco.Tests.Benchmarks/BulkInsertBenchmarks.cs index 2503931600..737082d485 100644 --- a/src/Umbraco.Tests.Benchmarks/BulkInsertBenchmarks.cs +++ b/src/Umbraco.Tests.Benchmarks/BulkInsertBenchmarks.cs @@ -15,7 +15,7 @@ using ILogger = Umbraco.Core.Logging.ILogger; namespace Umbraco.Tests.Benchmarks { - [QuickRunWithMemoryDiagnoser] + [QuickRunWithMemoryDiagnoserConfig] public class BulkInsertBenchmarks { private static byte[] _initDbBytes = null; diff --git a/src/Umbraco.Tests.Benchmarks/Config/QuickRunAttribute.cs b/src/Umbraco.Tests.Benchmarks/Config/QuickRunConfigAttribute.cs similarity index 58% rename from src/Umbraco.Tests.Benchmarks/Config/QuickRunAttribute.cs rename to src/Umbraco.Tests.Benchmarks/Config/QuickRunConfigAttribute.cs index c06932aa2e..f7d6b6bb72 100644 --- a/src/Umbraco.Tests.Benchmarks/Config/QuickRunAttribute.cs +++ b/src/Umbraco.Tests.Benchmarks/Config/QuickRunConfigAttribute.cs @@ -8,18 +8,26 @@ namespace Umbraco.Tests.Benchmarks.Config /// /// Configures the benchmark to run with less warmup and a shorter iteration time than the standard benchmark. /// - public class QuickRunAttribute : Attribute, IConfigSource + public class QuickRunConfigAttribute : Attribute, IConfigSource { - public QuickRunAttribute() + /// + /// Initializes a new instance of the class. + /// + public QuickRunConfigAttribute() { - Config = ManualConfig.CreateEmpty() + Config = (ManualConfig) ManualConfig.CreateEmpty() .With(Job.Default.WithLaunchCount(1) // benchmark process will be launched only once .WithIterationTime(new TimeInterval(100, TimeUnit.Millisecond)) // 100ms per iteration .WithWarmupCount(3) // 3 warmup iteration .WithTargetCount(3)); // 3 target iteration } + /// + /// Gets the manual configuration. + /// + protected ManualConfig Config { get; } + /// - public IConfig Config { get; } + IConfig IConfigSource.Config => Config; } } \ No newline at end of file diff --git a/src/Umbraco.Tests.Benchmarks/Config/QuickRunWithMemoryDiagnoserAttribute.cs b/src/Umbraco.Tests.Benchmarks/Config/QuickRunWithMemoryDiagnoserAttribute.cs deleted file mode 100644 index 12e3c9164e..0000000000 --- a/src/Umbraco.Tests.Benchmarks/Config/QuickRunWithMemoryDiagnoserAttribute.cs +++ /dev/null @@ -1,17 +0,0 @@ -using BenchmarkDotNet.Configs; -using BenchmarkDotNet.Diagnosers; - -namespace Umbraco.Tests.Benchmarks.Config -{ - /// - /// Configures the benchmark to run with less warmup and a shorter iteration time than the standard benchmark. - /// Memory usage diagnosis is included in the benchmark - /// - public class QuickRunWithMemoryDiagnoserAttribute : QuickRunAttribute - { - public QuickRunWithMemoryDiagnoserAttribute() - { - ((ManualConfig)this.Config).Add(new MemoryDiagnoser()); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Tests.Benchmarks/Config/QuickRunWithMemoryDiagnoserConfigAttribute.cs b/src/Umbraco.Tests.Benchmarks/Config/QuickRunWithMemoryDiagnoserConfigAttribute.cs new file mode 100644 index 0000000000..381efc9139 --- /dev/null +++ b/src/Umbraco.Tests.Benchmarks/Config/QuickRunWithMemoryDiagnoserConfigAttribute.cs @@ -0,0 +1,20 @@ +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Diagnosers; + +namespace Umbraco.Tests.Benchmarks.Config +{ + /// + /// Configures the benchmark to run with less warmup and a shorter iteration time than the standard benchmark, + /// and include memory usage diagnosis. + /// + public class QuickRunWithMemoryDiagnoserConfigAttribute : QuickRunConfigAttribute + { + /// + /// Initializes a new instance of the class. + /// + public QuickRunWithMemoryDiagnoserConfigAttribute() + { + Config.Add(new MemoryDiagnoser()); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests.Benchmarks/StringReplaceManyBenchmarks.cs b/src/Umbraco.Tests.Benchmarks/StringReplaceManyBenchmarks.cs index 12f2ffe7b7..796fe4f4b6 100644 --- a/src/Umbraco.Tests.Benchmarks/StringReplaceManyBenchmarks.cs +++ b/src/Umbraco.Tests.Benchmarks/StringReplaceManyBenchmarks.cs @@ -2,97 +2,132 @@ using System; using System.Collections.Generic; using System.Linq; +using Umbraco.Tests.Benchmarks.Config; namespace Umbraco.Tests.Benchmarks { - [MemoryDiagnoser] + [QuickRunWithMemoryDiagnoserConfig] public class StringReplaceManyBenchmarks { - private static string text = "1,2.3:4&5#6"; + /* - private static char[] chars = new[] { ',', '.', ':', '&', '#' }; + short text, short replacement: - private static char replacement = '*'; + Method | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated | + --------------------------------------------- |---------:|----------:|----------:|-------:|---------:|-------:|----------:| + 'String.ReplaceMany w/chars - Aggregate' | 236.0 ns | 40.92 ns | 2.312 ns | 1.00 | 0.00 | 0.0461 | 200 B | + 'String.ReplaceMany w/chars - For Loop' | 166.7 ns | 70.51 ns | 3.984 ns | 0.71 | 0.01 | 0.0420 | 180 B | + 'String.ReplaceMany w/dictionary - Aggregate' | 606.5 ns | 342.94 ns | 19.377 ns | 2.57 | 0.07 | 0.0473 | 212 B | + 'String.ReplaceMany w/dictionary - For Each' | 571.8 ns | 232.33 ns | 13.127 ns | 2.42 | 0.05 | 0.0458 | 212 B | - private static Dictionary replacements = new Dictionary(5) + long text, short replacement: + + Method | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated | + --------------------------------------------- |----------:|----------:|----------:|-------:|---------:|-------:|----------:| + 'String.ReplaceMany w/chars - Aggregate' | 5.771 us | 9.963 us | 0.5630 us | 1.00 | 0.00 | 1.6798 | 6.94 KB | + 'String.ReplaceMany w/chars - For Loop' | 4.962 us | 2.121 us | 0.1199 us | 0.87 | 0.08 | 1.6840 | 6.92 KB | + 'String.ReplaceMany w/dictionary - Aggregate' | 14.514 us | 8.189 us | 0.4627 us | 2.53 | 0.22 | 1.6447 | 6.96 KB | + 'String.ReplaceMany w/dictionary - For Each' | 15.445 us | 24.745 us | 1.3981 us | 2.69 | 0.30 | 1.5696 | 6.96 KB | + + short text, long replacements dictionary: + + Method | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated | + --------------------------------------------- |-----------:|-----------:|----------:|-------:|---------:|-------:|----------:| + 'String.ReplaceMany w/chars - Aggregate' | 257.0 ns | 200.0 ns | 11.30 ns | 1.00 | 0.00 | 0.0452 | 200 B | + 'String.ReplaceMany w/chars - For Loop' | 182.4 ns | 221.0 ns | 12.49 ns | 0.71 | 0.05 | 0.0425 | 180 B | + 'String.ReplaceMany w/dictionary - Aggregate' | 7,273.8 ns | 2,747.1 ns | 155.22 ns | 28.34 | 1.12 | 0.0714 | 464 B | + 'String.ReplaceMany w/dictionary - For Each' | 6,981.0 ns | 5,500.7 ns | 310.80 ns | 27.20 | 1.38 | 0.0775 | 464 B | + + long text, long replacements dictionary: + + Method | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated | + --------------------------------------------- |-----------:|-----------:|-----------:|-------:|---------:|-------:|----------:| + 'String.ReplaceMany w/chars - Aggregate' | 4.868 us | 3.420 us | 0.1932 us | 1.00 | 0.00 | 1.6816 | 6.94 KB | + 'String.ReplaceMany w/chars - For Loop' | 4.958 us | 2.633 us | 0.1487 us | 1.02 | 0.04 | 1.6791 | 6.92 KB | + 'String.ReplaceMany w/dictionary - Aggregate' | 181.309 us | 210.177 us | 11.8754 us | 37.29 | 2.32 | 5.3571 | 24.28 KB | + 'String.ReplaceMany w/dictionary - For Each' | 174.567 us | 113.733 us | 6.4262 us | 35.90 | 1.57 | 5.8594 | 24.28 KB | + + */ + + // don't use constants + // ReSharper disable ConvertToConstant.Local + + // input text for ReplaceMany + private static readonly string Text; + + // replaced chars for ReplaceMany with chars + private static readonly char[] ReplacedChars = { ',', '.', ':', '&', '#' }; + + // replacement char for ReplaceMany with chars + private static readonly char ReplacementChar = '*'; + + // replacements for ReplaceMany with dictionary + private static readonly IDictionary Replacements; + + // ReSharper restore ConvertToConstant.Local + + static StringReplaceManyBenchmarks() { - {",", "*" }, - {".", "*" }, - {":", "*" }, - {"&", "*" }, - {"#", "*" }, - }; + // pick what you want to benchmark - [Benchmark(Description = "String.ReplaceMany with Aggregate", Baseline = true)] - public string ReplaceManyAggregate() - { - if (text == null) + // short + Text = "1,2.3:4&5#6"; + + // long + //Text = "Sed ut perspiciatis unde omnis iste natus &error sit voluptatem accusantium doloremque l:audantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et &quasi architecto beatae vitae ::dicta sunt explicabo. Nemo enim ipsam volupta:tem quia voluptas sit aspernatur aut o&dit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciun&t. Neque porro quisquam est, qui dolorem: ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut e:nim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi co&&nsequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse: quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?"; + + // short + Replacements = new Dictionary { - throw new ArgumentNullException("text"); - } + { ",", "*" }, + { ".", "*" }, + { ":", "*" }, + { "&", "*" }, + { "#", "*" }, + }; - if (chars == null) - { - throw new ArgumentNullException("chars"); - } - - string result = text; - return chars.Aggregate(result, (current, c) => current.Replace(c, replacement)); + // long + //Replacements = new Dictionary(); + //for (var i = 2; i < 100; i++) + // Replacements[Convert.ToChar(i).ToString()] = "*"; } - [Benchmark(Description = "String.ReplaceMany with For Loop")] + // this is what v7 originally did + [Benchmark(Description = "String.ReplaceMany w/chars - Aggregate", Baseline = true)] + public string ReplaceManyAggregate() + { + var result = Text; + return ReplacedChars.Aggregate(result, (current, c) => current.Replace(c, ReplacementChar)); + } + + [Benchmark(Description = "String.ReplaceMany w/chars - For Loop")] public string ReplaceManyForLoop() { - if (text == null) - { - throw new ArgumentNullException("text"); - } + var result = Text; - if (chars == null) + // ReSharper disable once LoopCanBeConvertedToQuery + // ReSharper disable once ForCanBeConvertedToForeach + for (var i = 0; i < ReplacedChars.Length; i++) { - throw new ArgumentNullException("chars"); - } - - string result = text; - for (int i = 0; i < chars.Length; i++) - { - result = result.Replace(chars[i], replacement); + result = result.Replace(ReplacedChars[i], ReplacementChar); } return result; } - [Benchmark(Description = "String.ReplaceMany Dictionary with Aggregate")] + // this is what v7 originally did + [Benchmark(Description = "String.ReplaceMany w/dictionary - Aggregate")] public string ReplaceManyDictionaryAggregate() { - if (text == null) - { - throw new ArgumentNullException("text"); - } - - if (replacements == null) - { - throw new ArgumentNullException("replacements"); - } - - return replacements.Aggregate(text, (current, kvp) => current.Replace(kvp.Key, kvp.Value)); + return Replacements.Aggregate(Text, (current, kvp) => current.Replace(kvp.Key, kvp.Value)); } - [Benchmark(Description = "String.ReplaceMany Dictionary with For Each")] + [Benchmark(Description = "String.ReplaceMany w/dictionary - For Each")] public string ReplaceManyDictionaryForEach() { - if (text == null) - { - throw new ArgumentNullException("text"); - } - - if (replacements == null) - { - throw new ArgumentNullException("replacements"); - } - - string result = text; - foreach (KeyValuePair item in replacements) + var result = Text; + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var item in Replacements) { result = result.Replace(item.Key, item.Value); } diff --git a/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj b/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj index 11507d825a..2eb4f5f3d3 100644 --- a/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj +++ b/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj @@ -162,8 +162,8 @@ - - + + diff --git a/src/Umbraco.Tests.Benchmarks/XmlBenchmarks.cs b/src/Umbraco.Tests.Benchmarks/XmlBenchmarks.cs index c95af1d64a..e07bac9f78 100644 --- a/src/Umbraco.Tests.Benchmarks/XmlBenchmarks.cs +++ b/src/Umbraco.Tests.Benchmarks/XmlBenchmarks.cs @@ -5,7 +5,7 @@ using Umbraco.Tests.Benchmarks.Config; namespace Umbraco.Tests.Benchmarks { - [QuickRunWithMemoryDiagnoser] + [QuickRunWithMemoryDiagnoserConfig] public class XmlBenchmarks { [GlobalSetup] diff --git a/src/Umbraco.Tests.Benchmarks/XmlPublishedContentInitBenchmarks.cs b/src/Umbraco.Tests.Benchmarks/XmlPublishedContentInitBenchmarks.cs index 9113c6e6f4..94ce1d7043 100644 --- a/src/Umbraco.Tests.Benchmarks/XmlPublishedContentInitBenchmarks.cs +++ b/src/Umbraco.Tests.Benchmarks/XmlPublishedContentInitBenchmarks.cs @@ -11,7 +11,7 @@ using Umbraco.Web.PublishedCache.XmlPublishedCache; namespace Umbraco.Tests.Benchmarks { - [QuickRunWithMemoryDiagnoser] + [QuickRunWithMemoryDiagnoserConfig] public class XmlPublishedContentInitBenchmarks { public XmlPublishedContentInitBenchmarks()