This commit is contained in:
Stephan
2018-01-20 15:10:22 +01:00
parent 8d4fd9d1cf
commit 363c8fab2c
12 changed files with 358 additions and 327 deletions

View File

@@ -0,0 +1,52 @@
using System;
namespace Umbraco.Core.Collections
{
/// <summary>
/// Represents a composite key of (Type, Type) for fast dictionaries.
/// </summary>
internal struct CompositeTypeTypeKey : IEquatable<CompositeTypeTypeKey>
{
/// <summary>
/// Initializes a new instance of the <see cref="CompositeTypeTypeKey"/> struct.
/// </summary>
public CompositeTypeTypeKey(Type type1, Type type2)
{
Type1 = type1;
Type2 = type2;
}
/// <summary>
/// Gets the first type.
/// </summary>
public Type Type1 { get; }
/// <summary>
/// Gets the second type.
/// </summary>
public Type Type2 { get; }
/// <inheritdoc/>
public bool Equals(CompositeTypeTypeKey other)
=> Type1 == other.Type1 && Type2 == other.Type2;
/// <inheritdoc/>
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;
/// <inheritdoc/>
public override int GetHashCode()
{
unchecked
{
return (Type1.GetHashCode() * 397) ^ Type2.GetHashCode();
}
}
}
}

View File

@@ -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.
/// </summary>
public static class ObjectExtensions
{
// Used for caching the various type lookups
{
// Cache the various type lookups
private static readonly Dictionary<Type, Type> NullableGenericCache = new Dictionary<Type, Type>();
private static readonly Dictionary<TypePair, TypeConverter> InputTypeConverterCache = new Dictionary<TypePair, TypeConverter>();
private static readonly Dictionary<TypePair, TypeConverter> DestinationTypeConverterCache = new Dictionary<TypePair, TypeConverter>();
private static readonly Dictionary<TypePair, IConvertible> AssignableTypeCache = new Dictionary<TypePair, IConvertible>();
private static readonly Dictionary<CompositeTypeTypeKey, TypeConverter> InputTypeConverterCache = new Dictionary<CompositeTypeTypeKey, TypeConverter>();
private static readonly Dictionary<CompositeTypeTypeKey, TypeConverter> DestinationTypeConverterCache = new Dictionary<CompositeTypeTypeKey, TypeConverter>();
private static readonly Dictionary<CompositeTypeTypeKey, IConvertible> AssignableTypeCache = new Dictionary<CompositeTypeTypeKey, IConvertible>();
private static readonly Dictionary<Type, bool> BoolConvertCache = new Dictionary<Type, bool>();
private static readonly char[] NumberDecimalSeparatorsToNormalize = { '.', ',' };
private static readonly CustomBooleanTypeConverter CustomBooleanTypeConverter = new CustomBooleanTypeConverter();
//private static readonly ConcurrentDictionary<Type, Func<object>> ObjectFactoryCache = new ConcurrentDictionary<Type, Func<object>>();
private static readonly CustomBooleanTypeConverter CustomBooleanTypeConverter = new CustomBooleanTypeConverter();
//private static readonly ConcurrentDictionary<Type, Func<object>> ObjectFactoryCache = new ConcurrentDictionary<Type, Func<object>>();
/// <summary>
///
/// </summary>
@@ -71,62 +72,60 @@ namespace Umbraco.Core
/// <returns>The <see cref="Attempt{T}"/></returns>
public static Attempt<T> TryConvertTo<T>(this object input)
{
Attempt<object> result = TryConvertTo(input, typeof(T));
if (!result.Success)
{
// Just try a straight up conversion
try
{
var converted = (T)input;
return Attempt<T>.Succeed(converted);
}
catch (Exception e)
{
return Attempt<T>.Fail(e);
}
}
var result = TryConvertTo(input, typeof(T));
if (result.Success)
return Attempt<T>.Succeed((T) result.Result);
// just try to cast
try
{
return Attempt<T>.Succeed((T) input);
}
catch (Exception e)
{
return Attempt<T>.Fail(e);
}
}
return Attempt<T>.Succeed((T)result.Result);
}
/// <summary>
/// Attempts to convert the input object to the output type.
/// </summary>
/// <remarks>This code is an optimized version of the original Umbraco method</remarks>
/// <param name="input">The input.</param>
/// <param name="destinationType">The type to convert to</param>
/// <param name="target">The type to convert to</param>
/// <returns>The <see cref="Attempt{Object}"/></returns>
public static Attempt<object> TryConvertTo(this object input, Type destinationType)
public static Attempt<object> TryConvertTo(this object input, Type target)
{
if (target == null)
{
return Attempt<object>.Fail();
}
try
{
if (destinationType == null)
{
return Attempt<object>.Fail();
}
if (input == null)
{
// Nullable is ok
if (destinationType.IsGenericType && GetCachedGenericNullableType(destinationType) != null)
if (target.IsGenericType && GetCachedGenericNullableType(target) != null)
{
return Attempt<object>.Succeed(null);
}
// Reference types are ok
return Attempt<object>.SucceedIf(!destinationType.IsValueType, null);
return Attempt<object>.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<object>.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<object>.Succeed(null);
}
}
// Recursively call into this method with the inner (not-nullable) type and handle the outcome
Attempt<object> 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<object>.Fail(nonNullable.Exception);
return Attempt<object>.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<object>? 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<T> and source type implementing IEnumerable<T> 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<object>.Fail();
}
}
/// <summary>
/// Attempts to convert the input string to the output type.
/// </summary>
/// <remarks>This code is an optimized version of the original Umbraco method</remarks>
/// <param name="input">The input.</param>
/// <param name="destinationType">The type to convert to</param>
/// <param name="target">The type to convert to</param>
/// <returns>The <see cref="Nullable{Attempt}"/></returns>
private static Attempt<object>? TryConvertToFromString(this string input, Type destinationType)
private static Attempt<object>? TryConvertToFromString(this string input, Type target)
{
// Easy
if (destinationType == typeof(string))
if (target == typeof(string))
{
return Attempt<object>.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<object>.Succeed(false);
}
if (destinationType == typeof(DateTime))
if (target == typeof(DateTime))
{
// null/empty = min DateTime value
return Attempt<object>.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<object>.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<object>.SucceedIf(decimal.TryParse(input2, out decimal value2), Convert.ToInt32(value2));
var input2 = NormalizeNumberDecimalSeparator(input);
return Attempt<object>.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<object>.Succeed(value);
}
// Same as int
string input2 = NormalizeNumberDecimalSeparator(input);
return Attempt<object>.SucceedIf(decimal.TryParse(input2, out decimal value2), Convert.ToInt64(value2));
var input2 = NormalizeNumberDecimalSeparator(input);
return Attempt<object>.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<object>.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<object>.SucceedIf(short.TryParse(input, out short value), value);
return Attempt<object>.SucceedIf(short.TryParse(input, out var value), value);
case TypeCode.Double:
string input2 = NormalizeNumberDecimalSeparator(input);
return Attempt<object>.SucceedIf(double.TryParse(input2, out double valueD), valueD);
var input2 = NormalizeNumberDecimalSeparator(input);
return Attempt<object>.SucceedIf(double.TryParse(input2, out var valueD), valueD);
case TypeCode.Single:
string input3 = NormalizeNumberDecimalSeparator(input);
return Attempt<object>.SucceedIf(float.TryParse(input3, out float valueF), valueF);
var input3 = NormalizeNumberDecimalSeparator(input);
return Attempt<object>.SucceedIf(float.TryParse(input3, out var valueF), valueF);
case TypeCode.Char:
return Attempt<object>.SucceedIf(char.TryParse(input, out char valueC), valueC);
return Attempt<object>.SucceedIf(char.TryParse(input, out var valueC), valueC);
case TypeCode.Byte:
return Attempt<object>.SucceedIf(byte.TryParse(input, out byte valueB), valueB);
return Attempt<object>.SucceedIf(byte.TryParse(input, out var valueB), valueB);
case TypeCode.SByte:
return Attempt<object>.SucceedIf(sbyte.TryParse(input, out sbyte valueSb), valueSb);
return Attempt<object>.SucceedIf(sbyte.TryParse(input, out var valueSb), valueSb);
case TypeCode.UInt32:
return Attempt<object>.SucceedIf(uint.TryParse(input, out uint valueU), valueU);
return Attempt<object>.SucceedIf(uint.TryParse(input, out var valueU), valueU);
case TypeCode.UInt16:
return Attempt<object>.SucceedIf(ushort.TryParse(input, out ushort valueUs), valueUs);
return Attempt<object>.SucceedIf(ushort.TryParse(input, out var valueUs), valueUs);
case TypeCode.UInt64:
return Attempt<object>.SucceedIf(ulong.TryParse(input, out ulong valueUl), valueUl);
return Attempt<object>.SucceedIf(ulong.TryParse(input, out var valueUl), valueUl);
}
}
else if (destinationType == typeof(Guid))
else if (target == typeof(Guid))
{
return Attempt<object>.SucceedIf(Guid.TryParse(input, out Guid value), value);
return Attempt<object>.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<object>.Fail();
}
else if (destinationType == typeof(DateTimeOffset))
else if (target == typeof(DateTimeOffset))
{
return Attempt<object>.SucceedIf(DateTimeOffset.TryParse(input, out DateTimeOffset value), value);
return Attempt<object>.SucceedIf(DateTimeOffset.TryParse(input, out var value), value);
}
else if (destinationType == typeof(TimeSpan))
else if (target == typeof(TimeSpan))
{
return Attempt<object>.SucceedIf(TimeSpan.TryParse(input, out TimeSpan value), value);
return Attempt<object>.SucceedIf(TimeSpan.TryParse(input, out var value), value);
}
else if (destinationType == typeof(decimal))
else if (target == typeof(decimal))
{
string input2 = NormalizeNumberDecimalSeparator(input);
return Attempt<object>.SucceedIf(decimal.TryParse(input2, out decimal value), value);
var input2 = NormalizeNumberDecimalSeparator(input);
return Attempt<object>.SucceedIf(decimal.TryParse(input2, out var value), value);
}
else if (input != null && destinationType == typeof(Version))
else if (input != null && target == typeof(Version))
{
return Attempt<object>.SucceedIf(Version.TryParse(input, out Version value), value);
return Attempt<object>.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;
}
}
}

View File

@@ -1,54 +0,0 @@
using System;
namespace Umbraco.Core
{
/// <summary>
/// A lightweight struct for storing a pair of types for caching.
/// </summary>
internal struct TypePair : IEquatable<TypePair>
{
/// <summary>
/// Initializes a new instance of the <see cref="TypePair"/> struct.
/// </summary>
/// <param name="type1">The first type</param>
/// <param name="type2">The second type</param>
public TypePair(Type type1, Type type2)
{
this.Type1 = type1;
this.Type2 = type2;
}
/// <summary>
/// Gets the first type
/// </summary>
public Type Type1 { get; }
/// <summary>
/// Gets the second type
/// </summary>
public Type Type2 { get; }
/// <inheritdoc/>
public bool Equals(TypePair other)
{
return this.Type1 == other.Type1 && this.Type2 == other.Type2;
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
return (obj is TypePair) && this.Equals((TypePair)obj);
}
/// <inheritdoc/>
public override int GetHashCode()
{
unchecked
{
int hashCode = this.Type1.GetHashCode();
hashCode = (hashCode * 397) ^ this.Type2.GetHashCode();
return hashCode;
}
}
}
}

View File

@@ -1531,7 +1531,7 @@
<Compile Include="ReadLock.cs" />
<Compile Include="TypeFinder.cs" />
<Compile Include="TypeHelper.cs" />
<Compile Include="TypePair.cs" />
<Compile Include="Collections\CompositeTypeTypeKey.cs" />
<Compile Include="Udi.cs" />
<Compile Include="UdiDefinitionAttribute.cs" />
<Compile Include="UdiEntityType.cs" />

View File

@@ -15,7 +15,7 @@ using ILogger = Umbraco.Core.Logging.ILogger;
namespace Umbraco.Tests.Benchmarks
{
[QuickRunWithMemoryDiagnoser]
[QuickRunWithMemoryDiagnoserConfig]
public class BulkInsertBenchmarks
{
private static byte[] _initDbBytes = null;

View File

@@ -8,18 +8,26 @@ namespace Umbraco.Tests.Benchmarks.Config
/// <summary>
/// Configures the benchmark to run with less warmup and a shorter iteration time than the standard benchmark.
/// </summary>
public class QuickRunAttribute : Attribute, IConfigSource
public class QuickRunConfigAttribute : Attribute, IConfigSource
{
public QuickRunAttribute()
/// <summary>
/// Initializes a new instance of the <see cref="QuickRunConfigAttribute"/> class.
/// </summary>
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
}
/// <summary>
/// Gets the manual configuration.
/// </summary>
protected ManualConfig Config { get; }
/// <inheritdoc/>
public IConfig Config { get; }
IConfig IConfigSource.Config => Config;
}
}

View File

@@ -1,17 +0,0 @@
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnosers;
namespace Umbraco.Tests.Benchmarks.Config
{
/// <summary>
/// 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
/// </summary>
public class QuickRunWithMemoryDiagnoserAttribute : QuickRunAttribute
{
public QuickRunWithMemoryDiagnoserAttribute()
{
((ManualConfig)this.Config).Add(new MemoryDiagnoser());
}
}
}

View File

@@ -0,0 +1,20 @@
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnosers;
namespace Umbraco.Tests.Benchmarks.Config
{
/// <summary>
/// Configures the benchmark to run with less warmup and a shorter iteration time than the standard benchmark,
/// and include memory usage diagnosis.
/// </summary>
public class QuickRunWithMemoryDiagnoserConfigAttribute : QuickRunConfigAttribute
{
/// <summary>
/// Initializes a new instance of the <see cref="QuickRunWithMemoryDiagnoserConfigAttribute"/> class.
/// </summary>
public QuickRunWithMemoryDiagnoserConfigAttribute()
{
Config.Add(new MemoryDiagnoser());
}
}
}

View File

@@ -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<string, string> replacements = new Dictionary<string, string>(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<string, string> 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<string, string>
{
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<string, string>();
//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<string, string> item in replacements)
var result = Text;
// ReSharper disable once LoopCanBeConvertedToQuery
foreach (var item in Replacements)
{
result = result.Replace(item.Key, item.Value);
}

View File

@@ -162,8 +162,8 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Config\QuickRunAttribute.cs" />
<Compile Include="Config\QuickRunWithMemoryDiagnoserAttribute.cs" />
<Compile Include="Config\QuickRunConfigAttribute.cs" />
<Compile Include="Config\QuickRunWithMemoryDiagnoserConfigAttribute.cs" />
<Compile Include="LinqCastBenchmarks.cs" />
<Compile Include="ModelToSqlExpressionHelperBenchmarks.cs" />
<Compile Include="BulkInsertBenchmarks.cs" />

View File

@@ -5,7 +5,7 @@ using Umbraco.Tests.Benchmarks.Config;
namespace Umbraco.Tests.Benchmarks
{
[QuickRunWithMemoryDiagnoser]
[QuickRunWithMemoryDiagnoserConfig]
public class XmlBenchmarks
{
[GlobalSetup]

View File

@@ -11,7 +11,7 @@ using Umbraco.Web.PublishedCache.XmlPublishedCache;
namespace Umbraco.Tests.Benchmarks
{
[QuickRunWithMemoryDiagnoser]
[QuickRunWithMemoryDiagnoserConfig]
public class XmlPublishedContentInitBenchmarks
{
public XmlPublishedContentInitBenchmarks()