DataBinder and Speed
In many projects, there is a need to access the properties of objects using the mechanism
So, a little test was done. To begin with, we made 2 classes (for testing virtual and overridden properties), the properties of which we will refer to:
Now the actual test:
Result:
Wow, 17 times slower! Although something similar was supposed.
To increase the speed, it was first proposed to cache calls to Reflection (where would it be without such a task), but this did not give serious acceleration. Then the approach was tested with the generation of the access code on the fly, that is:
I will not give the generation code in the topic, who are interested in downloading the Project (29Kb) .
Execution Result:
Excellent result! The speed is comparable to a direct call, with several starts this time was sometimes less than 100%. He called the call cached since the class with the method was remembered and it was constantly accessed without checking the existence of such a class.
To complete the experiment, a static class was written that cached the constructed classes and created them as needed.
Execution Result:
Just 38% slower than direct calling. Several launches gave an average result of 150%, but still this is not 1746%.
Full test output: 2 million calls: And 10 times more: What else can I add:
Appendix 1.
Class diagram of a more advanced version:
Class diagram of a code generator:
System.Web.UI.DataBinder.Eval
and the question arose: how fast does it work and is it possible to perform such an operation even faster?So, a little test was done. To begin with, we made 2 classes (for testing virtual and overridden properties), the properties of which we will refer to:
public class c1
{
string _str;
public c1(string str)
{
_str = str;
}
public string Str1 { get { return "1: "+_str; } }
public virtual string Str2 { get { return "2: "+_str; } }
}
public class c2 : c1
{
public c2(string str)
: base(str)
{
}
public override string Str2 { get { return "C"+base.Str2; } }
}
Now the actual test:
c2 c = new c2("str");
// Прямой доступ к свойствам
for (int i=0; i<ITERATIONS; i++)
{
String.Format("Str1: {0}, Str2: {1}", c.Str1, c.Str2);
}
// Доступ через DataBinder
for (int i=0; i<ITERATIONS; i++)
{
String.Format("Str1: {0}, Str2: {1}", DataBinder.Eval(c, "Str1"), DataBinder.Eval(c, "Str2"));
}
Result:
- Direct call: 00: 00: 00.065 (100%)
- DataBinder.Eval: 00: 00: 01.135 ( 1746% )
Wow, 17 times slower! Although something similar was supposed.
To increase the speed, it was first proposed to cache calls to Reflection (where would it be without such a task), but this did not give serious acceleration. Then the approach was tested with the generation of the access code on the fly, that is:
- At the first call, we study the class and property through Reflection
- We go to the class where the property was defined or redefined
- We build a class that consists of one method: get the value of the desired property from an object of a certain type
- We call the constructed method
I will not give the generation code in the topic, who are interested in downloading the Project (29Kb) .
c2 c = new c2("str");
PropertyAccessor p1 = PEval.GetPropertyAccessor(c.GetType(), "Str1");
PropertyAccessor p2 = PEval.GetPropertyAccessor(c.GetType(), "Str2");
for (int i=0; i<ITERATIONS; i++)
{
String.Format("Str1: {0}, Str2: {1}", p1.GetValue( c ), p2.GetValue( c ));
}
Execution Result:
- Cached Call: 00: 00: 00.065 (100%)
Excellent result! The speed is comparable to a direct call, with several starts this time was sometimes less than 100%. He called the call cached since the class with the method was remembered and it was constantly accessed without checking the existence of such a class.
To complete the experiment, a static class was written that cached the constructed classes and created them as needed.
c2 c = new c2("str");
for (int i=0; i<ITERATIONS; i++)
{
String.Format("Str1: {0}, Str2: {1}", PEval.GetValue(c, "Str1"), PEval.GetValue(c, "Str2"));
}
Execution Result:
- PEval.GetValue: 00: 00: 00.090 (138%)
Just 38% slower than direct calling. Several launches gave an average result of 150%, but still this is not 1746%.
Full test output: 2 million calls: And 10 times more: What else can I add:
Create PropertyAccessor for TestDataBinding.c1.Str1
Create PropertyAccessor for TestDataBinding.c2.Str2
=========================
ITERATIONS: 100000
Прямой вызов: 00:00:00.065 (100%)
PEval.GetValue: 00:00:00.090 (138%)
Кэшированный вызов: 00:00:00.065 (100%)
DataBinder.Eval: 00:00:01.135 (1746%)
=========================
TestDataBinding.c2 (TestDataBinding.PropEvaluator.PropertyAccessor) [200003]
TestDataBinding.c1_Str1_Accessor => Str1 (G) [100002]
TestDataBinding.c2_Str2_Accessor => Str2 (G) [100001]
TestDataBinding.c1 (TestDataBinding.PropEvaluator.PropertyAccessor) [2]
TestDataBinding.c1_Str1_Accessor => Str1 (G) [100002]
=========================
=========================
ITERATIONS: 1000000
Прямой вызов: 00:00:00.930 (100%)
PEval.GetValue: 00:00:01.085 (116%)
Кэшированный вызов: 00:00:00.738 (79%)
DataBinder.Eval: 00:00:10.976 (1180%)
=========================
=========================
ITERATIONS: 10000000
Прямой вызов: 00:00:06.802 (100%)
PEval.GetValue: 00:00:10.917 (160%)
Кэшированный вызов: 00:00:07.017 (103%)
DataBinder.Eval: 00:01:45.476 (1550%)
=========================
- Make methods to change the value of a property
- Extend the class of access to the property with additional functions: the display name of the property, formatting the value into a string / parsing the value from the string, validating the value
- Make a call not of one property, but chains
PEval.GetValue(o, "Prop1.Prop2.Prop4")
Appendix 1.
Class diagram of a more advanced version:
Class diagram of a code generator: