Ahmed ilyas
Ahmed ilyas

Reputation: 5822

Improving performance by caching or delegate call?

I am trying to improve performance in the code below and kinda know how but not sure which is the best approach. The first hit will take longer but subsequent hits should be quicker. Now, I could cache T (where T is a class) and then check the cache to see if "T" exists, if so - go ahead and get its related information (NamedArguments) and go through each of the NamedArguments and finally if the criteria matches, go ahead and set the value of the current property.

I just want to make it more efficient and performant. Any ideas?

var myProps = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static).Where(prop => Attribute.IsDefined(prop, typeof(MyCustomAttribute)) && prop.CanWrite && prop.GetSetMethod() != null);

foreach (var currentProperty in myProps)
{
    foreach (var currentAttributeForProperty in currentProperty.GetCustomAttributesData())
    {
        foreach (var currentNamedArgument in currentAttributeForProperty.NamedArguments)
        {
            if (string.Equals(currentNamedArgument.MemberInfo.Name, "PropName", StringComparison.OrdinalIgnoreCase))
            {
                currentAttribParamValue = currentNamedArgument.TypedValue.Value == null ? null : currentNamedArgument.TypedValue.Value.ToString();

                // read the reader for the currentAttribute value
                if (reader.DoesFieldExist(currentAttribParamValue))
                {
                    var dbRecordValue = reader[currentAttribParamValue] == DBNull.Value ? null : reader[currentAttribParamValue];

                    // set it in the property
                    currentProperty.SetValue(val, dbRecordValue, null);
                }
                break;
            }
        }
    }
}

Upvotes: 1

Views: 722

Answers (4)

Tim M.
Tim M.

Reputation: 54359

DynamicMethods or ExpressionTrees will be much* faster than reflection. You could build a cache of all property getter/setters for a type, and then cache that information in a Dictionary (or ConcurrentDictionary) with type as the key.

Flow

  • Discover type information (e.g. on app startup).
  • Compile dynamic methods for each property (do all properties at once).
  • Store those methods in a metadata class (example follows).
  • Cache the metadata somewhere (even a static field is fine, as long as access is synchronized). Use the type as the key.
  • Get the metadata for the type when needed.
  • Find the appropriate getter/setter.
  • Invoke, passing the instance on which you wish to act.

// Metadata for a type
public sealed class TypeMetadata<T> {

    // The compiled getters for the type; the property name is the key
    public Dictionary<string, Func<T, object>> Getters {
        get;
        set;
    }

    // The compiled setters for the type; the property name is the key
    public Dictionary<string, Action<T, object>> Setters {
        get;
        set;
    }
}

// rough invocation flow
var type = typeof( T);
var metadata = _cache[type];

var propertyName = "MyProperty";
var setter = metadata[propertyName];

var instance = new T();
var value = 12345;
setter( instance, value );

Example Setter

Excerpted from Dynamic Method Implementation (good article on the subject).

I can't vouch that this exact code works, but I've written very similar code myself. If you aren't comfortable with IL, definitely consider an expression tree instead.

public static LateBoundPropertySet CreateSet(PropertyInfo property)
{
    var method = new DynamicMethod("Set" + property.Name, null, new[] { typeof(object), typeof(object) }, true);
    var gen = method.GetILGenerator();

    var sourceType = property.DeclaringType;
    var setter = property.GetSetMethod(true);

    gen.Emit(OpCodes.Ldarg_0); // Load input to stack
    gen.Emit(OpCodes.Castclass, sourceType); // Cast to source type
    gen.Emit(OpCodes.Ldarg_1); // Load value to stack
    gen.Emit(OpCodes.Unbox_Any, property.PropertyType); // Unbox the value to its proper value type
    gen.Emit(OpCodes.Callvirt, setter); // Call the setter method
    gen.Emit(OpCodes.Ret);

    var result = (LateBoundPropertySet)method.CreateDelegate(typeof(LateBoundPropertySet));

    return result;
}

*25-100x faster in my experience

Upvotes: 4

Honza Brestan
Honza Brestan

Reputation: 10947

Reflection is notoriously slow in loops, so some kind of caching would probably help. But to decide what to cache, you should measure. As the famous saying goes: "premature optimization is the root of all evil"; you should make sure that you really need to optimize and what exactly to optimize.

For a more concrete advice, attributes are attached at compile time, so you could cache a type and a list of its propertyInfos for example.

Upvotes: 2

Sasha
Sasha

Reputation: 8850

I also had similar problem once - reflection is extreemely slow. I used caching, like you are planning and performance grow more than 10 times. It was never again be a bottleneck in performance.

Upvotes: 1

JRoughan
JRoughan

Reputation: 1655

I've created similar logic before where I've cached an 'execution plan' for each type encountered. It was definitely faster for subsequent runs but you would have to profile your scenario to see whether it's worth the extra code complexity and memory usage.

Upvotes: 0

Related Questions