This project has moved. For the latest updates, please go here.

Aggregate Invoker

Jul 19, 2010 at 8:10 PM

Hi,

 

I had previously written my own DynamicMethod emitter, but I like the way yours is structured and didn't want to be burdened with maintaining my own anymore, so I integrated fasterflect into my project in place of my own emitter.

 

The code is much clearer and more succinct, and I'm happy for that, but it's also about half the speed. In doing profiling, the specific reason is that my own emitter was executing operations on multiple MethodInfos and FieldInfos in a singleDynamicMethod. In other words, 1 DynamicMethod for multiple operations. This might not seem like a big difference, but having 10,000 DynamicMethod invocations executing 10 steps each vs. 100,000 invocations executing 1 step each really seems to make a big performance difference.

 

I'm thinking of adding this functionality to Fasterflect. Could you advise on the most appropriate strategy for doing so?

 

Thanks!

Coordinator
Jul 19, 2010 at 10:36 PM

First, welcome and we're glad you like the project :-)

We're already doing this for the Map extensions (implementation in Extensions/Services/MapExtensions.cs and Emitter/MapEmitter.cs), which allow you to copy fields and/or properties between types using a single delegate. To set multiple properties you could simply create an anonymous type with the desired properties and map that to an existing object.

Also note that Fasterflect allows you to work with the generated delegated directly. This avoids the overhead of Fasterflect having to locate the delegate in its cache, which really is what eats most of the time for any subsequent uses of the same delegate. See this for details.

If none of the above are what you were looking for, it'd be helpful with a quick example of the scenario(s) you're trying to solve. If it's not something that Fasterflect already supports then contributions to making it so are obviously more than welcome :-)

Jul 20, 2010 at 3:30 AM
Edited Jul 20, 2010 at 4:14 AM

Thanks for the quick reply. I actually already am hanging on directly to the generated delegate instance.

Let me clarify a little bit more...

Previously, I had an array of MethodInfos that I needed to invoke dynamically, and I did so like this:

object[] results = MyDynamicMethodDelegate(myMethodTargets /* object[] */, myMethodInfos /* MethodInfo[] */, myMethodParams /* object[][] each entry is an array of parameters for one of myMethodInfo */);

Across about 10,000 invocations, this took about 200ms (I know the number is arbitrary without context)..now, I'm doing:

List<object> results = new List<object>();
foreach(var o in operations){ /* operations.Count == 10 */
  MethodInvoker invoker=o.MyInvoker; /* this is an instance of a MethodInvoker that I generate once upfront for a MethodInfo */
  object result = invoker(o.Target, o.Params);
  results.Add(result);
}

This takes about 420ms (time sample does not include the upfront time for generating the MethodInvoker the first time) for the same 10,000 operations (times ten items in each operations collection). I have run the built in .NET performance profiler and I see that a ton of time is taken up inside MyInvoker...but I can't tell on what exactly. It's ExclusiveSamples, not inclusive, so the time taken is actually in the DynamicMethod itself, not in the MethodInfo it calls....so I'm assuming this is the overhead in making many DynamicMethod calls, instead of just one...but I guess I can't be sure.

Any thoughts?

Thanks.

P.S. I guess to clarify on what I'm doing here - this is a fast dynamic method based, DataContract style serialization component. I'm sure a bunch of people have written similar stuff, but I couldn't find one that was SL compatible, fast, supported type to type mapping, surrogates, multiple output formats (binary, JSON, XML), and high object compatibility (anonymous types, non-serializable types, etc). The reason I have a list of MethodInfo I need to invoke is that each "SerializationMember" (aka DataMember) in a "SerializationContract" (aka DataContract) can refer to an surrogate MethodInfo for serialization and deserialization (it doesn't have to be properties or fields). Specifically, I need this support so as to support lightweight WSDL generated objects on a SL client that serialize/deserialize to heavy-weight LLBLGen (an ORM) objects on a WCF server, where there isn't a direct property to property mapping)....sorry if that's a bit of a mouthful.

Coordinator
Jul 21, 2010 at 3:13 PM

lol, that is indeed a bit of a mouthful, but it makes perfect sense nonetheless :-)

It sounds like an awful lot - 220ms overhead for just 10000 invocations - but if you pass a lot of parameters for every method call I guess it's conceivable. I'd try downloading the JetBrains dotTrace 4.0 profiler, as I've found it to be much better at locating hot-spots than the built-in tools (although I'm no profiling expert). I particularly like that they can also profile the framework code, so you can drill into every line of code you've got. They have a trial so trying it out should be free :)

Assuming that nothing weird is going on feel free to go ahead with the customized emitter. Given the complexity of what you're trying to do it might be worthwhile to look at the Expression classes for code generation rather than emitting IL directly, as the latter is a pain to troubleshoot. This will make the code .NET 4.0 only, but that's OK for something that is a bit specialized, but only works if you don't need to target earlier versions. Have a look here for more info on this.

As for having your changes included in Fasterflect - if it is something that others might also find useful the only requirements are that the code is working, well documented (code and wiki) and covered by unit tests. I'd suggest that you add a file in the Extensions/Services folder to hold your extension methods, and supporting files where they seem most appropriate. Also, try to use names (methods and parameters) that are consistent with the rest of the framework.

Don't hesitate to ask for feedback, help, suggestions or anything else as you start hacking away at the problem :)

Jul 22, 2010 at 2:35 PM
Edited Jul 23, 2010 at 12:36 AM

We think alike...I was thinking about Expressions a few days ago too and it's already perfectly possible to do most of what I want in 3.5: http://kohari.org/2009/03/06/fast-late-bound-invocation-with-expression-trees/

 

However, when you say I need 4.0 (which unfortunately isn't an option right now), I assume you're talking about the two pieces of functionality which I can't do in 3.5:

1. Block expressions to create multi-statement expressions (for multiple methodinfo calls).

2. Set fields with 3.5 expression trees.

Is that what you're referring to? I did try replacing MethodInvokers with compiled lambda's a couple of days ago and didn't notice a big performance increase (which I wouldn't expect)...only down to about 400ms from 420ms. I'll try dotTrace and let you know what I find. Thanks!

Jul 22, 2010 at 4:01 PM
Well...technically the DLR is 3.5 compatible ala the Microsoft.Scripting dlls...well let's see where this goes......
Jul 22, 2010 at 8:07 PM
Edited Jul 23, 2010 at 1:18 AM

Wow...success!

I'm down to 151.263157894737ms, which is even less than when I had my own aggregate DynamicMethod!

I'm using the 2.0 compatible DLR from Codeplex I mentioned above.

I know this probably doesn't mean much out of context, but I've pasted what I'm doing below. It's pretty clean too, compared to my original DynamicMehtod emitter...

    /// <summary>
    /// Specifies a member to serialize or a method to use to get/set data for that member
    /// </summary>
    public class SerializationMember
    {
.
.
.
.
        /// <summary>
        /// Gets the getter expression as determined by the GetField/GetMethod/GetMethodTarget.
        /// </summary>
        public Expression GetGetter(ParameterExpression targetParameter)
        {

            if (GetField != null)
            {
                var fieldAccess = Expression.Field(Expression.Convert(targetParameter, GetField.DeclaringType), GetField);

                return fieldAccess;
            }
            else if (GetMethod != null)
            {

                Expression methodTarget = null;

                List<Expression> callParameters = new List<Expression>();

                if (GetMethod.IsStatic)
                {
                    callParameters.Add(Expression.Convert(targetParameter, GetMethod.Parameters().First().ParameterType));

                    if (GetMethod.Parameters().Last().ParameterType == typeof(SerializationMember))
                    {
                        callParameters.Add(Expression.Constant(this));
                    }
                }
                else if (GetMethodTarget != null)
                {
                    methodTarget = Expression.Convert(Expression.Constant(GetMethodTarget), GetMethod.DeclaringType);

                    callParameters.Add(Expression.Convert(targetParameter, GetMethod.Parameters().First().ParameterType));

                    if (GetMethod.Parameters().Last().ParameterType == typeof(SerializationMember))
                    {
                        callParameters.Add(Expression.Constant(this));
                    }
                }
                else
                {
                    methodTarget = Expression.Convert(targetParameter, GetMethod.DeclaringType);
                    if (GetMethod.Parameters().Select(p => p.ParameterType).LastOrDefault() == typeof(SerializationMember))
                    {
                        callParameters.Add(Expression.Convert(targetParameter, GetMethod.Parameters().First().ParameterType));
                    }
                }

                var call = Expression.Call(methodTarget, GetMethod, callParameters.ToArray());

                return call;
            }

            throw new InvalidOperationException("Could not resolve member value getter.");
        }
.
.
.
    }
        /// <summary>
        /// Gets the writer to write the members of the contract. Calling writer should invoke action(writer,value).
        /// </summary>
        /// <value>The member writers.</value>
        public SerializationContractDataWriterInvoker MembersWriter
        {
            get
            {
                if (_membersWriter == null)
                {
                    if (Contract.Members.Length > 0)
                    {
                        ParameterExpression writerParameter = Expression.Parameter(typeof(object), "writer");
                        ParameterExpression valueParameter = Expression.Parameter(typeof(object), "value");

                        List<Expression> writes = new List<Expression>();
                        foreach (var member in Contract.Members)
                        {
                            var getMemberValue = member.GetGetter(valueParameter);
                            var writeCall = GetWriteMemberCall(member, writerParameter, getMemberValue);

                            writes.Add(writeCall);
                        }
                        var block = Expression.Block(writes.ToArray());

                        var lambda = Expression.Lambda<SerializationContractDataWriterInvoker>(block, writerParameter, valueParameter);

                        _membersWriter = lambda.Compile();
                    }
                    else
                    {
                        // no members to write
                        _membersWriter = (w, v) => { };
                    }

                }
                return _membersWriter;
            }
        }

        private MethodCallExpression GetWriteMemberCall(SerializationMember member, ParameterExpression writerParameter, Expression memberValueArgument)
        {
            MethodInfo writeMethod = Provider.ResolveWriteMethod(member.Type);

            var callArguments = new List<Expression>();

            callArguments.Add(Expression.Convert(memberValueArgument, writeMethod.Parameters().First().ParameterType));

            if (writeMethod.Parameters().Select(p => p.ParameterType).LastOrDefault() == typeof(SerializationMember))
            {
                callArguments.Add(Expression.Constant(member));
            }

            MethodCallExpression writeCall = Expression.Call(Expression.Convert(writerParameter, writeMethod.DeclaringType), writeMethod, callArguments.ToArray());

            return writeCall;
        }
     
Coordinator
Aug 12, 2010 at 11:17 AM
Haven't heard from you in a while.. did you finish your work on this? Do you have something that we should include in Fasterflect? The above certainly looks useful, but incomplete as it is posted here...
Aug 19, 2010 at 5:26 AM
Edited Aug 19, 2010 at 5:29 AM

Thanks for the follow up. This actually worked out superbly with the DLR in the general fashion above, by building a big block expression which invokes all the "member getters" and all the "output stream writes" in one compiled lambda invocation. It's faster (and cleaner) than I thought it could possibly be. Unfortunately, not something that would fit in with the Fasterflect framework, as it's more of a DLR/Expression Tree dependent venture now (which is even neater since this is all running on the 3.5 CLR). Thanks again for your assistance and great job on the Fasterflect framework all the same!