Here be dragons: Управление памятью в Windows как оно есть [2/3]

:


Каталог:
Один
Два
Три

Оказывается длинные опусы нужно разбивать. А я то думал «многосерийные» топики публикуют исключительно для зарабатывания рейтинга :-)

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

Немного о том, как страницы попадают из рабочих наборов в эти самые списки.
Попадают разными способами. Один уже рассмотрен: кто-то явно вызвал EmptyWorkingSet для процесса. Это происходит нечасто. Обычно же это случается при помощи так называемого тримминга (trimming).

Когда система приближается к одному из установленных лимитов на свободную память, она начинает эту память освобождать. Во первых система находит процессы, максимально превысившие свой лимит на размер рабочего набора. Для этих процессов запускается процесс «старения» страниц (aging), для определения какие из страниц меньше всего используются. После этого, самые старые страницы «подрезаются» в standby или modified список.

Из той же лекции:

На диаграмме видно еще Delete page, которая происходит когда приложение просто освобождает выделенную память — с содержимым этих страниц попросту уже ничего нельзя делать, потому как по всем правилом оно не валидно и даже если новая память будет выделена по тому же адресу — это должна быть новая память.

Pagefile


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

Миф: Для повышения производительности нужно уменьшать количество обращений к пейджфайлу.
На самом деле: Для повышения производительности нужно уменьшать количество обращений К ДИСКУ. Пейджфайл является почти таким же файлом как и остальные.

Миф: Винда использует пейджфайл, даже если свободной памяти еще завались.
На самом деле: В пейджфайл страница может попасть только из modified списка. В modified список — при подрезке редко используемых страниц у разросшихся приложений. После того, как страница сброшена, она остается в standby списке и перечитываться не будет. Память никогда не берется из standby списка, если еще есть free или zeroed (то есть кешированные данные никогда не выбрасываются, если еще есть страницы вообще без данных). Standby список имеет 8 уровней приоритета (которыми до некоторой степени может управлять как само приложение, так и Superfetch, осуществляющий динамическое управление приоритетами страниц на основе анализа реального использования файлов/страниц), если не остается вообще никакого выбора — винда первым делом выбрасывает кеш самого низкого приоритета.
Вот пример получасовой работы в обычном режиме:

Прошу отметить, что файлы отсортированы по названию и между kernel.etl и «Program Files» должен был бы быть pagefile.sys.

Миф: Но винда сама признается, что использует пейджфайл.
На самом деле: Рассмотрено выше. Чаще всего речь идет о Commit Charge, который можно назвать «использованием», но совершенно не в том, смысле в котором это обычно принято понимать. В большинстве случаев при отсутсвии необходимости, приватные страницы (подлежащие сбросу в пейджфайл) будут сидеть в modified списке (даже если попадут туда) практически неограниченно долго.

Миф: Отключение пейджфайла улучшает производительность системы.
На самом деле: В редких случаях, приложение, которое злоупотребляет памятью и не понижает приоритет страниц для своих потоков может привести к понижению отзывчивости, однако в подавляющем большинстве случаев производительность только повышается за счет выгрузки неиспользуемого «хлама» в чулан и использовании освободившегося места для хранения более актуальных данных (тем самым снижая общее количество обращений к диску).

Приоритеты памяти и ввода/вывода


Чем ниже I/O приоритет потока, выполняющего запись/чтение, тем меньше он мешает нормальной работе (в целом, как и случае с приоритетами потоков используется round robin алгоритм для операций, ожидающих с наивысшим приоритетом, при этом небольшая часть пропускной способности отдается операциям с низким приоритетом в целях борьбы со starvation — на русский переводится не совсем адекватно). Запустите встроенный дефрагментатор. Resource Monitor покажет 100% загрузку диска, однако система при этом если и теряет в отзывчивости, то на глаз я этого заметить не могу.
Про приоритеты памяти я уже упоминал. Независимо от i/o приоритета, существует page приоритет (Page Priority). Каждая физическая страница в системе (а если точнее, то данные, которые она содержит в каждый момент времени) имеет приоритет. Когда страница перемещается в Standby list она попадает в один из восьми отдельных списков, соответствующий ее приоритету. Данные с более высоким приоритетом не могут быть замещены данными с более низким.

Prefetch и Superfetch


Prefetch появился в XP и является способом ускорить запуск приложений. Вот утилита, позволяющая просмотреть содержимое pf файлов. Собственно говоря там находятся имена и смещения файлов, к которым соответствующее приложение обращается в первые несколько (кажется 10) секунд после запуска. Так как запуск зачастую сопровождается бешенным чтением данных с диска, то упорядочивание этих чтений уменьшает количество head seek-ов и тем самым ускоряет запуск. Работает из службы SysMain. Помимо префетча и суперфетча этот сервис отвечает еще за ReadyBoot (который помогал ускорять загрузку в одном из предыдущих постов), ReadyBoost (штука довольно бесполезная на системах с большим количеством ОЗУ, но очень полезная на low-end системах) и ReadyDrive (это такой ReadyBoost, но с гарантией, что «флешка» не будет выниматься между перезагрузками — позволяет сильно ускорить основные операции не прибегая к полной замене HDD на SSD — используя так называемые гибридные или H-HDD: скорость SSD по цене HDD).

С суперфетчем все несколько сложнее. Мало кто понимает его назначение, но каждый норовит обвинить этот сервис в использовании памяти во время Idle и в чтении «чего то» с диска. Для начала чтение:

Осуществляется с background приоритетом и практически незаметно при использовании. Если раздражает мигание светодиода — его можно заклеить изолентой. Более того, это чтение загружает кеш нужными данными (можно посмотреть на увеличение standby после загрузки/просыпания даже при том, что пользователь не делает никаких активных операций с диском). Эти данные загруженные сейчас с низким приоритетом и с хорошим упорядочением, потом сохранят несколько секунд и бешенное перемещение головок при старте какого нибудь приложения. Речь идет чаще всего о загрузке данных на 6-7 уровни приоритетов (да, I/O операции с низким приоритетом используются для загрузки страниц в Standby списки высоких уровней), которые могут быть вытеснены только в самом крайнем случае, когда памяти не хватает уже ни на что.

Память этот сервис использует для хранения истории обращений к диску/страницам памяти. Периодически (в Idle) он выделяет больше для того, чтобы произвести анализ этих данных и построить долгосрочный план действий (а также бутплан на следующую загрузку). Кроме того, он производит динамическое перераспределение приоритетов страниц. Вот например, что происходит на моей домашней машине, на которой я частенько пользуюсь дотнетом (в виде powershell):

Там в списке предствлены страницы с приоритетами от 1 до 6 (7 — это специальный приоритет, который никогда не меняется). А вот свежеустановленная виртуальная машина, на которой powershell был запущен раз в жизни за несколько секунд до этого:

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

Видно, что все страницы понизили свой приоритет.

Копирование больших файлов


Здесь все просто. Запускаем копирование исошника. Видим, что копирование производится с нормальным I/O приоритетом:

Page priority потока я не сохранил, но могу с большой долей уверенности сказать, что там тоже «норма» — то есть 5. Значит ли это, что копирование файлов рано или поздно повыкидывает из кеша всех остальных? Проверим:

Все закешировалось с приоритетом 2 даже несмотря на то, что чтение осуществлялось с приоритетом 5. В чем же дело? А вот в чем:

Для операций копирования используется флаг FILE_FLAG_SEQUENTIAL_SCAN, который приводит к понижению приоритета кеша по сравнению с базовым, к использованию только одного VACB для кеширования и к более агрессивному read-ahead-у.