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

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

В основном мой вопрос касается библиотек 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, о которых он упоминает, что-то, что у меня было в моем списке некоторое время, но теперь я подозреваю, что не могу больше откладывать это :)


Изменить. Я нашел несколько ссылок по этой теме:


person Lasse V. Karlsen    schedule 26.01.2009    source источник


Ответы (3)


Это к вопросу 1)

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

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

Это касается вопроса 2)

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

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

person Brian Rasmussen    schedule 26.01.2009
comment
Хорошо, это выглядит многообещающе, по крайней мере, мы можем получить некоторые цифры по этому поводу. Спасибо! - person Lasse V. Karlsen; 26.01.2009
comment
Я полагаю, основываясь на других ответах здесь, что мне нужно перебазировать, если не для чего-то еще, кроме как избежать частных копий dll вместо общих сопоставленных ссылок. Спасибо за информацию! - person Lasse V. Karlsen; 26.01.2009
comment
Надеюсь, вы обратили внимание на этот вопрос, Брайан, знаете ли вы, есть ли в Интернете сайт, на котором я могу купить PDF-версию этой книги? Нашел только сайт APRESS, который доставляет файлы, защищенные паролем, что звучит громоздко, скорее, у меня есть тот, который имеет цифровую подпись к моей учетной записи Adobe ... - person Lasse V. Karlsen; 11.02.2009
comment
У меня есть бумажная копия, но я думаю, что есть и версия в формате PDF. Я помню, как видел PDF-файл всего за ... на обратной стороне. Я проверю, когда вернусь домой (на работе нет книги). - person Brian Rasmussen; 11.02.2009
comment
Что ж, я должен был догадаться. Книга доступна в электронном виде в Apress, так что я думаю, вы уже в курсе. Однако я нашел пресс-релиз, в котором говорилось, что книги Apress доступны в Safari, так что это может быть другим вариантом. - person Brian Rasmussen; 11.02.2009

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

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

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

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

С помощью 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

person Michael Burr    schedule 26.01.2009
comment
Я посмотрю на эти ссылки, ngen был одним из вариантов, который мы включили в список вещей, которые нужно изучить, если это окажется проблемой. Спасибо! - person Lasse V. Karlsen; 26.01.2009

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

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

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

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

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

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

person Jorge Córdoba    schedule 26.01.2009
comment
Но разделяют ли они код? Я предполагаю, что они будут совместно использовать инструкции IL, поскольку они будут загружены как часть двоичного образа DLL, но этот код должен быть JIT перед выполнением, является ли он каким-либо образом совместно используемым? Простите, если вы ответите на это, то я не понимаю вашего ответа. - person Lasse V. Karlsen; 26.01.2009