Переполнение памяти: увеличивающееся количество Microsoft.CSharp.RuntimeBinder.Semantics

В настоящее время мы ищем утечки памяти в нашем приложении. Когда мы выполняем какую-либо операцию (загружаем и закрываем один проект внутри нашего приложения), мы знаем, что объем памяти всегда немного увеличивается.

Мы уже нашли много из них, но сейчас 10+ наиболее растущих классов (согласно нашему инструменту ANTS Memory Profiler 8.2):

  • 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.

Проблема в том, что когда мы выполняем "открытие / закрытие" проекта, мы проходим большую часть (большую часть) нашего кода.

РЕДАКТИРОВАТЬ Одна часть нашего приложения использует dynamic Ключевое слово для некоторых ресурсов, оно может быть связано. Класс здесь не одноразовый, я должен сделать что-то особенное с ними?

РЕДАКТИРОВАТЬ 2

Я уверен, что это связано с моим dynamic вещи, кажется, что C# создает кеш при использовании динамического. Но в настоящее время я не знаю, почему он растет (я загружаю одни и те же классы все время, и у меня всегда будет одна и та же подпись), и как это очистить.

2 ответа

Я столкнулся с точно такой же проблемой сегодня, профилируя утечки памяти в моем приложении RepoZ. Этот инструмент должен запускаться в фоновом режиме, проверять репозитории Git и периодически обновлять заголовки окон Windows Explorer. Последняя задача должна сделать несколько вызовов COM для "Shell.Application", чтобы найти окна проводника и определить путь, на который они в данный момент указывают.

Используя dynamic Ключевое слово, как это...

dynamic shell = Activator.CreateInstance(...);
foreach (object window in shell.Windows())
{ 
    var hwnd = window.Hwnd;
    ...
}

... Я получил такой дамп памяти через несколько часов:

ComBridge

Чтобы решить эту проблему, я написал небольшой вспомогательный класс под названием "Combridge", который предназначен для освобождения COM-объектов и обеспечивает довольно простой доступ к методам и свойствам базового COM-объекта. Это очень легко и просто, ничего особенного здесь. Он использует Reflection для COM-объектов, поэтому наблюдается некоторая потеря производительности (см. Ниже).

При этом пример кода сверху выглядит так:

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 открытых окон Explorer. Для каждого окна 4 метода или свойства вызываются для этого COM-объекта. Итак, мы говорим о 40.000 COM-звонках.

Длительность возросла с ~2500 мс (dynamic) до ~6000 мс (Combridge). Это от 0,062 мс до 0,150 мс для каждого звонка.

Таким образом, сейчас требуется около 2,4 раза больше времени, чтобы закончить.

Это важно, я знаю. Но это нормально для моих требований и утечка памяти исчезла.

Вот и все - я хотел поделиться этой историей с вами, надеюсь, вы сможете использовать этот класс (или его улучшенную версию), чтобы выйти из динамического ада.

~ Update ~

Через 10 часов RepoZ по-прежнему работает с очень постоянным объемом памяти.

Таким образом, с 10 открытыми окнами Explorer, 4 COM-вызовами в каждом окне и всем этим циклом два раза в секунду, RepoZ создал около 72 000 экземпляров COM и в общей сложности около 2,880 000 COM-вызовов без увеличения потребления памяти.

Я думаю, мы можем сказать, что проблема действительно идет с dynamic,

Динамическое ключевое слово следует использовать редко, поскольку в большинстве случаев можно найти обходные пути, не требующие этого.

Основываясь на вашем приложении, лучший совет - тщательно продумать, можете ли вы разработать свое решение, чтобы избежать динамики. Вот некоторые допустимые варианты использования для динамического: https://msdn.microsoft.com/en-us/library/dd264736.aspx

Учитывая, что вам действительно нужно использовать динамические, я бы предложил инструментальные средства вашего кода и выяснить, какие части занимают больше всего памяти. Действительно, использование динамического увеличения увеличивает потребление памяти на основе того факта, что он должен выполнять все виды поиска, но для исключения из-за нехватки памяти вам нужно будет использовать много динамических переменных для большого количества неизвестных типов.

Существует много разных способов вызова методов для неизвестных типов, и измерение и настройка узких мест - это путь.

PS: Кроме того, размещение некоторых фрагментов кода очень помогает.

Другие вопросы по тегам