首页 > 代码库 > 调用.NET Serviced Component引发的性能问题及其解决

调用.NET Serviced Component引发的性能问题及其解决

 

在企业用户环境里,.NET Serviced Component使用广泛。它比较好的把传统COM+封装和.NET应用逻辑衔接了起来,在服务器端应用起到重要作用。.NET Serviced Component 的使用需要注意到很多方面,特别是要做到对象资源合理应用(pooling)和及时释放(Dispose)。现有文章就这方面有很多具体讨论。

在这里,我要分析的是一种以前处理过的特定情况下.NET Serviced Component的引发的High CPU的问题。不只一个客户遇到此类问题,觉得有必要和大家分享一下。

如果程序出现High CPU,这个应用程序线程一定出现了繁忙的运算,比如重复的计算或者长时间的循环,而不是等待数据库,锁,或其它资源。如果这个程序是.NET程序,频繁Garbage Collection导致High CPU就必须要重点考虑。因为每次GC都需要检查.NET 对象树和调整.NET堆, 是繁重的操作。我们可以通过捕捉性能日志(perfmon),查看.NET CLR Memory 对象下的%Time in GC 和 Induced GC 这两个性能计数器指标,来判断High CPU是不是和GC有关。

在这个例子里面,通过查看性能监视器,可以看到Induced GC增长非常的快速,表明是这次High CPU的直接原因:

 

clip_image001

 

通常情况下,Induced GC 出现是客户代码里面直接调用了GC.Collect 。然而,在查看客户代码后,发现根本没有GC.Collect出现。

我们注意到这个程序里面使用了.NET Enterprise Serviced Components, 开始便建议客户使用Dispose方法释放这些Components, 而不是用DisposeObject:

http://msdn.microsoft.com/en-us/library/system.enterpriseservices.servicedcomponent.disposeobject.aspx

并且尝试启用对象池,, 发现改善并不明显:

http://www.codeproject.com/Articles/579/COM-Object-Pooling

http://msdn.microsoft.com/en-us/library/windows/desktop/ms684273(v=vs.85).aspx

于是在这个程序High CPU的时候抓取了Memory Dump。

注:抓取Memory Dump的方法很多,可以用WinDBG, procdump或者DebugDiag

通过仔细分析Memory Dump,发现Induced GC逻辑出现在以下Call Stack里面:

非托管 Call Stack:

Child-SP          RetAddr           Call Site00000000`0f59bbe8 00000000`7704ce00 ntdll!ZwWaitForSingleObject+0xa00000000`0f59bbf0 000007fe`f52fa74a kernel32!WaitForSingleObjectEx+0x9c00000000`0f59bcb0 000007fe`f52fa83b mscorwks!CLREventWaitHelper+0x4200000000`0f59bd10 000007fe`f53cee38 mscorwks!CLREvent::WaitEx+0x6300000000`0f59bdc0 000007fe`f53c1cb7 mscorwks!SVR::gc_heap::wait_for_gc_done+0x8800000000`0f59be00 000007fe`f542913b mscorwks!SVR::GCHeap::GarbageCollectGeneration+0x14700000000`0f59be90 000007fe`f5412ac3 mscorwks!SVR::GCHeap::GarbageCollect+0x5b00000000`0f59bee0 000007fe`f57301a5 mscorwks!GCInterface::AddMemoryPressure+0x13b00000000`0f59bf70 000007fe`f57b9beb mscorwks!RCW::AddMemoryPressure+0x1500000000`0f59bfb0 000007fe`f57dbbf4 mscorwks!RCW::CreateRCW+0x17b00000000`0f59c030 000007fe`f57dbdea mscorwks!COMInterfaceMarshaler::CreateObjectRef+0x7400000000`0f59c130 000007fe`f58843f4 mscorwks!COMInterfaceMarshaler::WrapWithComObject+0x3a00000000`0f59c1a0 000007fe`f0268995 mscorwks!MarshalNative::WrapIUnknownWithComObject+0x13400000000`0f59c3c0 000007fe`f0270933 System_EnterpriseServices_ni!System.EnterpriseServices.RemoteServicedComponentProxy..ctor(System.Type, IntPtr, Boolean)+0xd500000000`0f59c420 000007fe`f0270213 System_EnterpriseServices_ni!System.EnterpriseServices.FastRSCPObjRef.GetRealObject(System.Runtime.Serialization.StreamingContext)+0x3300000000`0f59c470 000007fe`f37254db System_EnterpriseServices_ni!System.EnterpriseServices.ServicedComponentProxyAttribute.CreateProxy(System.Runtime.Remoting.ObjRef, System.Type, System.Object, System.Runtime.Remoting.Contexts.Context)+0x13300000000`0f59c500 000007fe`f37253ff mscorlib_ni!System.Runtime.Remoting.RemotingServices.SetOrCreateProxy(System.Runtime.Remoting.Identity, System.Type, System.Object)+0x9b00000000`0f59c560 000007fe`f372466f mscorlib_ni!System.Runtime.Remoting.RemotingServices.GetOrCreateProxy(System.Runtime.Remoting.Identity, System.Object, Boolean)+0xbf00000000`0f59c5c0 000007fe`f027050a mscorlib_ni!System.Runtime.Remoting.RemotingServices.InternalUnmarshal(System.Runtime.Remoting.ObjRef, System.Object, Boolean)+0x12f00000000`0f59c650 000007fe`f40e3edf System_EnterpriseServices_ni!System.EnterpriseServices.ServicedComponentProxyAttribute.CreateInstance(System.Type)+0x2ba00000000`0f59c7c0 000007fe`f5416e61 mscorlib_ni!System.Runtime.Remoting.Activation.ActivationServices.IsCurrentContextOK(System.Type, System.Object[], Boolean)+0x9ba81f

 

对应的.NET 托管 Call Stack:

Child-SP         RetAddr          Call Site000000000f59c3c0 000007fef0270933 System.EnterpriseServices.RemoteServicedComponentProxy..ctor(System.Type, IntPtr, Boolean)000000000f59c420 000007fef0270213 System.EnterpriseServices.FastRSCPObjRef.GetRealObject(System.Runtime.Serialization.StreamingContext)000000000f59c470 000007fef37254db System.EnterpriseServices.ServicedComponentProxyAttribute.CreateProxy(System.Runtime.Remoting.ObjRef, System.Type, System.Object, System.Runtime.Remoting.Contexts.Context)000000000f59c500 000007fef37253ff System.Runtime.Remoting.RemotingServices.SetOrCreateProxy(System.Runtime.Remoting.Identity, System.Type, System.Object)000000000f59c560 000007fef372466f System.Runtime.Remoting.RemotingServices.GetOrCreateProxy(System.Runtime.Remoting.Identity, System.Object, Boolean)000000000f59c5c0 000007fef027050a System.Runtime.Remoting.RemotingServices.InternalUnmarshal(System.Runtime.Remoting.ObjRef, System.Object, Boolean)000000000f59c650 000007fef40e3edf System.EnterpriseServices.ServicedComponentProxyAttribute.CreateInstance(System.Type)

通过上面的调用栈信息,可以得知当serviced componnet被创建的时候,.NET RCW 逻辑会在mscorwks!RCW::CreateRCW  里调用GC.AddMemoryPressure 对可能的额外内存需要做提前预估. 当内存在不停扩展时, AddMemoryPressure会增加 Induced GC 的计数(如我们在性能监视器里发现的), 并且调用GarbageCollect。

为了解决这种问题,我们需要在应用程序里减少创建Serviced Component的次数。光在COM+里面修改成Object Pooling是不够的,因为这种Induced GC是在每次应用程序调用RCW的时候都会发生。通过研究代码和测试,如在每次调用创建Serviced Component之前使用 RemoveMemoryPressure(4004) 用来抵消RCW里的相同bytes的AddMemoryPressure(4004)可以临时解决这个问题.

最终解决方法是我们在应用程序本身创建了自己的对象池(建立一组全局对象,简单管理它们的存在周期),以彻底减少CreateInsance和CreateRCW的调用。这样处理以后,即使在大压力情况下, 程序运行也是良好的。

调用.NET Serviced Component引发的性能问题及其解决