内存溢出:Microsoft.CSharp.RuntimeBinder.Semantics数量不断增加

14
我们目前正在寻找应用程序中的一些内存泄漏问题,当执行某些操作(在应用程序内加载和关闭一个项目)时,我们知道内存总是会稍微增加一点。
我们已经找到了很多问题,但现在,根据我们的工具(ANTS Memory Profiler 8.2),最大的10个增加类如下:
Microsoft.CSharp.RuntimeBinder.Semantics.SYMTBL+Key Microsoft.CSharp.RuntimeBinder.Semantics.LocalVariableSymbol Microsoft.CSharp.RuntimeBinder.Semantics.CONSTVAL Microsoft.CSharp.RuntimeBinder.Semantics.EXPRCONSTANT Microsoft.CSharp.RuntimeBinder.Semantics.EXPRCLASS Microsoft.CSharp.RuntimeBinder.Semantics.EXPRTYPEOF Microsoft.CSharp.RuntimeBinder.Semantics.EXPRLIST Microsoft.CSharp.RuntimeBinder.Semantics.MethWithInst Microsoft.CSharp.RuntimeBinder.Semantics.CMemberLookupResults Microsoft.CSharp.RuntimeBinder.Semantics.EXPRMEMGRP Microsoft.CSharp.RuntimeBinder.Semantics.EXPRCALL Microsoft.CSharp.RuntimeBinder.Semantics.EXPRWRAP Microsoft.CSharp.RuntimeBinder.Semantics.AggregateDeclaration Microsoft.CSharp.RuntimeBinder.Semantics.Scope 不幸的是,我不知道这是什么,所以对我来说有点难找出如何释放和修改它们。
我检查了实例树,但它跟Microsoft的东西有着紧密关联。
问题在于当我们进行项目的“打开/关闭”时,我们要经过很多(大部分)我们的代码。
编辑1:我们的应用程序的一个部分使用dynamic 关键字访问一些资源,可能与此有关。这些类不可回收,我应该对它们做些特殊处理吗?
编辑2:我相信这与我的dynamic相关,似乎C#在使用dynamic时会创建缓存。但目前我不知道为什么它会增长(我一直加载相同的类,并且始终具有完全相同的签名),也不知道如何清除它。

1
你对这个主题有什么发现吗? - cmart
@MarChr 目前还没有 :( 我相当确定这与我的 dynamic 使用有关,但我不明白为什么它会不断增长。 - J4N
1
嗯...看起来我遇到了同样的问题。但这是一个庞大的应用程序,很难找出哪里出了问题。但是当我找到一些信息时,我会发布一个示例让你知道! - cmart
1
你真的需要使用dynamic吗?你能否确定你的应用程序中正在使用dynamic的代码片段并在此处发布它? - Tamas Ionut
在《编写高性能.NET代码》一书中有一节关于动态的内容。以下是作者在该节开头对“动态”的看法: “任何使用dynamic关键字或DLR的代码都不会被高度优化。性能调整通常涉及剥离抽象,但使用DLR则添加了一个巨大的抽象层。” - dragonfly02
显示剩余5条评论
2个回答

4
我今天在分析我的应用程序RepoZ中的内存泄漏时遇到了完全相同的问题。该工具应该在后台运行,定期检查Git存储库并更新Windows Explorer窗口标题。后者需要通过对“Shell.Application”进行一些COM调用来查找Explorer窗口并确定它们当前指向的路径。
通过像这样使用dynamic关键字...
dynamic shell = Activator.CreateInstance(...);
foreach (object window in shell.Windows())
{ 
    var hwnd = window.Hwnd;
    ...
}

几小时后我最终遇到了这样的内存转储:

enter image description here

Combridge

为了解决这个问题,我编写了一个小的辅助类 "Combridge",它负责释放COM对象并提供对底层COM对象的方法和属性的非常简单的访问。这很容易和直接,没有什么特别之处。它利用Reflection to COM objects,因此性能有所损失(见下文)。

使用它,上面的代码示例看起来像这样:

using (var shell = new Combridge(Activator.CreateInstance(...)))
{
    var windows = shell.InvokeMethod<IEnumerable>("Windows");
    foreach (var window in windows)
    {
        var hwnd = window.GetPropertyValue<long>("Hwnd");
        ... 
    }
}

你可以查看ExplorerWindowActor文件了解它在RepoZ中的使用方法。
这种方式并不像使用dynamic那样美观,而且第一次尝试时性能也变差了。快速测试显示如下:

性能

我测试了1000个迭代,在每个迭代中处理10个打开的资源管理器窗口。对于每个窗口,会调用该COM对象的4个方法或属性。因此,我们谈论的是40000个COM调用。
持续时间从 ~2500ms(dynamic)增加到 ~6000ms(Combridge)。每个调用从0.062ms增加到0.150ms。 所以现在需要花费大约2.4倍的时间来完成。 我知道这很显著。但对于我的要求来说还可以,内存泄漏也解决了。
就是这样 - 我想与您分享这个故事,希望您可以使用该类(或其改进版本)摆脱动态地狱。

~更新~

经过10个小时的运行,RepoZ仍然具有非常稳定的内存占用。

enter image description here

所以,当有10个浏览器窗口打开时,每个窗口有4个COM调用,并且整个循环每秒运行两次,RepoZ创建了大约72,000个COM实例,总共进行了约2,880,000个COM调用,而没有增加任何内存消耗。
我想我们可以说问题确实与dynamic有关。

1

动态关键字应该很少使用,因为在大多数情况下可以找到不需要它的解决方法。

根据您的应用程序,最好的建议是仔细思考是否可以设计您的解决方案以避免使用动态关键字。以下是一些有效的使用动态关键字的用例:https://msdn.microsoft.com/en-us/library/dd264736.aspx

如果确实需要使用动态关键字,我建议您对代码进行检测并确定哪些部分占用了最多的内存。事实上,使用动态会增加您的内存消耗,因为它需要执行各种查找, 但要出现内存不足异常,您需要使用大量动态变量来处理许多未知类型。

有很多不同的方法可以调用未知类型的方法,测量和调整瓶颈是正确的方法。

PS:此外,发布一些代码片段会有很大帮助。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接