Сборка сборок Dynami c: при переходе от одной сборки к нескольким модулям и сборкам использование памяти без RunAndCollect утроилось - PullRequest
0 голосов
/ 14 января 2020

В нашей компании есть несколько собственных языков и компиляторов / декомпиляторов, так что мы активно используем возможности генерации и отражения кода C# dynamici c. Большая часть кода, который мы пишем, предназначена просто для динамического создания кода во время выполнения или на нашем проприетарном языке сценариев.

В определенный момент мы обнаружили, что добавление типов в динамическую c сборку масштабируется O (n ^ 2), предположительно потому, что вся сборка переписана? Это означало, что когда было создано более 10 000 типов, наше программное обеспечение стало работать медленнее. Затем мы сохранили одну динамическую сборку и модуль c и добавляли в нее типы всякий раз, когда мы генерировали динамический код c (с некоторыми исключениями для DynamicMethods).

Чтобы перенести это, мы сделал градиентный спуск на оптимальной комбинации типов, модулей и сборок для динамического генерирования, чтобы мы могли получить производительность O (n).

graph of assembly loading optimisations

Мы реализовал слой абстракции, который автоматически создавал модули / сборки по мере необходимости в соответствии с оптимальным способом, показанным результатами, для случаев, когда мы создаем динамические типы c. Это имело желаемые преимущества в производительности. Эти отражающие сборки / модули / типы объектов всегда имеют сильную ссылку, скрытно скрытую за ними в различных словарях и хэш-наборах.

Однако через несколько месяцев начались очень странные проблемы встречающаяся. Первый был чрезвычайно прерывистым и происходил каждые 3/4 пробега. Это было роковое исключение ExecutionEngineException с кодом ошибки 80131506. Это очень сбивало с толку, и мы изначально думали, что это вызвано обновлением с. NET 451 до. NET 472 (у нас очень иногда возникала эта проблема раньше) , В конце концов мы отследили это до установки System.Reflection.Emit.AssemblyBuilderAccess в RunAndCollect. Когда мы изменили это на «Run», у нас больше никогда не возникало этой проблемы. Имейте в виду, что мы всегда держим сильные ссылки на эти сборки, поэтому очень сложно понять, как это может решить проблему.

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

При таком поведении кажется, что мы производим слишком много динамических c сборок, и их нужно собирать, иначе у нас не хватит памяти. Однако мне трудно поверить, что динамические c сборки могут занимать много места, и я не понимаю, как их можно собрать, учитывая, что мы поддерживаем объекты, которые являются экземплярами типов в динамических c сборках (которые предположительно содержат ссылку на объекты Type) и прямые прямые ссылки на объекты отражения этих динамических c сборок в любом случае.

Поэтому мой вопрос состоит в том, как именно работает сборка динамических c сборок. NET framework, и почему мы получаем эти исключения FatalExecutionEngine Exception / OutOfMemory?

enter image description here

1 Ответ

1 голос
/ 16 января 2020

Я думаю, что код получаемой вами фатальной ошибки (80135106) часто вызывается циклическими ссылками на типы в динамических c сборках (поэтому сборка 1 содержит типы, которые ссылаются на типы в сборке 2 - возможно, через частные поля, но Сборка 2 в конечном итоге содержит типы, ссылающиеся на сборку 1). Это может легко вызвать переполнение стека при сборке сборок.

См. Эту ссылку для случая, когда это произошло через XAML Visual Studio 2008 аварийно завершает работу при отображении представления XAML. Как получить больше информации?

...