DLL, отображение памяти, базовый адрес, использование памяти и.NET?

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

Мой вопрос о DLL и.NET, в основном. У нас есть приложение, которое использует довольно много памяти, и мы пытаемся выяснить, как правильно это измерить, особенно когда проблема в основном возникает на клиентских компьютерах.

Меня поразило то, что у нас есть довольно большие сборки.NET с сгенерированным ORM-кодом.

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

Вопрос в том, что происходит со сборкой.NET. Эта DLL содержит IL, и хотя эта ее часть может использоваться приложениями, как насчет кода JITted, полученного из этого IL? Это поделился? If not, how do I measure to figure out of this is actually contributing to the problem or not? (Yes, I know, it will contribute, but I'm not going to spend much time on this until it is the biggest problem).

Also, I know that we haven't looked at the base address for all the.NET assemblies in our solution, is it necessary for.NET assemblies to do so? And if so, are there some guidelines available on how to determine these addresses?

Any insight into this area would be most welcome, even if it turns out that this is not a big problem, or not even a problem at all.


Edit: Just found this question: .NET assemblies and DLL rebasing which partially answers my question, but I'd still like to know how JITted code factors into all of this.

It appears from that question and its accepted answer that the JITted code is placed on a heap, which means that each process will load up the shared binary assembly image, and produce a private JITted copy of the code inside its own memory space.

Есть ли способ для нас измерить это? Если это приводит к большому количеству кода, нам нужно больше посмотреть на сгенерированный код, чтобы выяснить, нужно ли его настраивать.


Изменить: Добавлен более короткий список вопросов здесь:

  1. Есть ли смысл в том, чтобы убедиться, что базовые адреса сборок.NET уникальны и не перекрываются, чтобы избежать перебазирования dll, которая будет в основном использоваться для извлечения кода IL для JITting?
  2. Как я могу измерить, сколько памяти используется для кода JITted, чтобы выяснить, действительно ли это проблема или нет?

Ответ Brian Rasmussen здесь указывает на то, что JITting будет производить копии JITted-кода для каждого процесса, как я и ожидал, но что перебазирование сборок будет иметь эффект в отношении уменьшения использования памяти. Мне придется копаться в инструментах WinDbg+SoS, которые он упоминает, что-то, что у меня было в моем списке некоторое время, но теперь я подозреваю, что не могу больше откладывать это:)


Изменить: Некоторые ссылки, которые я нашел по этому вопросу:

3 ответа

Решение

Это для вопроса 1)

Свернутый код помещается в специальную кучу. Вы можете проверить эту кучу, используя !eeheap команда в WinDbg + SoS. Таким образом, каждый процесс будет иметь свою собственную копию объединенного кода. Команда также покажет вам общий размер кучи кода.

Дайте мне знать, если вы хотите получить дополнительную информацию о получении этой информации из WinDbg.

Это для вопроса 2)

Согласно книге Эксперт.NET 2.0 IL Сборка .reloc часть PE-файла pure-IL содержит только одну запись исправления для заглушки запуска CLR. Таким образом, количество исправлений, необходимых для управляемой DLL во время перебазирования, довольно ограничено.

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

Я не уверен, насколько точна следующая информация с более новыми версиями.NET и / или Windows. MS могла решить некоторые проблемы с загрузкой / совместным использованием DLL с первых дней существования.NET. Но я считаю, что многое из следующего по-прежнему применимо.

В сборках.NET многие преимущества совместного использования страниц между процессами (и между сеансами сервера терминалов) исчезают, потому что JIT должен писать собственный код на лету - нет файла изображения для резервного копирования собственного кода. Таким образом, каждый процесс получает свои собственные, отдельные страницы памяти для объединенного кода.

Это похоже на проблемы, вызванные неправильным основанием библиотек DLL - если ОС требуется выполнять исправления для стандартной библиотеки DLL Win32, когда она загружена, страницы памяти для исправленных частей не могут использоваться совместно.

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

Можно помочь разделить страницы памяти со сборкой.NET с помощью ngen. но это влечет за собой свои проблемы.

Посмотрите эту старую запись в блоге Джейсона Зандера для некоторых деталей:

http://blogs.msdn.com/jasonz/archive/2003/09/24/53574.aspx

У Ларри Остермана есть неплохая статья в блоге о совместном использовании страниц DLL и эффектах исправлений:

http://blogs.msdn.com/larryosterman/archive/2004/07/06/174516.aspx

Я думаю, что вы запутались из-за общих сборок и библиотек и пространства памяти процесса.

Как.NET, так и стандартная Win32 DLL совместно используют код между различными процессами, использующими их. В случае.NET это верно только для библиотек DLL с одинаковой сигнатурой версии, так что две разные версии одной и той же библиотеки DLL могут быть загружены в память одновременно.

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

Так что да, на самом деле код DLL загружается один раз и распределяется между вызывающими, но инструкции кода (и, следовательно, распределения) размещаются отдельно в пространстве вызывающего процесса.

Изменить: Хорошо, давайте посмотрим, как JIT работает со сборками.NET.

Когда мы говорим о JIT-коде, процесс относительно прост. Внутренне существует структура, называемая таблицей виртуальных методов, которая в основном содержит виртуальный адрес, который будет вызываться во время вызова. В.NET JIT работает, в основном, редактируя эту таблицу так, чтобы каждый отдельный вызов перенаправлялся в JIT-компилятор. Таким образом, каждый раз, когда мы вызываем метод, в который входит JIT и компилирует код с фактическими машинными инструкциями (следовательно, Just In Time), после того, как это будет сделано, JIT возвращается к VMT и заменяет старую запись, которая вызвала ему указать сгенерированный код низкого уровня. Таким образом, все последующие вызовы будут перенаправлены в скомпилированный код (поэтому мы просто скомпилируем один раз). Таким образом, JIT не вызывается каждый раз, и все последующие вызовы будут перенаправлены на один и тот же скомпилированный код. Для библиотек DLL процесс, вероятно, будет таким же (хотя я не могу вас полностью заверить).

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