DLL, отображение памяти, базовый адрес, использование памяти и .NET? - PullRequest
10 голосов
/ 26 января 2009

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

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

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

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

Вопрос в том, что происходит со сборкой .NET. Эта DLL содержит IL, и хотя эта ее часть может использоваться совместно приложениями, как насчет кода JITted, полученного из этого IL? Это общий? Если нет, то как мне измерить, чтобы выяснить, действительно ли это способствует проблеме или нет? (Да, я знаю, это будет способствовать, но я не собираюсь тратить на это много времени, пока это не станет самой большой проблемой).

Кроме того, я знаю, что мы не проверяли базовый адрес для всех сборок .NET в нашем решении, необходимо ли для сборок .NET это делать? И если да, то есть ли какие-то рекомендации по определению этих адресов?

Любое понимание этой области будет приветствоваться, даже если окажется, что это не большая проблема или вообще не проблема.


Редактировать : Только что нашел этот вопрос: .NET сборок и перебазирование DLL , что частично отвечает на мой вопрос, но я все еще хотел бы знать, как JITted код влияет на все это .

Из этого вопроса и принятого ответа следует, что код JITted помещается в кучу, что означает, что каждый процесс загрузит общий двоичный образ сборки и создаст частную копию кода JITted внутри своего собственного пространства памяти. .

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


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

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

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


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

Ответы [ 3 ]

6 голосов
/ 26 января 2009

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

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

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

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

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

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

3 голосов
/ 26 января 2009

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

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

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

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

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

См. Этот старый пост в блоге Джейсона Зандера для некоторых деталей:

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

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

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

0 голосов
/ 26 января 2009

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

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

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

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

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

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

...