.NET Forum / .NET Framework / CLR / September 2006
Reflection in 1.1 and 2.0
|
|
Thread rating:  |
Eric - 12 Sep 2006 20:50 GMT 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 { using System; using System.ComponentModel; using System.Reflection;
public sealed 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();
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(Person)); string test1 = typeof(TypeDescriptor).Name; TimeSpan span1 = Measure(iterations, person, properties["FirstName"], properties["LastName"], properties["Age"]);
Console.WriteLine("{0} = {1}", test1, span1); string test2 = typeof(MyPropertyDescriptor).Name; TimeSpan span2 = Measure(iterations, person, new MyPropertyDescriptor(typeof(Person).GetProperty("FirstName")), new MyPropertyDescriptor(typeof(Person).GetProperty("LastName")), new MyPropertyDescriptor(typeof(Person).GetProperty("Age")));
Console.WriteLine("{0} = {1}", test2, span2); Console.WriteLine("{0}/{1} = {2:P}", test1, test2, span1.TotalMilliseconds / span2.TotalMilliseconds);
Console.WriteLine("Press ENTER to end."); Console.ReadLine(); }
static TimeSpan Measure(int iterations, Person person, params PropertyDescriptor[] properties) { DateTime start; start = DateTime.Now; for (int i = 0; i < iterations; i++) { properties[0].SetValue(person, "John"); properties[1].SetValue(person, "Doe"); properties[2].SetValue(person, 22); } return DateTime.Now - start; }
public sealed class MyPropertyDescriptor : PropertyDescriptor { private readonly PropertyInfo _property;
public MyPropertyDescriptor(PropertyInfo property) : base(property.Name, null) { _property = property; }
public override bool CanResetValue(object component) { return false; }
public override object GetValue(object component) { return _property.GetValue(component, null); }
public override void ResetValue(object component) { throw new NotSupportedException(); }
public override void SetValue(object component, object value) { _property.SetValue(component, value, null); }
public override bool ShouldSerializeValue(object component) { return false; }
public override Type ComponentType { get { return _property.ReflectedType; } }
public override bool IsReadOnly { get { return !_property.CanWrite; } }
public override Type PropertyType { get { return _property.PropertyType; } } } } }
Jeffrey Tan[MSFT] - 13 Sep 2006 10:27 GMT Hi Eric,
Yes, I can reproduce the performance issue you reported.
Based on the research, I find that the IL generated by .Net1.1 and .Net2.0 C# compiler is the same. So the problem should be in .Net FCL or CLR.
I will try to contact our CLR team to confirm this issue. I will feedback here ASAP. Thanks.
Best regards, Jeffrey Tan Microsoft Online Community Support ================================================== Get notification to my posts through email? Please refer to http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notif ications.
Note: The MSDN Managed Newsgroup support offering is for non-urgent issues where an initial response from the community or a Microsoft Support Engineer within 1 business day is acceptable. Please note that each follow up response may take approximately 2 business days as the support professional working with you may need further investigation to reach the most efficient resolution. The offering is not appropriate for situations that require urgent, real-time or phone-based interactions or complex project analysis and dump analysis issues. Issues of this nature are best handled working with a dedicated Microsoft Support Engineer by contacting Microsoft Customer Support Services (CSS) at http://msdn.microsoft.com/subscriptions/support/default.aspx. ================================================== This posting is provided "AS IS" with no warranties, and confers no rights.
Jeffrey Tan[MSFT] - 18 Sep 2006 04:09 GMT Hi Eric,
Sorry for letting you wait.
I am trying to contact our CLR team regarding this performance issue these days. After several round of emails, I finally find the correct dev team that is responsible for System.ComponentModel.TypeDescriptor namespace changes. The dev team is current working on this issue now.
I will feedback any progress here ASAP. Thanks for your patient.
Best regards, Jeffrey Tan Microsoft Online Community Support ================================================== Get notification to my posts through email? Please refer to http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notif ications.
Note: The MSDN Managed Newsgroup support offering is for non-urgent issues where an initial response from the community or a Microsoft Support Engineer within 1 business day is acceptable. Please note that each follow up response may take approximately 2 business days as the support professional working with you may need further investigation to reach the most efficient resolution. The offering is not appropriate for situations that require urgent, real-time or phone-based interactions or complex project analysis and dump analysis issues. Issues of this nature are best handled working with a dedicated Microsoft Support Engineer by contacting Microsoft Customer Support Services (CSS) at http://msdn.microsoft.com/subscriptions/support/default.aspx. ================================================== This posting is provided "AS IS" with no warranties, and confers no rights.
Atif Aziz - 18 Sep 2006 10:05 GMT Hi Jeffrey,
The problem is in the System.Reflection namespace and nothing to do with System.ComponentModel or TypeDescriptor. Here's a bit of background. I originally wrote the sample and pointed out the problem to a distribution list within our company. Eric kindly posted the problem here with the sample included verbatim. However, the sample is a little too elaborate to prove the point. Originally, I was testing the relative performance of doing a property-set via PropertyDescriptor.SetValue versus PropertyInfo.SetValue. Of course, PropertyDescriptor.SetValue internally eventually calls PropertyInfo.SetValue but goes through a lot more hoops (such as engaging with IComponentChangeService if on e is around) before just doing that and I wanted to measure the cost of those hoops. My sample code creates its own variant of PropertyDescriptor implementation that simply calls PropertyInfo.SetValue. The idea here was to include the cost of v-table dispatch across both measurements. Anyhow, I've reworked the sample to show that the performance issue exists solely within the System.Reflection space:
/* ============ C# SAMPLE START ============ */
using System; using System.Reflection;
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();
PropertyInfo fn = typeof(Person).GetProperty("FirstName"); PropertyInfo ln = typeof(Person).GetProperty("LastName"); PropertyInfo age = typeof(Person).GetProperty("Age");
DateTime start = DateTime.Now;
for (int i = 0; i < iterations; i++) { fn.SetValue(person, "John", null); ln.SetValue(person, "Doe", null); age.SetValue(person, 42, null); }
Console.WriteLine(DateTime.Now - start); } }
/* ============= C# SAMPLE END ============= */
Assuming the sample is saved as the file Program.cs, compile using:
csc /optimize+ Program.cs
Needless to say, the csc.exe should come from the version of the framework against which you wish to make performance test. I would recommend compiling the program with v1.0.3705. Then run it against the various versions using the following batch to see the differences:
@REM ========== BATCH START ============
@echo off
setlocal set vers=%1 if "%1"=="" set vers=v1.0.3705 v1.1.4322 v2.0.50727 for %%i in (%vers%) do call :run %%i goto exit
:run set COMPLUS_Version=%1 program.exe
:exit REM ============ BATCH END =============
If you run this against 1.x and 2.0, you'll find that 2.0 is consistently twice as slow as 1.x for setting a property. On my machine, the results are:
Runtime version = 1.0.3705.6018 Iterations = 1,000,000 00:00:02 Runtime version = 1.1.4322.2032 Iterations = 1,000,000 00:00:02.1093750 Runtime version = 2.0.50727.42 Iterations = 1,000,000 00:00:04.5468750
- Atif
> Hi Eric, > [quoted text clipped - 28 lines] > ================================================== > This posting is provided "AS IS" with no warranties, and confers no rights. Stuart Carnie - 18 Sep 2006 23:47 GMT 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); }
> I have here a little sample which does reflection on a class. > I striked me that 1.1 is nearly two times faster. [quoted text clipped - 146 lines] > } > } Atif Aziz - 19 Sep 2006 10:44 GMT 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.
> I'd suggest you take advantage of Lightweight Code Generation (LCG). > [quoted text clipped - 250 lines] > > } > > } Stuart Carnie - 19 Sep 2006 21:19 GMT 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>)); }
}
> Hi Stuart, > [quoted text clipped - 286 lines] >>> } >>> } Atif Aziz - 19 Sep 2006 21:42 GMT 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.
> 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. [quoted text clipped - 289 lines] > >>> > >>> int iterations = args.Length > 0 ? int.Parse(args[0]) : 1000000; Stuart Carnie - 20 Sep 2006 01:03 GMT 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
> Hi Stuart, > [quoted text clipped - 297 lines] >>>>> >>>>> int iterations = args.Length > 0 ? int.Parse(args[0]) : 1000000; Atif Aziz - 20 Sep 2006 09:41 GMT 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.
> 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. [quoted text clipped - 286 lines] > >>>>> > >>>>> namespace ComponentProfiling Stuart Carnie - 20 Sep 2006 17:32 GMT 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
> 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 [quoted text clipped - 298 lines] >>>>>>> >>>>>>> namespace ComponentProfiling Stuart Carnie - 19 Sep 2006 21:24 GMT That's a very interesting observation - private types slows down by a factor of 4?
I definitely agree, performance characteristics would be useful, especially for something as fundamental as reflection.
I did some similar profiling exercises with ADO.NET and the DataSet in v2.0. Whilst the performance was a huge improvement, the RBTree and some other structures used internally to manage the indexes increased the memory requirements by 2 times! So, for those loading large data sets (that contributed a significant percentage of their applications working set) effectively doubled!
Cheers,
Stu
> Hi Stuart, > [quoted text clipped - 286 lines] >>> } >>> } Atif Aziz - 19 Sep 2006 21:45 GMT Yeah, I'm hoping Jeffrey or someone from the CLR team will get back to us on this to shed some light about what has changed internally between 1.x and 2.0.
> That's a very interesting observation - private types slows down by a factor of 4? > [quoted text clipped - 290 lines] > >>> get { return !_property.CanWrite; } > >>> } Jeffrey Tan[MSFT] - 20 Sep 2006 07:26 GMT Hi Eric,
The CLR team finally filed this issue as a bug in the internal database now. Thank you for reporting this issue. If there is any interesting information available I will come back and post here. Thanks.
Best regards, Jeffrey Tan Microsoft Online Community Support ================================================== Get notification to my posts through email? Please refer to http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notif ications.
Note: The MSDN Managed Newsgroup support offering is for non-urgent issues where an initial response from the community or a Microsoft Support Engineer within 1 business day is acceptable. Please note that each follow up response may take approximately 2 business days as the support professional working with you may need further investigation to reach the most efficient resolution. The offering is not appropriate for situations that require urgent, real-time or phone-based interactions or complex project analysis and dump analysis issues. Issues of this nature are best handled working with a dedicated Microsoft Support Engineer by contacting Microsoft Customer Support Services (CSS) at http://msdn.microsoft.com/subscriptions/support/default.aspx. ================================================== This posting is provided "AS IS" with no warranties, and confers no rights.
Atif Aziz - 20 Sep 2006 09:44 GMT Hi Jeffrey,
Does this also bug also apply to my comment about performance degrading by nearly 4 times if your target public members of a private type, and from within the same assembly that resides in a trusted zone?
- Atif
> Hi Eric, > [quoted text clipped - 23 lines] > ================================================== > This posting is provided "AS IS" with no warranties, and confers no rights. Eric - 21 Sep 2006 08:39 GMT Hi Jeffrey,
Thanks for taking care about the Issue. Could you give us some more information about the bug? Where's the problem?
Eric
> Hi Eric, > [quoted text clipped - 23 lines] > ================================================== > This posting is provided "AS IS" with no warranties, and confers no rights. Jeffrey Tan[MSFT] - 21 Sep 2006 10:23 GMT Hi Eric,
Thanks for the feedback.
Sorry, since the bug is just filed, our CLR engineer is still working on the bug now and I did not see much information in the bug record currently. It may require some time for the engineer to figure out the root cause, anyway, I will check the bug after some time and feedback interesting information here.
Thanks for the understanding.
Best regards, Jeffrey Tan Microsoft Online Community Support ================================================== Get notification to my posts through email? Please refer to http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notif ications.
Note: The MSDN Managed Newsgroup support offering is for non-urgent issues where an initial response from the community or a Microsoft Support Engineer within 1 business day is acceptable. Please note that each follow up response may take approximately 2 business days as the support professional working with you may need further investigation to reach the most efficient resolution. The offering is not appropriate for situations that require urgent, real-time or phone-based interactions or complex project analysis and dump analysis issues. Issues of this nature are best handled working with a dedicated Microsoft Support Engineer by contacting Microsoft Customer Support Services (CSS) at http://msdn.microsoft.com/subscriptions/support/default.aspx. ================================================== This posting is provided "AS IS" with no warranties, and confers no rights.
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|