ReactiveUI icon indicating copy to clipboard operation
ReactiveUI copied to clipboard

Bindings: Improve perf by using compiled expressions.

Open jlaanstra opened this issue 10 years ago • 7 comments

Cached compiled expression can significantly improve performance. http://www.palmmedia.de/Blog/2012/2/4/reflection-vs-compiled-expressions-vs-delegates-performance-comparision

jlaanstra avatar May 22 '14 10:05 jlaanstra

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

rikbosch avatar May 22 '14 11:05 rikbosch

Can't compile expressions on iOS, need a fallback at least

anaisbetts avatar May 22 '14 16:05 anaisbetts

Alright, so this has to be flexible enough to work for iOS.

jlaanstra avatar May 22 '14 17:05 jlaanstra

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.

kentcb avatar Jan 11 '16 09:01 kentcb

What's the current state of this?

Kavignon avatar Aug 27 '19 14:08 Kavignon

It's in progress at the moment in our incubator project.

glennawatson avatar Aug 27 '19 14:08 glennawatson

Is there any way to get some insights on the current state of affairs?

Kavignon avatar Aug 27 '19 14:08 Kavignon