Skip to content

WIP - Use Lambda Expressions for field/property accessor when UseReflectionOptimizer enabled #1942

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions src/NHibernate.Test/PropertyTest/AccessorPerformanceFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
using System;
using System.Diagnostics;
using NHibernate.Properties;
using NUnit.Framework;

namespace NHibernate.Test.PropertyTest
{
public abstract class AccessorPerformanceFixture<T> where T : new()
{
private IPropertyAccessor _accessor;
private ISetter _reflectionSetter;
private ISetter _ilSetter;
private IGetter _reflectionGetter;
private IGetter _ilGetter;

protected abstract string AccessorType { get; }

protected abstract string Path { get; }

protected abstract object GetValue();

[SetUp]
public void SetUp()
{
_accessor = PropertyAccessorFactory.GetPropertyAccessor(AccessorType);
var optimize = Cfg.Environment.UseReflectionOptimizer;

try
{
Cfg.Environment.UseReflectionOptimizer = false;
_reflectionSetter = _accessor.GetSetter(typeof(T), Path);
_reflectionGetter = _accessor.GetGetter(typeof(T), Path);

Cfg.Environment.UseReflectionOptimizer = true;
_ilSetter = _accessor.GetSetter(typeof(T), Path);
_ilGetter = _accessor.GetGetter(typeof(T), Path);
}
finally
{
Cfg.Environment.UseReflectionOptimizer = optimize;
}
}

[TestCase(50000)]
[TestCase(100000)]
[TestCase(200000)]
[TestCase(500000)]
public void TestGet(int iter)
{
var target = new T();
var stopwatch = new Stopwatch();

// Warm up
TestGetter(_reflectionGetter, target, 100);
TestGetter(_ilGetter, target, 100);

stopwatch.Restart();
TestGetter(_reflectionGetter, target, iter);
stopwatch.Stop();
Console.WriteLine($"Reflection getter total time for {iter} iterations: {stopwatch.ElapsedMilliseconds}ms");

stopwatch.Restart();
TestGetter(_ilGetter, target, iter);
stopwatch.Stop();
Console.WriteLine($"IL getter total time for {iter} iterations: {stopwatch.ElapsedMilliseconds}ms");
}

[TestCase(50000)]
[TestCase(100000)]
[TestCase(200000)]
[TestCase(500000)]
public void TestSet(int iter)
{
var target = new T();
var stopwatch = new Stopwatch();

// Warm up
TestSetter(_reflectionSetter, target, 100);
TestSetter(_ilSetter, target, 100);

stopwatch.Restart();
TestSetter(_reflectionSetter, target, iter);
stopwatch.Stop();
Console.WriteLine($"Reflection setter total time for {iter} iterations: {stopwatch.ElapsedMilliseconds}ms");

stopwatch.Restart();
TestSetter(_ilSetter, target, iter);
stopwatch.Stop();
Console.WriteLine($"IL setter total time for {iter} iterations: {stopwatch.ElapsedMilliseconds}ms");
}

private static void TestGetter(IGetter getter, object target, int iter)
{
for (var i = 0; i < iter; i++)
{
var val = getter.Get(target);
}
}

private void TestSetter(ISetter setter, object target, int iter)
{
for (var i = 0; i < iter; i++)
{
setter.Set(target, GetValue());
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using NUnit.Framework;

namespace NHibernate.Test.PropertyTest
{
[TestFixture, Explicit]
public class BasicPropertyAccessorPerformanceFixture : AccessorPerformanceFixture<BasicPropertyAccessorPerformanceFixture.A>
{
protected override string AccessorType => "property";

protected override string Path => "Id";

protected override object GetValue()
{
return 5;
}

public class A
{
public int Id { get; set; }
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using NUnit.Framework;

namespace NHibernate.Test.PropertyTest
{
[TestFixture, Explicit]
public class FieldAccessorPerformanceFixture : AccessorPerformanceFixture<FieldAccessorPerformanceFixture.A>
{
protected override string AccessorType => "field";

protected override string Path => "_id";

protected override object GetValue()
{
return 10;
}

public class A
{
private int _id = 5;

public int Id => _id;
}
}
}
122 changes: 104 additions & 18 deletions src/NHibernate/Properties/BasicPropertyAccessor.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System;
using System.Collections;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.Serialization;
using NHibernate.Engine;

namespace NHibernate.Properties
Expand Down Expand Up @@ -159,11 +161,13 @@ internal static BasicSetter GetSetterOrNull(System.Type type, string propertyNam
/// An <see cref="IGetter"/> for a Property <c>get</c>.
/// </summary>
[Serializable]
public sealed class BasicGetter : IGetter, IOptimizableGetter
public sealed class BasicGetter : IGetter, IOptimizableGetter, IDeserializationCallback
{
private readonly System.Type clazz;
private readonly PropertyInfo property;
private readonly string propertyName;
[NonSerialized]
private Lazy<Func<object, object>> _getDelegate;

/// <summary>
/// Initializes a new instance of <see cref="BasicGetter"/>.
Expand All @@ -176,6 +180,8 @@ public BasicGetter(System.Type clazz, PropertyInfo property, string propertyName
this.clazz = clazz;
this.property = property;
this.propertyName = propertyName;

SetupDelegate();
}

public PropertyInfo Property
Expand All @@ -196,7 +202,9 @@ public object Get(object target)
{
try
{
return property.GetValue(target, Array.Empty<object>());
return _getDelegate == null
? property.GetValue(target, Array.Empty<object>())
: _getDelegate.Value(target);
}
catch (Exception e)
{
Expand Down Expand Up @@ -249,17 +257,45 @@ public void Emit(ILGenerator il)
}
il.EmitCall(OpCodes.Callvirt, method, null);
}

public void OnDeserialization(object sender)
{
SetupDelegate();
}

private void SetupDelegate()
{
if (!Cfg.Environment.UseReflectionOptimizer)
{
return;
}

_getDelegate = new Lazy<Func<object, object>>(CreateDelegate);
}

private Func<object, object> CreateDelegate()
{
var targetParameter = Expression.Parameter(typeof(object), "t");
return Expression.Lambda<Func<object, object>>(
Expression.Convert(
Expression.Call(Expression.Convert(targetParameter, clazz), Method),
typeof(object)),
targetParameter)
.Compile();
}
}

/// <summary>
/// An <see cref="ISetter"/> for a Property <c>set</c>.
/// </summary>
[Serializable]
public sealed class BasicSetter : ISetter, IOptimizableSetter
public sealed class BasicSetter : ISetter, IOptimizableSetter, IDeserializationCallback
{
private readonly System.Type clazz;
private readonly PropertyInfo property;
private readonly string propertyName;
[NonSerialized]
private Lazy<Action<object, object>> _setDelegate;

/// <summary>
/// Initializes a new instance of <see cref="BasicSetter"/>.
Expand All @@ -272,6 +308,8 @@ public BasicSetter(System.Type clazz, PropertyInfo property, string propertyName
this.clazz = clazz;
this.property = property;
this.propertyName = propertyName;

SetupDelegate();
}

public PropertyInfo Property
Expand All @@ -293,27 +331,23 @@ public void Set(object target, object value)
{
try
{
property.SetValue(target, value, Array.Empty<object>());
}
catch (ArgumentException ae)
{
// if I'm reading the msdn docs correctly this is the only reason the ArgumentException
// would be thrown, but it doesn't hurt to make sure.
if (property.PropertyType.IsInstanceOfType(value) == false)
if (_setDelegate == null)
{
// add some details to the error message - there have been a few forum posts an they are
// all related to an ISet and IDictionary mixups.
string msg =
String.Format("The type {0} can not be assigned to a property of type {1}", value.GetType(),
property.PropertyType);
throw new PropertyAccessException(ae, msg, true, clazz, propertyName);
property.SetValue(target, value, Array.Empty<object>());
}
else
{
throw new PropertyAccessException(ae, "ArgumentException while setting the property value by reflection", true,
clazz, propertyName);
_setDelegate.Value(target, value);
}
}
catch (InvalidCastException ce)
{
HandleException(ce, value);
}
catch (ArgumentException ae)
{
HandleException(ae, value);
}
catch (Exception e)
{
throw new PropertyAccessException(e, "could not set a property value by reflection", true, clazz, propertyName);
Expand Down Expand Up @@ -354,6 +388,58 @@ public void Emit(ILGenerator il)
}
il.EmitCall(OpCodes.Callvirt, method, null);
}

public void OnDeserialization(object sender)
{
SetupDelegate();
}

private void SetupDelegate()
{
if (!Cfg.Environment.UseReflectionOptimizer)
{
return;
}

_setDelegate = new Lazy<Action<object, object>>(CreateDelegate);
}

private void HandleException(Exception e, object value)
{
// if I'm reading the msdn docs correctly this is the only reason the ArgumentException
// would be thrown, but it doesn't hurt to make sure.
if (property.PropertyType.IsInstanceOfType(value) == false)
{
// add some details to the error message - there have been a few forum posts an they are
// all related to an ISet and IDictionary mixups.
string msg =
String.Format("The type {0} can not be assigned to a property of type {1}", value.GetType(),
property.PropertyType);
throw new PropertyAccessException(e, msg, true, clazz, propertyName);
}

if (_setDelegate == null)
{
throw new PropertyAccessException(e, "ArgumentException while setting the property value by reflection", true,
clazz, propertyName);
}

throw new PropertyAccessException(e, "could not set a property value by reflection", true, clazz, propertyName);
}

private Action<object, object> CreateDelegate()
{
var targetParameter = Expression.Parameter(typeof(object), "t");
var valueParameter = Expression.Parameter(typeof(object), "p");
return Expression.Lambda<Action<object, object>>(
Expression.Call(
Expression.Convert(targetParameter, clazz),
Method,
Expression.Convert(valueParameter, property.PropertyType)),
targetParameter,
valueParameter)
.Compile();
}
}
}
}
Loading