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

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

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

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

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

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

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

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


Изменить: просто нашел этот вопрос: сборки .NET и перезагрузка DLL, которые частично отвечают на мой вопрос, но я все равно хотел бы знать, как JIT-код ко всем факторам.

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

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


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

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

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


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

Ответ 1

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

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

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

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

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

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

Ответ 2

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

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

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

Однако, даже если джит-код не может быть разделен, есть преимущество для перезагрузки .NET DLL, потому что 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

Ответ 3

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

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

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

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

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

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