Autofac:使用 DynamicProxy 时提高性能的技巧?
我今天刚开始使用 DynamicProxy2。并发现它导致性能显着下降。
请参阅下面的代码。 Test1 比 Test2 慢 10 倍。
使用 DynamicProxy 时有什么提高性能的技巧吗?
class Program
{
public void Main()
{
for (int i = 0; i < 3; i++)
{
var stopWatch = Stopwatch.StartNew();
int count = 1 * 1000 * 1000;
Test1(count);
//Test2(count);
long t = stopWatch.ElapsedMilliseconds;
Console.WriteLine(t.ToString() + " milliseconds");
Console.WriteLine(((double)count/(t/1000)).ToString() + " records/1 seconds");
}
}
void Test1(int count)
{
var builder = new ContainerBuilder();
builder.RegisterType<TestViewModel>()
.EnableClassInterceptors()
.InterceptedBy(typeof(NotifyPropertyChangedInterceptor));
builder.RegisterType<NotifyPropertyChangedInterceptor>();
var container = builder.Build();
for (int i = 0; i < count; i++)
{
container.Resolve<TestViewModel>();
}
}
void Test2(int count)
{
var builder = new ContainerBuilder();
builder.RegisterType<TestViewModel>();
var container = builder.Build();
for (int i = 0; i < count; i++)
{
container.Resolve<TestViewModel>();
}
}
}
public class TestViewModel : INotifyPropertyChanged
{
[Notify]
public virtual string Value { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
}
/// <summary>
/// Copied from: http://serialseb.blogspot.com/2008/05/implementing-inotifypropertychanged.html
/// </summary>
public class NotifyPropertyChangedInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
// let the original call go through first, so we can notify *after*
invocation.Proceed();
if (invocation.Method.Name.StartsWith("set_"))
{
string propertyName = invocation.Method.Name.Substring(4);
var pi = invocation.TargetType.GetProperty(propertyName);
// check that we have the attribute defined
if (Attribute.GetCustomAttribute(pi, typeof(NotifyAttribute)) == null)
return;
// get the field storing the delegate list that are stored by the event.
FieldInfo info = invocation.TargetType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic)
.Where(f => f.FieldType == typeof(PropertyChangedEventHandler))
.FirstOrDefault();
if (info != null)
{
// get the value of the field
PropertyChangedEventHandler evHandler = info.GetValue(invocation.InvocationTarget) as PropertyChangedEventHandler;
// invoke the delegate if it's not null (aka empty)
if (evHandler != null)
evHandler.Invoke(invocation.TargetType, new PropertyChangedEventArgs(propertyName));
}
}
}
}
更新:
在我的机器上,Test1 大约需要 45 秒,Test2 大约需要 4.5 秒。在阅读 Krzysztof Koźmic 的答案后,我尝试将 NotifyPropertyChangedInterceptor 放入单例范围:
builder.RegisterType<NotifyPropertyChangedInterceptor>().SingleInstance();
这节省了我大约 4 秒的时间。现在 Test1 大约需要 41 秒。
更新 2:
Test3 在我的机器上大约需要 8.3 秒。所以看来单独使用 Autofac 或 DynamicProxy 性能并不是一个很大的问题(在我的项目中),但是将它们组合在一起会导致性能大幅下降。
public void Test3(int count)
{
var generator = new Castle.DynamicProxy.ProxyGenerator();
for (int i = 0; i < count; i++)
{
generator.CreateClassProxy(typeof(TestViewModel),
new NotifyPropertyChangedInterceptor());
}
}
I just start using DynamicProxy2 today. And found it caused significant performance drop.
See the code below. Test1 is 10 times slower than Test2.
Any tips for increasing performance when using DynamicProxy?
class Program
{
public void Main()
{
for (int i = 0; i < 3; i++)
{
var stopWatch = Stopwatch.StartNew();
int count = 1 * 1000 * 1000;
Test1(count);
//Test2(count);
long t = stopWatch.ElapsedMilliseconds;
Console.WriteLine(t.ToString() + " milliseconds");
Console.WriteLine(((double)count/(t/1000)).ToString() + " records/1 seconds");
}
}
void Test1(int count)
{
var builder = new ContainerBuilder();
builder.RegisterType<TestViewModel>()
.EnableClassInterceptors()
.InterceptedBy(typeof(NotifyPropertyChangedInterceptor));
builder.RegisterType<NotifyPropertyChangedInterceptor>();
var container = builder.Build();
for (int i = 0; i < count; i++)
{
container.Resolve<TestViewModel>();
}
}
void Test2(int count)
{
var builder = new ContainerBuilder();
builder.RegisterType<TestViewModel>();
var container = builder.Build();
for (int i = 0; i < count; i++)
{
container.Resolve<TestViewModel>();
}
}
}
public class TestViewModel : INotifyPropertyChanged
{
[Notify]
public virtual string Value { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
}
/// <summary>
/// Copied from: http://serialseb.blogspot.com/2008/05/implementing-inotifypropertychanged.html
/// </summary>
public class NotifyPropertyChangedInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
// let the original call go through first, so we can notify *after*
invocation.Proceed();
if (invocation.Method.Name.StartsWith("set_"))
{
string propertyName = invocation.Method.Name.Substring(4);
var pi = invocation.TargetType.GetProperty(propertyName);
// check that we have the attribute defined
if (Attribute.GetCustomAttribute(pi, typeof(NotifyAttribute)) == null)
return;
// get the field storing the delegate list that are stored by the event.
FieldInfo info = invocation.TargetType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic)
.Where(f => f.FieldType == typeof(PropertyChangedEventHandler))
.FirstOrDefault();
if (info != null)
{
// get the value of the field
PropertyChangedEventHandler evHandler = info.GetValue(invocation.InvocationTarget) as PropertyChangedEventHandler;
// invoke the delegate if it's not null (aka empty)
if (evHandler != null)
evHandler.Invoke(invocation.TargetType, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Update:
On my machine, Test1 takes about 45 seconds, Test2 takes about 4.5 seconds. After read Krzysztof Koźmic's answer, I tried to put NotifyPropertyChangedInterceptor into singleton scope:
builder.RegisterType<NotifyPropertyChangedInterceptor>().SingleInstance();
that saved me about 4 seconds. Now Test1 takes about 41 seconds.
Update 2:
Test3 takes about 8.3 seconds on my machine. So it seems using Autofac or DynamicProxy alone performance is not a very big problem (in my project), but combining them together would cause great performance drop.
public void Test3(int count)
{
var generator = new Castle.DynamicProxy.ProxyGenerator();
for (int i = 0; i < count; i++)
{
generator.CreateClassProxy(typeof(TestViewModel),
new NotifyPropertyChangedInterceptor());
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
你得到什么样的数字?在实际使用中性能下降是否明显?
我不熟悉 Autofac 如何在内部使用 DP,但您不应该注意到巨大的性能影响。
容器必须做更多的工作来代理虚拟机、实例化拦截器(因此您将创建两个对象,而不是只创建一个)并将拦截器与代理附加在一起。
如果缓存使用得当,当 DP 实际生成代理类型时,您将受到一次性性能影响。然后应该重用该类型。
您可以通过检查最后返回的代理的类型来轻松检查这一点。
如果是
Castle.Proxies.TestViewModelProxy
则意味着缓存工作正常。如果它是
Castle.Proxies.TestViewModelProxy_1000000
那么您每次都会生成一个新的代理类型,这会明显降低您的性能。一般来说,按照现实生活标准,性能影响应该可以忽略不计。
What sorts of numbers are you getting? Is the performance drop noticeable in real life usage?
I'm not familiar with how Autofac uses DP internally but you shouldn't notice big performance impact.
The container has to do more work to proxy the VM, instantiate the interceptor (so you're creating two objects instead of just one) and attach the interceptor with the proxy.
If the caching is used right you will get a one-time performance hit when DP is actually generating the proxy type. The type should then be reused.
You can easily check that by inspecting the type of the last proxy returned.
If it's
Castle.Proxies.TestViewModelProxy
that means caching works fine.If it's
Castle.Proxies.TestViewModelProxy_1000000
then you're generating a new proxy type each time which understandably decreases your performance.In general the performance impact should be neglectable by real life standards.
不是答案,但我想我会添加我的意见。
我没有使用 AutofacContrib.DynamicProxy2 扩展,而是尝试设置容器来手动构建代理,因此 Test1 看起来像:
这似乎在我的机器上运行了大约 13.5 秒(仅供参考,您的原始测试对我来说也需要大约 45 秒) )。
我想知道是否正如 Krzysztof 所建议的那样,AutofacContrib.DynamicProxy2 正在做一些天真的事情,比如每次都尝试创建一个新的 ProxyGenerator。但是当我尝试手动模拟这个时,我遇到了 OOM 异常(但是我在这台机器上只有 2 gig)。
Not an answer but thought I'd add my input.
Instead of using the AutofacContrib.DynamicProxy2 extensions I tried setting up the container to build the proxy manually, so Test1 looks like:
This seems to run in about 13.5 seconds on my machine (for reference, your original test also takes about 45 seconds for me).
I was wondering if, as Krzysztof suggested, the AutofacContrib.DynamicProxy2 was doing something naive like trying to create a new ProxyGenerator each time. But when I tried to manually emulate this I got a OOM exception (I only have 2 gig in this machine however).