ReactiveUI
ReactiveUI copied to clipboard
Bindings: Improve perf by using compiled expressions.
Cached compiled expression can significantly improve performance. http://www.palmmedia.de/Blog/2012/2/4/reflection-vs-compiled-expressions-vs-delegates-performance-comparision
Are expression trees supported in Portable Libraries?
This should indeed improve performance, the following performance test uses a memoizingmru cache for caching the property setters:
namespace ReactiveUI.Tests
{
public class PerfTestExporessionTrees
{
[Fact]
public void PerformanceTest()
{
var exprTreeCache = new MemoizingMRUCache<Tuple<Type, string>, Action<object, object>>((x, _) => GenerateSetPropertyActionWithLinqExpression(x.Item1, x.Item2), RxApp.BigCacheLimit);
var reflectionCache =new MemoizingMRUCache<Tuple<Type, string>, Action<object, object>>((x, _) => GenerateSetPropertyActionWithReflection(x.Item1, x.Item2), RxApp.BigCacheLimit);
var target = new TestClassWithProperties();
//warmup the caches
var setterUsingExpresisonTreer = exprTreeCache.Get(Tuple.Create(typeof(TestClassWithProperties), "Property1"));
setterUsingExpresisonTreer(target, "overridden by expressiontree");
Assert.Equal("overridden by expressiontree", target.Property1);
var setterReflection = reflectionCache.Get(Tuple.Create(typeof(TestClassWithProperties), "Property1"));
setterReflection(target, "overridden by reflection");
Assert.Equal("overridden by reflection", target.Property1);
const int iter = 100000;
Action performTest = () => {
var sw = Stopwatch.StartNew();
for (int i = 0; i < iter; i++) {
var setter = reflectionCache.Get(Tuple.Create(typeof(TestClassWithProperties), "Property1"));
setter(target, "overridden");
}
sw.Stop();
Console.WriteLine("REFLECTION total for {0} iterations: {1} ms", iter, sw.ElapsedMilliseconds);
sw.Reset();
sw.Start();
for (int i = 0; i < iter; i++) {
var setter = exprTreeCache.Get(Tuple.Create(typeof(TestClassWithProperties), "Property1"));
setter(target, "overridden");
}
sw.Stop();
Console.WriteLine("EXPRESSION TREE total for {0} iterations: {1} ms", iter, sw.ElapsedMilliseconds);
};
for (int i = 0; i < 20; i++) {
performTest();
}
}
public class TestClassWithProperties
{
public string Property1 { get; set; }
public string Property2 { get; set; }
public TestClassWithProperties()
{
Property1 = "this is property1";
Property2 = "this is property2";
}
}
public static Action<object, object> GenerateSetPropertyActionWithReflection(Type type, string property)
{
var pi = type.GetRuntimeProperties()
.FirstOrDefault(y => !y.IsStatic() && y.Name ==property);
if (pi != null)
{
return ((y, v) => pi.SetValue(y, v, null));
}
return null;
}
public static Action<object, object> GenerateSetPropertyActionWithLinqExpression(Type type, string property)
{
var propertyInfo = type.GetProperty(property);
var propertyType = propertyInfo.PropertyType;
var instanceParameter = Expression.Parameter(typeof(object), "instance");
var valueParameter = Expression.Parameter(typeof(object), "value");
var lambda =
Expression.Lambda<Action<object, object>>(
Expression.Assign(
Expression.Property(
Expression.Convert(
instanceParameter,
type),
propertyInfo),
Expression.Convert(valueParameter,
propertyType)),
instanceParameter,
valueParameter);
return lambda.Compile();
}
}
On my machine, this yields the following results:
REFLECTION total for 100000 iterations: 82 ms
EXPRESSION TREE total for 100000 iterations: 47 ms
REFLECTION total for 100000 iterations: 95 ms
EXPRESSION TREE total for 100000 iterations: 48 ms
REFLECTION total for 100000 iterations: 90 ms
EXPRESSION TREE total for 100000 iterations: 46 ms
REFLECTION total for 100000 iterations: 75 ms
EXPRESSION TREE total for 100000 iterations: 42 ms
REFLECTION total for 100000 iterations: 76 ms
EXPRESSION TREE total for 100000 iterations: 42 ms
REFLECTION total for 100000 iterations: 78 ms
EXPRESSION TREE total for 100000 iterations: 42 ms
REFLECTION total for 100000 iterations: 78 ms
EXPRESSION TREE total for 100000 iterations: 43 ms
REFLECTION total for 100000 iterations: 78 ms
EXPRESSION TREE total for 100000 iterations: 44 ms
REFLECTION total for 100000 iterations: 74 ms
EXPRESSION TREE total for 100000 iterations: 41 ms
REFLECTION total for 100000 iterations: 76 ms
EXPRESSION TREE total for 100000 iterations: 44 ms
REFLECTION total for 100000 iterations: 76 ms
EXPRESSION TREE total for 100000 iterations: 42 ms
REFLECTION total for 100000 iterations: 76 ms
EXPRESSION TREE total for 100000 iterations: 42 ms
REFLECTION total for 100000 iterations: 76 ms
EXPRESSION TREE total for 100000 iterations: 42 ms
REFLECTION total for 100000 iterations: 77 ms
EXPRESSION TREE total for 100000 iterations: 42 ms
REFLECTION total for 100000 iterations: 75 ms
EXPRESSION TREE total for 100000 iterations: 42 ms
REFLECTION total for 100000 iterations: 75 ms
EXPRESSION TREE total for 100000 iterations: 41 ms
REFLECTION total for 100000 iterations: 77 ms
EXPRESSION TREE total for 100000 iterations: 41 ms
REFLECTION total for 100000 iterations: 76 ms
EXPRESSION TREE total for 100000 iterations: 42 ms
REFLECTION total for 100000 iterations: 77 ms
EXPRESSION TREE total for 100000 iterations: 42 ms
REFLECTION total for 100000 iterations: 76 ms
EXPRESSION TREE total for 100000 iterations: 42 ms
Can't compile expressions on iOS, need a fallback at least
Alright, so this has to be flexible enough to work for iOS.
Following on from a Slack discussion today, it would be awesome if we could do this at compile time via Roslyn compile-time hooks. Alas, they do not support that yet, but when they do we will be able to pull off some binding infrastructure that is super fast and platform agnostic.
What's the current state of this?
It's in progress at the moment in our incubator project.
Is there any way to get some insights on the current state of affairs?