В микросервисном мире добавление новой функциональности осуществляется путем написания нового сервиса. При этом стоимость добавления новой единицы составляет минимум 150 Мб оперативной памяти, хотя нашего кода в нем достаточно мало и используются, как правило, одни и те же сборки с небольшими отличиями в версиях. В этой статье будут показаны пути оптимизации исключительно за счет настроек сервера, таким образом переписывание и перекомпиляция приложений не потребуется. Будет достигнут результат 25 Мб в среднем на один микросервис. В качестве первого шага следует определить, чем же занята память процесса, и значима ли данная оптимизация в конкретном случае. Для анализа структуры памяти нам понадобится одно приложение от Sysinternals — VMMap. Откроем VMMap и рассмотрим структуру конкретного w3wp процесса, где хостится наш сайт. В верхней части приложения мы видим 3 горизонтальные диаграммы:
Committed — память “доступная” для процесса
Private Bytes — виртуальная память
Working Set — физическая память (RAM)  Цветами в диаграммах обозначены различные типы памяти, приведем лишь некоторые из них, составляющие топ 4 для Working Set: Image — исполняемые файлы, такие как .exe или .dll, которые могут быть загружены в процесс image loader
Managed Heap — память выделенная .NET runtime, обычно содержащая данные приложения
Page Table — область памяти, отвечающая за отображение виртуальных адресов в физические
Heap — память выделенная С или C++ runtime, обычно содержащая данные приложения Для каждого типа памяти можно получить детальную информацию о том, как и где он выделен: Total WS — кол-во физической памяти
Private WS — кол-во физической памяти, которое не может быть использовано совместно (shared) с другими процессами
Sharable WS — кол-во физической памяти, которое может быть использовано совместно с другими процессами
Shared WS — кол-во физической памяти, которое в настоящее время используется совместно с другими процессами. Подробное описание приложения VMMap см. по ссылкам [1], [2]. Но вернемся к нашему w3wp процессу и рассмотрим топ 3 по типам памяти:  73 из 220 Мб отходит к Image
60 из 220 мб к Heap
58 из 220 Мб к Managed Heap Следует дополнительно отметить, что в Managed Heap включается память алоцированная JIT. Ее можно вычислить как размер Managed Head минус размер GC. В нашем распоряжении имеется инстанс Amazon t2.large с 8 Gb оперативной памяти, 2 ядрами Intel Xeon ES-2676 v3 2.40GHz и Windows Server 2012 R2 в качестве ОС. Внутри 47 микро-сервисов под управлением IIS. В каждом микросервисе есть контроллер с методом, возвращающим версию сборки (см. пример ниже). Именно его мы будем вызывать для “прогрева” сайтов. Теперь последовательно запустим и “прогреем” наши сайты, чтобы увидеть картину в целом. Для автоматизации этого процесса приведем скрипт на PowerShell (ссылка на github).
В результате 47 сайтов запустились за 6 минут 43 секунды и заняли всю оперативную память сервера. Средний размер одного микросервиса составил 7 Гб / 47 = 152 Мб (1 Гб на ОС).   Теперь рассмотрим w3wp процесс через призму ProcessExplorer. Перейдя на вкладку .NET Assemblies, мы увидим 3 т.н. application domains: sharedDomain, defaultDomain и siteDomain (/LM/W3SVC/3/). Последнее справедливо, впрочем, только когда мы создаем для каждого сайта свой application pool.
 Какие изменения последуют в случае, если мы объединим несколько сайтов в один application pool? Будут добавлены N appDomains с именами /LM/W3SVC/3/… — siteDomain, где N число добавленных сайтов. Теперь обратим внимание, что часть сборок находятся в sharedDomain, а часть сборок в appDomains. При этом сборки находящиеся в sharedDomain присутствуют в приложении в единственном экземпляре, а сборки находящиеся siteDomains будут загружаться независимо для каждого домена. Следует отметить, что сборки находящиеся в sharedDomain имеют флаг DomainNeutral и путь, который указывает либо в GAC (Global Assembly Cache), либо в кэш нативных образов (native images). MSDN [3] дает следующее определение “Domain Neutral Assembly”: В книге “Pro .NET Performance: Optimize Your C# “ [4] рекомендуется помещать подписанные сборки (strong name assemblies) в GAC, в противном случае загрузка сборки потребует её полного чтения для подтверждения ее цифровой подписи.
Последнее также облегчает создание нативных образов для всех приложений, ссылающихся на эту сборку. Следовательно для облегчения размера приложения нам потребуется поместить подписанные сборки в GAC и сгруппировать сайты в applicationPools по их профилю нагрузки.  Для решения первой задачи существует консольное приложение aspnet_intern.exe, поставляемое вместе с Windows SDK.
Приложение анализирует используемые сборки и далее копирует их в указанный каталог, при этом заменяя исходный файл на символическую ссылку, чем экономит место на диске и ускоряет запуск w3wp процесса [5]. Пример:
откроем командную строку в режиме администратора и перейдем в каталог Windows SDK  для получения справки о всех возможных опциях выполним для получения списка всех сборок без их фактического интернирования выполним *для 32 битного приложения используйте путь: C:\Windows\Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files
непосредственно для интернирования сборок к каталог C:\ASPNETCommonAssemblies выполним следующую команду  Пример на PowerShell Для загрузки сборок в GAC нам вновь потребуется обратиться к Windows SDK, но уже за приложением gacutil.exe. После интернирования мы получили каталог, содержащий подписанные и неподписанные сборки.
Так как инсталлировать в GAC можно только те из них, которые подписаны, то нам потребуется написать небольшой скрипт на Powershell для их инсталляции:  Проводить эксперимент с объединением сайтов в applicationPools будем вручную, хотя последнее возможно сделать средствами PowerShell через модуль WebAdministration [7].
В конкретном случае мы будем объединять 47 микросервисов в 6 applicationPools по их профилю нагрузки. Для бэкапа, восстановления или переноса конфигурации на другие машины, рекомендую обратить внимание на appcmd.exe [7], [8] После проделанных изменений мы имеем следующую картину: 47 сайтов "прогрелись" за 2 минуты и 33 секунды, что быстрее в 2.6 раза. Общий размер используемой оперативной памяти составил 4.1 Гб. При этом средний размер одного микросервиса составил 3.1 Гб / 47 = 67 Мб, что меньше в 2.2 раза. 
 Помимо совместного использования (sharing) сборок между доменами в рамках одного процесса возможно совместное использование сборок между различными процессами, но последнее требует создания нативного образа и помещения его в кэш. Для этой цели мы будем использовать ngen.exe [9]. Перечислим плюсы от использования нативных образов: Создание нативных образов возможно как для подписанных, так и для неподписанных сборок. Однако здесь есть нюансы: если сборка будет загружена не в sharedDomain, то она не будет иметь возможности совместно использоваться с другими appDomains. На данном этапе нам придется повторить все предыдущие шаги и выполнить один новый:  В результате мы увидим следующую картину:   Software developer