Re: Reflection in 1.1 and 2.0 - generic, fast copy delegate?



Totally understand. I didn't mean to hijack the thread.

I seem to have been advocating LCG lately :)

I had a scenario where I was constructing lots of objects, which are loaded dynamically, and Activator.CreateInstance is notoriously slow, so I created a delegate from a DynamicMethod to call the constructor directly. Much better :)

Good work on finding the issue - and glad to see it has been raised as a bug.

Cheers,

Stu

Atif Aziz wrote:
Yes, there's nothing that's going to beat code that's custom-tailored to a particular scenario. In your case, it's good for copying public properties from one instance to another of the same type but then that's where it ends. I didn't mean to get into a discussion about the best and fastest way to set properties going from 1.x to 2.0. My original code was setting properties because that seemed simple enough to illustrate the drastic change in reflection-invocation performance that I haven't seen reported anywhere so far. Internally, all invocations-via-reflection go through MethodBase.Invoke (as you probably know) so one could illustrate the same problem with invoking a method rather than setting properties. Anyway, for better or worse, it's officially a bug now, according to MS.

"Stuart Carnie" wrote:

It would not be as efficient in the 'copy' case, as I've emitted 4 IL instructions to copy from the source object to the destination.

Correct me, if I'm wrong, but you would have to create two delegates per property. One to get the value from the source and a second to set the value on the destination.

I'm making one delegate call, and then a callvirt to each get/set.

A class with 6 public properties (6 set_* and 6 get_*), 1,000,000 iterations

-----------------------------------------------------
| Method | Calls (per iteration) | Total calls |
-----------------------------------------------------
| | Delegate | get_* | set_* | |
-----------------------------------------------------
| LCG | 1 | 6 | 6 | 13,000,000 |
-----------------------------------------------------
| Delegate| 12 | 6 | 6 | 24,000,000 |
-----------------------------------------------------

Your delegate solution is is still significantly more efficient that reflection. I was just providing another alternative.

Cheers,

Stu

Atif Aziz wrote:
Hi Stuart,

But once more, why use LCG? Why not just use the standard delegate infrastructure to get at the same result? Also you need permissions to emit code with the LCG version whereas using delegates does not appear to require permissions beyond those for public reflection and so you can have a fast solution even in low trust environments.

"Stuart Carnie" wrote:

Sure thing, your particular example is certainly better :). What I presented was just an example. I'm sure you are aware that LCG is significantly more powerful than that.

How about a generic 'copy public read/write properties' delegate. The delegate is strongly typed, and you can generate one for any object that has public, read/write properties.

Runtime version = 2.0.50727.42
Iterations = 1,000,000
00:00:00.0625004
FirstName: John, LastName: Doe, Age: 64

Cheers,

Stu

/* ********** COPY BELOW ********** */

using System;
using System.Reflection;
using System.Reflection.Emit;

public class Person
{
private string _firstName;
private string _lastName;
private int _age;

public string FirstName
{
get { return _firstName; }
set { _firstName = value; }
}

public string LastName
{
get { return _lastName; }
set { _lastName = value; }
}

public int Age
{
get { return _age; }
set { _age = value; }
}
}

delegate void CopyPublicPropertiesDelegate<T>(T source, T dest);

class Program
{
static void Main(string[] args)
{
Console.WriteLine("Runtime version = {0}", Environment.Version);

int iterations = args.Length > 0 ? int.Parse(args[0]) : 1000000;
Console.WriteLine("Iterations = {0}", iterations.ToString("N0"));

Person srcperson = new Person();
srcperson.FirstName = "John";
srcperson.LastName = "Doe";
srcperson.Age = 64;

Person dstperson = new Person();

DateTime start = DateTime.Now;

CopyPublicPropertiesDelegate<Person> cd = GenerateCopyDelegate<Person>();

for (int i = 0; i < iterations; i++)
{
cd(srcperson, dstperson);
}

Console.WriteLine(DateTime.Now - start);

Console.WriteLine("FirstName: {0}, LastName: {1}, Age: {2}", dstperson.FirstName, dstperson.LastName, dstperson.Age);
}

static CopyPublicPropertiesDelegate<T> GenerateCopyDelegate<T>()
{
Type type = typeof(T);

DynamicMethod dm = new DynamicMethod(type.Name, null, new Type[] { typeof(T), typeof(T) }, typeof(Program).Module);

ILGenerator il = dm.GetILGenerator();

PropertyInfo[] props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);

foreach (PropertyInfo prop in props)
{
if (prop.CanRead && prop.CanWrite)
{
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldarg_0);
il.EmitCall(OpCodes.Callvirt, prop.GetGetMethod(), null);
il.EmitCall(OpCodes.Callvirt, prop.GetSetMethod(), null);
}
}
il.Emit(OpCodes.Ret);

return (CopyPublicPropertiesDelegate<T>)dm.CreateDelegate(typeof(CopyPublicPropertiesDelegate<T>));
}

}


Atif Aziz wrote:
Hi Stuart,

I'm well aware of LCG, and in fact, I'm not even sure why you need it in 2.0. You can get the same results without LCG using purely delegates. Define the delegate like this:

delegate void ValueSetter<TContainer, TValue>(TContainer container, TValue value);

Then create it like this:

ValueSetter<Person, string> fnSetter = (ValueSetter<Person, string>) Delegate.CreateDelegate(stringSetterType, fn.GetSetMethod());

And use it as you did:

fnSetter(person, "John");

Less code and doesn't require LCG or IL magic and is even strongly typed on the container type (Person). Incidentally, this version performs 145 times faster than using reflection.

Anyhow I'm interested in understanding where the performance issues are occurring. Generally, things should get faster with each version of the framework, but going twice as slow as previous version mandates at least a warning or notice in the documentation. Don't you think? The problem is that we can't upgrade all applications to 2.0 overnight. Moreover, loading 1.x assemblies is 2.0 apps will upgrade automatically at runtime but suffer twice the performance if they're internally using reflection.

BTW, have you tried making Person private? The performance slows down by a factor of 4 times than when its public. Here the behavior is fairly identical across 1.x and 2.0. If it's a CAS issue, then I can't imagine why I'm being more taxed for accessing private types more than public ones! It's also quite a tax for a fully trusted application.

"Stuart Carnie" wrote:

I'd suggest you take advantage of Lightweight Code Generation (LCG).

I knocked a v2.0 sample together using your example.

NO LCG:
Runtime version = 2.0.50727.42
Iterations = 1,000,000
00:00:04.8898738

USING LCG:
Runtime version = 2.0.50727.42
Iterations = 1,000,000
00:00:00.0937632

- - - - - COPY BELOW - - - - - -

#define LCG
// remove this define to use the old PropertyInfo way.

using System;
using System.Reflection;
using System.Reflection.Emit;

public class Person
{
private string _firstName;
private string _lastName;
private int _age;

public string FirstName
{
get { return _firstName; }
set { _firstName = value; }
}

public string LastName
{
get { return _lastName; }
set { _lastName = value; }
}

public int Age
{
get { return _age; }
set { _age = value; }
}
}

class Program
{
static void Main(string[] args)
{
Console.WriteLine("Runtime version = {0}", Environment.Version);

int iterations = args.Length > 0 ? int.Parse(args[0]) : 1000000;
Console.WriteLine("Iterations = {0}", iterations.ToString("N0"));

Person person = new Person();
person.FirstName = "Hello";


PropertyInfo fn = typeof(Person).GetProperty("FirstName");
PropertyInfo ln = typeof(Person).GetProperty("LastName");
PropertyInfo age = typeof(Person).GetProperty("Age");

DateTime start = DateTime.Now;

#if LCG
FastSetterDelegate<string> fns = GenerateFastSetter<string>(fn);
FastSetterDelegate<string> lns = GenerateFastSetter<string>(ln);
FastSetterDelegate<int> ages = GenerateFastSetter<int>(age);

for (int i = 0; i < iterations; i++)
{
fns(person, "John");
lns(person, "Doe");
ages(person, 42);
}
#else
for (int i = 0; i < iterations; i++)
{
fn.SetValue(person, "John", null);
ln.SetValue(person, "Doe", null);
age.SetValue(person, 42, null);
}
#endif
Console.WriteLine(DateTime.Now - start);
}

static FastSetterDelegate<T> GenerateFastSetter<T>(PropertyInfo pi)
{
DynamicMethod dm = new DynamicMethod(pi.Name, null, new Type[] { typeof(object), typeof(T) }, typeof(Program).Module);

ILGenerator il = dm.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.EmitCall(OpCodes.Callvirt, pi.GetSetMethod(), null);
il.Emit(OpCodes.Ret);

return (FastSetterDelegate<T>)dm.CreateDelegate(typeof(FastSetterDelegate<T>));
}

delegate void FastSetterDelegate<T>(object obj, T value);
}


Eric wrote:
I have here a little sample which does reflection on a class.
I striked me that 1.1 is nearly two times faster.
Could me somebody give a hint why?

For the sample: Compile it via "csc.exe /optimize+ Program.cs" with the two compiler for 1.1 and 2.0.

Thanks
Eric

namespace ComponentProfiling
.



Relevant Pages

  • Re: Reflection in 1.1 and 2.0 - generic, fast copy delegate?
    ... Your delegate solution is is still significantly more efficient that reflection. ... Also you need permissions to emit code with the LCG version whereas using delegates does not appear to require permissions beyond those for public reflection and so you can have a fast solution even in low trust environments. ... private string _firstName; ...
    (microsoft.public.dotnet.framework.clr)
  • Re: Reflection in 1.1 and 2.0 - generic, fast copy delegate?
    ... private string _firstName; ... private int _age; ... I'm well aware of LCG, and in fact, I'm not even sure why you need it in 2.0. ...
    (microsoft.public.dotnet.framework.clr)
  • Re: Reflection in 1.1 and 2.0 - generic, fast copy delegate?
    ... But once more, why use LCG? ... How about a generic 'copy public read/write properties' delegate. ... private string _firstName; ... private int _age; ...
    (microsoft.public.dotnet.framework.clr)
  • Re: Reflection in 1.1 and 2.0 - generic, fast copy delegate?
    ... Your delegate solution is is still significantly more efficient that reflection. ... But once more, why use LCG? ... private string _firstName; ... private int _age; ...
    (microsoft.public.dotnet.framework.clr)
  • Re: Reflection in 1.1 and 2.0
    ... USING LCG: ... private string _firstName; ... public override bool CanResetValue ...
    (microsoft.public.dotnet.framework.clr)