Skip to content

Commit

Permalink
Use cached delegates to set property values rather than SetValue (#170)
Browse files Browse the repository at this point in the history
* Use cached delegates to set property values rather than SetValue

* Minor refactoring. Added previous code comments.
  • Loading branch information
Mpdreamz authored and bchavez committed Aug 29, 2018
1 parent 59ae9f3 commit 04efeb0
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 27 deletions.
74 changes: 74 additions & 0 deletions Source/Benchmark/BenchGenerate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.Linq;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Attributes.Exporters;
using Bogus;

namespace Benchmark
{
[MarkdownExporter, MemoryDiagnoser]
public class BenchGenerate
{
public class Project
{
public long Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}

private static Faker<Project> FakerDefault { get; set; }
private static Faker<Project> FakerCustom { get; set; }
private static Faker<Project> FakerWithRules { get; set; }
private static Faker<Project> FakerWithRulesComplex { get; set; }

[GlobalSetup]
public void Setup()
{
FakerDefault = new Faker<Project>().UseSeed(1337);
FakerCustom = new Faker<Project>()
.CustomInstantiator(f=> new Project())
.UseSeed(1337);
FakerWithRules = new Faker<Project>()
.CustomInstantiator(f=> new Project())
.RuleFor(p=>p.Id, f => f.IndexGlobal)
.UseSeed(1337);
FakerWithRulesComplex = new Faker<Project>()
.CustomInstantiator(f=> new Project())
.RuleFor(p=>p.Id, f => f.IndexGlobal)
.RuleFor(p => p.Name, f => f.Person.Company.Name + f.UniqueIndex.ToString())
.RuleFor(p => p.Description, f => f.Lorem.Paragraphs(3))
.UseSeed(1337);
}

// [Benchmark]
public void Generate_Default()
{
var projects = FakerDefault.Generate(10_000).ToList();
}

// [Benchmark]
public void Generate_CustomInstantiator()
{
var projects = FakerDefault.Generate(10_000).ToList();
}

[Benchmark]
public void Generate_WithRules()
{
var projects = FakerWithRules.Generate(10_000).ToList();
}

//[Benchmark]
public void Generate_WithRulesComplex()
{
var projects = FakerWithRulesComplex.Generate(10_000).ToList();
}

// [Benchmark]
public void Constructor()
{
var projects = Enumerable.Range(0, 10_000).Select(i => new Project {Id = i}).ToList();
}
}
}
16 changes: 8 additions & 8 deletions Source/Benchmark/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

namespace Benchmark
{
class Program
{
static void Main()
{
BenchmarkRunner.Run<BenchStringFill>();
}
}
}
class Program
{
static void Main()
{
BenchmarkRunner.Run<BenchGenerate>();
}
}
}
36 changes: 36 additions & 0 deletions Source/Bogus/Extensions/ExtensionsForPropertyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using System.Reflection;

namespace Bogus.Extensions
{
public static class ExtensionsForPropertyInfo
{
private static readonly MethodInfo GenericSetterCreationMethod =
typeof(ExtensionsForPropertyInfo).GetMethod(nameof(CreateSetterGeneric), BindingFlags.Static | BindingFlags.NonPublic);

public static Action<T, object> CreateSetter<T>(this PropertyInfo property)
{
if (property == null) throw new ArgumentNullException(nameof(property));

var setter = property.GetSetMethod();
if (setter == null) throw new ArgumentException("The specified property does not have a public setter.");

var genericHelper = GenericSetterCreationMethod.MakeGenericMethod(property.DeclaringType, property.PropertyType);
return (Action<T, object>)genericHelper.Invoke(null, new object[] { setter });
}

private static Action<T, object> CreateSetterGeneric<T, V>(MethodInfo setter) where T : class
{
var setterTypedDelegate =
#if STANDARD
(Action<T, V>) setter.CreateDelegate(typeof(Action<T, V>))
#else
(Action<T, V>) Delegate.CreateDelegate(typeof(Action<T, V>), setter)
#endif
;
var setterDelegate = (Action<T, object>)((T instance, object value) => { setterTypedDelegate(instance, (V)value); });
return setterDelegate;
}

}
}
61 changes: 42 additions & 19 deletions Source/Bogus/Faker[T].cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using Bogus.Extensions;

namespace Bogus
{
Expand Down Expand Up @@ -53,6 +54,8 @@ public class Faker<T> : IFakerTInternal, ILocaleAware, IRuleSet<T> where T : cla
protected internal readonly Dictionary<string, FinalizeAction<T>> FinalizeActions = new Dictionary<string, FinalizeAction<T>>(StringComparer.OrdinalIgnoreCase);
protected internal Dictionary<string, Func<Faker, T>> CreateActions = new Dictionary<string, Func<Faker, T>>(StringComparer.OrdinalIgnoreCase);
protected internal readonly Dictionary<string, MemberInfo> TypeProperties;
protected internal readonly Dictionary<string, Action<T, object>> SetterCache = new Dictionary<string, Action<T, object>>(StringComparer.OrdinalIgnoreCase);

protected internal Dictionary<string, bool> StrictModes = new Dictionary<string, bool>();
protected internal bool? IsValid;
protected internal string currentRuleSet = Default;
Expand Down Expand Up @@ -526,8 +529,6 @@ protected virtual void PopulateInternal(T instance, string[] ruleSets)
throw MakeValidationException(vr ?? ValidateInternal(ruleSets));
}

var typeProps = TypeProperties;

lock( Randomizer.Locker.Value )
{
//Issue 57 - Make sure you generate a new context
Expand All @@ -543,23 +544,7 @@ protected virtual void PopulateInternal(T instance, string[] ruleSets)
{
foreach( var action in populateActions.Values )
{
typeProps.TryGetValue(action.PropertyName, out MemberInfo member);
var valueFactory = action.Action;
if( valueFactory is null ) continue; // An .Ignore() rule.

if( member != null )
{
var prop = member as PropertyInfo;
prop?.SetValue(instance, valueFactory(FakerHub, instance), null);

var field = member as FieldInfo;
field?.SetValue(instance, valueFactory(FakerHub, instance));
}
else // member would be null if this was an RuleForObject.
{
//Invoke this if this is a basic rule which does not select a property or a field.
var outputValue = valueFactory(FakerHub, instance);
}
PopulateProperty(instance, action);
}
}
}
Expand All @@ -574,6 +559,44 @@ protected virtual void PopulateInternal(T instance, string[] ruleSets)
}
}

private readonly object _setterCreateLock = new object();
private void PopulateProperty(T instance, PopulateAction<T> action)
{
var valueFactory = action.Action;
if (valueFactory is null) return; // An .Ignore() rule.

var value = valueFactory(FakerHub, instance);

if (SetterCache.TryGetValue(action.PropertyName, out var setter))
{
setter(instance, value);
return;
}

if (!TypeProperties.TryGetValue(action.PropertyName, out var member)) return;
if (member == null) return; // Member would be null if this was a .Rules()
// The valueFactory is already invoked
// which does not select a property or field.

lock (_setterCreateLock)
{
if (SetterCache.TryGetValue(action.PropertyName, out setter))
{
setter(instance, value);
return;
}

if (member is PropertyInfo prop)
setter = prop.CreateSetter<T>();
// TODO FieldInfo will need to rely on ILEmit to create a delegate
else if (member is FieldInfo field)
setter = (i, v) => field?.SetValue(i, v);
if (setter == null) return;

SetterCache.Add(action.PropertyName, setter);
setter(instance, value);
}
}
/// <summary>
/// When <seealso cref="StrictMode"/> is enabled, checks if all properties or fields of <typeparamref name="T"/> have
/// rules defined. Returns true if all rules are defined, false otherwise.
Expand Down

0 comments on commit 04efeb0

Please sign in to comment.