Время на прочтение8 мин
Количество просмотров180K
Каталог:
Один
Два
Три
Менеджер памяти (и связанные с ним вопросы контроллера кеша, менеджера ввода/вывода и пр) — одна из вещей, в которой (наряду с медициной и политикой) «разбираются все». Но даже люди «изучившие винду досконально» нет-нет, да и начинают писать чепуху вроде (не говоря уже о другой чепухе, написанной там же):
Грамотная работа с памятью!!! За все время использования у меня своп файл не увеличился ни на Килобайт. По этому Фаерфокс с 10-20 окнами сворачивается / разворачивается в/из трея как пуля. Такого эффекта я на винде добивался с отключенным свопом и с переносом tmp файлов на RAM диск.
Или к примеру μTorrent — у меня нет никаких оснований сомневаться в компетентности его авторов, но вот про работу памяти в Windows они со всей очевидностью знают мало. Не забываем и товарищей, производящих софт для слежения за производительностью и не имеющих ни малейшего понятия об управлении памятью в Windows (и поднявших по этому поводу истерику на пол интернета, на Ars-е даже был разбор полетов). Но самое потрясающее, что я видел всвязи с управлением памятью — это совет переместить pagefile на RAM-диск:
Из моих трех гигабайт под RAM disk был выделен один (на тот момент, когда на лаптопе еще была установлена XP), на котором я создал своп на 768МБ …
Цель данной статьи — не полное описание работы менеджера памяти (не хватит ни места ни опыта), а попытка пролить хоть немного света на темное царство мифов и суеверий, окружающих вопросы управления памятью в Windows.
Disclaimer
Сам я не претендую на то, чтобы знать все и никогда не ошибаться, поэтому с радостью приму любые сообщения о неточностях и ошибках.
Введение
С чего начать не знаю, поэтому начну с определений.
Commit Size — количество памяти, которое приложение запросило под собственные нужды.
Working Set (на картинке выше он так и называется Working Set) — это набор страниц физической памяти, которые в данный момент «впечатаны» в адресное пространство процесса. Рабочий набор процесса System принято выделять в отдельный «Системный рабочий набор», хотя механизмы работы с ним практически не отличаются от механизмов работы с рабочими наборами остальных процессов.
И уже здесь зачастую начинается непонимание. Если присмотреться, можно увидеть, что Commit у многих процессов меньше Working Set-а. То есть если понимать буквально, «запрошено» меньше памяти, чем реально используется. Так что уточню, Commit — это виртуальная память, «подкрепленная» (backed) только физической памятью или pagefile-ом, в то время как Working Set содержит еще и страницы из memory mapped файлов. Зачем это делается? Когда делается NtAllocateVirtualMemory (или любые обертки над heap manager-ом, например malloc или new) — память как бы резервируется (чтоб еще больше запутать, это не имеет никакого отношения к MEM_RESERVE, который резервирует адресное пространство, в данном же случае речь идет о резервировании именно физических страниц, которые система действительно может выделить), но физические страницы впечатываются только при фактическом обращении по выделенному адресу виртуальной памяти. Если позволить приложениям выделить больше памяти, чем система реально может предоставить — рано или поздно может случиться так, что все они попросят реальную страницу, а системе неоткуда будет ее взять (вернее некуда будет сохранить данные). Это не касается memory mapped файлов, так как в любой момент система может перечитать/записать нужную страницу прямо с/на диск(а).
В общем, суммарный Commit Charge в любой момент времени не должен превышать системный Commit Limit (грубо, суммарный объем физической памяти и всех pagefile-ов) и с этим связана одна из неверно понимаемых цифр на Task Manager-ах до Висты включительно.
Commit Limit не является неизменным — он может увеличиваться с ростом pagefile-ов. Вообще говоря, можно считать, что pagefile — это такой очень специальный memory mapped файл: привязка физической страницы в виртуальной памяти к конкретному месту в pagefile-е происходит в самый последний момент перед сбросом, в остальном же механизмы memory mapping-а и swapping-а очень схожи.
Working Set процесса делится на Shareable и Private. Shareable — это memory mapped файлы (в том числе и pagefile backed), вернее те части, которые в данный момент действительно представлены в адресном пространстве процесса физической страницей (это же Working Set в конце концов), а Private — это куча, стеки, внутренние структуры данных типа PEB/TEB и т.д. (опять таки, повторюсь на всякий случай: речь идет только той части кучи и прочих структур, которые физически находятся в адресном пространстве процесса). Это тот минимум информации, с которой уже можно что то делать. Для сильных духом есть Process Explorer, который показывает еще больше подробностей (в частности какая часть вот той Shareable действительно Shared).
И, самое главное, ни один из этих параметров по отдельности не позволяет сделать более менее полноценных выводов о происходящем в программе/системе.
Task Manager
Столбец «Memory» в списке процессов и практически вся вкладка «Performance» настолько часто понимаются неправильно, что у меня есть желание, чтоб Task Manager вообще удалили из системы: те, кому надо смогут воспользоваться Process Explorer-ом или хотя бы Resource Monitor-ом, всем остальным Task Manager только вредит. Для начала, собственно о чем речь
Начну с того, о чем я уже упоминал: Page File usage. XP показывает текущее использование pagefile-а и историю (самое забавное, что в статус баре те же цифры названы правильно), Виста — показывает Page File (в виде дроби Current/Limit), и только Win7 называет его так, чем оно на самом деле является: Commit Charge/Commit Limit.
Эксперимент. Открываем таск менеджер на вкладке с «использованием пейджфайла», открываем PowerShell и копируем в него следующее (для систем, у которых Commit Limit ближе, чем на 3 Гб от Commit Charge можно в последней строчке уменьшить 3Gb, а лучше увеличить pagefile):
add-type -Namespace Win32 -Name Mapping -MemberDefinition @" [DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr CreateFileMapping( IntPtr hFile, IntPtr lpFileMappingAttributes, uint flProtect, uint dwMaximumSizeHigh, uint dwMaximumSizeLow, [MarshalAs(UnmanagedType.LPTStr)] string lpName); [DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr MapViewOfFile( IntPtr hFileMappingObject, uint dwDesiredAccess, uint dwFileOffsetHigh, uint dwFileOffsetLow, uint dwNumberOfBytesToMap); "@ $mapping = [Win32.Mapping]::CreateFileMapping(-1, 0, 2, 1, 0, $null) [Win32.Mapping]::MapViewOfFile($mapping, 4, 0, 0, 3Gb)
Это приводит к мгновенному повышению «использования свопфайла» на 3 гигабайта. Повторная вставка «использует» еще 3 Гб. Закрытие процесса мгновенно освобождает весь «занятый свопфайл». Самое интересное, что, как я уже говорил memory mapped файлы (в том числе и pagefile backed) являются shareable и не относятся к какому либо конкретному процессу, поэтому не учитываются в Commit Size никакого из процессов, с другой стороны pagefile backed секции используют (charged against) commit, потому что именно физическая память или пейджфайл, а не какой нибудь посторонний файл, будут использоваться для того, чтобы хранить данные, которые приложение захочет разместить в этой секции. С третьей стороны, после меппинга секции себе в адресное пространство, процесс не трогает ее — следовательно, физические страницы по этим адресам не впечатываются и никаких изменений в Working Set процесса не происходит.
Строго говоря, пейджфайл действительно «используется» — в нем резервируется место (не конкретное положение, а именно место, как размер), но при этом реальная страница, для которой это место было зарезервировано может находиться в физической памяти, на диске или И ТАМ И ТАМ одновременно. Вот такая вот циферка, признайтесь честно, сколько раз глядя на «Page File usage» в Task Manager-е Вы действительно понимали, что она означает.
Что же до Processes таба — там все еще по дефолту показывается Memory (Private Working Set) и несмотря на то, что он называется совершенно правильно и не должен вызывать недоразумений у знающих людей — проблема в том, что подавляющее большинство людей, которые смотрят на эти цифры совершенно не понимают, что они означают. Простой эксперимент: запускаем утилилиту RamMap (советую скачать весь комплект), запускаете Task Manager со списком процессов. В RamMap выбираете в меню Empty->Empty Working Sets и смотрите на то, что происходит с памятью процессов.
Если кого-то все еще раздражают циферки в Task Manager-е, можете поместить следующий код в профайл павершелла:
add-type -Namespace Win32 -Name Psapi -MemberDefinition @" [DllImport("psapi", SetLastError=true)] public static extern bool EmptyWorkingSet(IntPtr hProcess); "@ filter Reset-WorkingSet { [Win32.Psapi]::EmptyWorkingSet($_.Handle) } sal trim Reset-WorkingSet
После чего станет возможно «оптимизировать» использование памяти одной командой, например для «оптимизации» памяти, занятой хромом: ps chrome | trim
Или вот «оптимизация» памяти всех процессов хрома, использующих больше 100 Мб физической памяти: ps chrome |? {$_.WS -gt 100Mb} | trim
Если хотя бы половина прочитавших отметет саму идею о подобной «оптимизации», как очевиднейший абсурд — можно будет сказать, что я не зря старался.
Кеш
В первую очередь отмечу, что кеш в Windows не блочный, а файловый. Это дает довольно много преимуществ, начиная от более простого поддержания когерентности кеша например при онлайн дефрагментации и простого механизма очистки кеша при удалении файла и заканчивая более консистентными механизмами его реализации (кеш контроллер реализован на основе механизма memory mapping-а), возможностью более интеллектуальных решений на основе более высокоуровневой информации о читаемых данных (к примеру интеллектуальный read-ahead для файлов открытых на последовательный доступ или возможность назначать приоритеты отдельным файловым хендлам).
В принципе из недостатков я могу назвать только значительно более сложную жизнь разработчиков файловых систем: слышали о том, что написание драйверов — это для психов? Так вот, написание драйверов файловых систем — для тех, кого даже психи считают психами.
Если же описывать работу кеша, то все предельно просто: когда файловая система запрашивает у кеш-менеджера какую нибудь часть файла, последний просто меппит часть этого файла в специальный «слот», описываемый структурой VACB (посмотреть все смепленные файлы можно из отладчика ядра с помощью расширения !filecache) после чего просто выполняет операцию копирования памяти (RtlCopyMemory). Происходт Page Fault, так как сразу после отображения файла в память все страницы невалидны и дальше система может либо найти необходимую страницу в одном из «свободных» списков либо выполнить операцию чтения.
Для того, чтобы понять рекурсию нужно понять рекурсию. Каким же образом выполняется операция чтения файла, необходимая для завершения операции чтения этого самого файла? Здесь опять все достаточно просто: пакет запроса на ввод/вывод (IRP) создается с флагом IRP_PAGING_IO и при получении такого пакета файловая система уже не обращается к кешу, а идет непосредственно к нижележащему дисковому устройству за данными. Все эти смепленные слоты идут в System Working Set и составляют ЧАСТЬ кеша.
Страница из лекции какого то токийского университета (эх, мне бы так):
На этом работа собственно кеш-менеджера заканчивается и начинается работа менедера памяти. Когда выше мы делали EmptyWorkingSet это не приводило ни к какой дисковой активности, но тем не менее, физическая память используемая процессом сокращалась (и все физические страницы действительно уходили из адресного пространства процесса делая его почти полностью невалидным). Так куда же она уходит после того, как отбирается у процесса? А уходит она, в зависимости от того, соответствует ли ее содержимое тому, что было прочитано с диска, в один из двух списков: Standby (начиная с Висты это не один список, а 8, о чем позже) или Modified:
Standby список таким образом — это свободная память, содержащая какие то данные с диска (в том числе возможно и pagefile-а).
Если Page Fault происходит по адресу, который спроецирован на часть файла, которая все еще есть в одном из этих списков — она просто возвращается обратно в рабочий набор процесса и впечатывается по искомому адресу (этот процесс называется softfault). Если нет — то, как и в случае со слотами кеш менеджера, выполняется PAGING_IO запрос (называется hardfault).
Modified список может содержать «грязные» страницы достаточно долго, но либо когда размер этого списка чрезмерно вырастает, либо по когда система видит недостаток свободной памяти, либо по таймеру, просыпается modified page writer thread и начинает частями сбрасывать этот список на диск, и перемещая страницы из modified списка в standby (ведь эти страницы опять содержат неизмененную копию данных с диска).
Upd:
Пользователь m17 дал ссылки на выступление Руссиновича на последнем PDC на ту же тему (хм, я честно его до этого не смотрел, хотя пост во много перекликается). Если понимание английского на слух позволяет, то чтение данного топика можно заменить прослушиванием презентаций:
Mysteries of Windows Memory Management Revealed, Part 1 of 2
Mysteries of Windows Memory Management Revealed, Part 2 of 2
Пользователь DmitryKoterov подсказывает, что перенос пейджфайла на RAM диск иногда действительно может иметь смысл (вот уж никогда б наверное и не догадался, если б не написал топик), а именно, если RAM-диск использует физическую память, недоступную остальной системе (PAE + x86 + 4+Gb RAM).
Пользователь Vir2o в свою очередь подсказывает что хотя при некоторых условиях и пожертвовав стабильностью системы ram-диск, использующий физическую память, невидимую остальной системе написать можно, но такое очень маловероятно.
Abusing the Process Working Set
Understanding the Working Set in Windows
The working set of a Windows process refers to the collection of pages in its virtual address space that the process has recently referenced. This set includes shared data, such as libraries used by multiple processes and private data unique to the process. The virtual memory manager optimizes performance by keeping only the necessary pages in memory, minimizing the overall demand on physical memory.
The virtual memory system in Windows is designed to balance efficiency and resource usage. By maintaining a working set, the system can avoid loading all possible pages into memory. This selective loading process ensures that memory is available for other processes and tasks, making the system more responsive.
However, not all memory pages are kept in the working set at all times. Pages that haven’t been used recently may be paged out to disk to free up memory for more active processes. When the application needs to access this paged-out memory unexpectedly, a page fault occurs.
What is a Page Fault?
A page fault happens when a process attempts to access a page not currently loaded into its working set. When this occurs, the operating system pauses the process, retrieves the required page from the disk, and loads it back into memory. Although this mechanism allows the system to manage memory efficiently, frequent page faults can degrade performance since accessing data from disk is slower than accessing it from memory.
Detecting Page Faults
Now that we understand what a page fault is, the next question is: how can we detect when one occurs? Fortunately, Windows provides tools to help us monitor memory activity. One such tool is the GetWsChanges
function in the Windows API. This function allows us to retrieve information about the pages that have been added to or removed from the working set of a specified process since its initialization.
Below is a C++ implementation of a working set watcher. It uses GetWsChangesEx
to gather the list of pages that have been added to or removed from the working set of the monitored process. It then checks if any of the faulting addresses are part of a predefined watch list and logs the details, including the thread ID and process information.
void watcher::watch( std::stop_token token ) const
{
std::vector< PSAPI_WS_WATCH_INFORMATION_EX > buffer( 100 );
while ( !token.stop_requested( ) )
{
const auto size = buffer.size( );
auto cb = static_cast< DWORD >( size * sizeof( PSAPI_WS_WATCH_INFORMATION_EX ) );
// Clear the buffer and resize it to the required size.
buffer.clear( );
buffer.resize( size );
if ( !GetWsChangesEx( handle, buffer.data( ), &cb ) )
{
const auto error = GetLastError( );
// This isn't an error, just no changes in the working set since the last call.
if ( error == ERROR_NO_MORE_ITEMS )
{
// Wait a bit until we try again
std::this_thread::sleep_for( 1s );
continue;
}
// Any other error code is a real error.
if ( error != ERROR_INSUFFICIENT_BUFFER )
throw std::runtime_error( std::format( "Failed to get working set changes: {0}", error ) );
// Resize the buffer to the required size.
buffer.resize( cb / sizeof( PSAPI_WS_WATCH_INFORMATION_EX ) );
continue;
}
// At this point, we have an array of pages that have been added or removed from the working set. Now
// lets see if we have a page that we care about.
for ( const auto& entry : buffer )
{
if ( entry.BasicInfo.FaultingPc == nullptr )
continue;
const auto faulting_va = reinterpret_cast< std::uintptr_t >( entry.BasicInfo.FaultingVa );
const auto faulting_page_va = page_align( faulting_va );
// Check if the current page is in the watch list.
if ( std::find( watch_list.begin( ), watch_list.end( ), faulting_page_va ) != watch_list.end( ) )
{
// Get the the process id of the thread that caused the fault.
const auto pid = get_process_id( entry.FaultingThreadId );
// If this is our process, then we can ignore it.
if ( pid == GetCurrentProcessId( ) )
continue;
// Print the faulting PC and the faulting VA.
std::println(
"[+] 0x{:x} (0x{:x}) was mapped by (TID: {}) @ {}",
faulting_page_va,
faulting_va,
entry.FaultingThreadId,
entry.BasicInfo.FaultingPc );
// Get the process path.
const auto path = get_process_path( pid );
// Print the process path.
std::printf( "\t--> %ws (PID: %lu)\n", path.c_str( ), pid );
}
}
// Wait a bit until we try again
std::this_thread::sleep_for( 1s );
}
}
This code is part of the loop body for a
std::jthread
which is why it has a stop token.
Creating and Managing Allocation
In the example above, the watch_list
contains a list of virtual addresses corresponding to memory pages that should not be paged back into the working set. These allocations are crucial for ensuring that specific memory regions remain outside the working set, reducing their impact on system performance and memory pressure.
To achieve this, all allocations referenced in the watch_list
must be created using the VirtualAlloc
function, a Windows API method that allows you to allocate memory in a process’s virtual address space. VirtualAlloc
provides control over the allocation size, memory protection, and type, allowing the creation of memory regions that can later be managed for paging.
Once these allocations are made, the pages need to be paged out of the working set. This can be done by calling the VirtualUnlock
function on the allocated memory region. VirtualUnlock
removes the specified pages from the process’s working set, forcing them to reside in the page file on disk rather than in physical memory. This prevents the pages from being accessed directly until they are paged back in as needed by the system.
template< typename T, typename... Args >
paged_ptr< T > make_paged( Args&&... args )
{
const auto buffer = VirtualAlloc( nullptr, sizeof( T ), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE );
if ( !buffer )
throw std::bad_alloc( );
const auto instance = new ( buffer ) T( std::forward< Args >( args )... );
return paged_ptr< T >( instance );
}
In the code snippet above, we allocate memory, ensuring that the region is reserved in the virtual address space and committed in physical memory. Below is the implementation for the constructor of the ws::paged_ptr
instance. This constructor is responsible for managing the allocation and ensuring the memory starts outside of the working set.
The key part of this design is that we use VirtualUnlock
during the construction phase to remove the memory page from the working set, making sure it’s paged out. This ensures the page won’t be in physical memory immediately after allocation, allowing the operating system to manage it based on usage patterns. Additionally, the memory is added to the watch list, so future accesses can be tracked for changes.
paged_ptr( T* instance ) : instance( instance ), locked( false )
{
// We initially unlock the memory so that the page is not in the working set.
if ( instance )
VirtualUnlock( reinterpret_cast< LPVOID >( instance ), sizeof( T ) );
// Add the instance to the watch list.
watcher::get( )->add( reinterpret_cast< std::uintptr_t >( instance ) );
}
Now we need a way to safely access this memory. This is where the implementation of the lock()
function. This function moves the memory into the working set and provides a shared pointer to access the memory safely. The design ensures that the memory is locked into the working set only when it is actually needed.
/// <summary>
/// Moves the memory into the working set and returns a shared pointer to the memory. This is the only way to access the memory.
/// </summary>
std::shared_ptr< T > lock( ) const
{
// If the current memory is not locked in the working set, we lock it.
if ( !locked && instance )
{
VirtualLock( reinterpret_cast< LPVOID >( instance ), sizeof( T ) );
locked = true;
}
return std::shared_ptr< T >(
instance,
[]( T* ptr )
{
// Now we call virtual unlock twice:
// 1. To unlock the memory.
// 2. To remove the page from the working set.
VirtualUnlock( reinterpret_cast< LPVOID >( ptr ), sizeof( T ) );
VirtualUnlock( reinterpret_cast< LPVOID >( ptr ), sizeof( T ) );
} );
}
We’ve created the ws::paged_ptr
data type and can use it in our code. Assuming the working set watcher is running simultaneously on a separate thread, we can use the pointer like so:
// Allocate a 10-byte array of paged memory
const auto& paged = ws::make_paged< std::uint8_t >( 10 );
// Lock the page in memory so that we can safely access it's data
if ( const auto& data = paged.lock( ) )
{
// Get the raw pointer to the data.
const auto& ptr = data.get( );
// Edit the memory
ptr[ 0 ] = 0xFF;
ptr[ 1 ] = 0xFF;
}
Our watcher captures all unauthorized access to memory protected by the ws::paged_ptr
data type and logs it to the console. Below, I’ve attached a screenshot of such a scenario, where ReClass (opens in a new tab) attempts to read from a remote address in our process.
Conclusion
In this post, we explored the concept of the working set in Windows processes and how it plays a crucial role in efficient memory management. By understanding how memory pages are loaded into and removed from the working set, we can manipulate the system’s memory management mechanisms to monitor specific regions of memory for external access.
The working set watcher we implemented takes advantage of the GetWsChangesEx
API to detect changes in the working set, allowing us to capture and log page faults triggered by external processes trying to access protected memory. This approach can be particularly useful in scenarios where securing memory from unauthorized access or external monitoring is critical, such as in anti-cheat systems, malware detection, or sensitive data protection.
Additionally, we introduced the ws::paged_ptr
data type to manage memory more effectively, ensuring that specific pages remain outside the working set until explicitly needed. The integration of VirtualLock
and VirtualUnlock
APIs allowed us to control when and how these pages are moved in and out of the working set, giving us more fine-grained control over memory access.
By combining these techniques, we can not only optimize memory usage but also detect unauthorized attempts to read or modify memory in real-time. This approach opens up possibilities for improving system security and performance in applications where memory management is critical. The working set can thus be «abused» in a controlled manner to gain visibility into memory access patterns and detect potential threats.
With tools like the working set watcher in place, we can protect critical memory regions and respond to unauthorized access quickly and effectively. This method adds an extra layer of monitoring and security in Windows applications.
For more details and to explore the code, this project is available via my GitHub: ws-watcher (opens in a new tab).
In computing, a working set is a vital concept that defines the amount of memory actively used by a process. This term encapsulates the current set of memory pages that a program needs to execute operations without incurring costly page faults that can slow performance. Our discussion will unravel the intricacies of working sets, how they are managed by modern operating systems like Windows, and the impact they have on application performance. We’ll delve into strategies for monitoring and optimizing the working set to maintain system efficiency and responsiveness.
Jump to:
- What is a Working Set?
- Working Set Mechanics
- Dynamic Management of Working Sets
- Impact on Application Performance
- Tools for Monitoring
- Using Performance Monitor to Analyze Working Sets
- Optimization Strategies
- Evolution and Relevance Today
- Conclusion
- References
1. What is a Working Set?
A working set constitutes the subset of virtual pages currently resident in physical memory and actively used by a process. This term is particularly relevant in the context of Microsoft Windows NT and later versions, including Windows 2000, XP, and subsequent releases up to Windows 10 and Windows Server editions. The working set is pivotal for a process because it holds the pages of code and data that the process accesses frequently.
In other words, the working set is the physical memory assigned to a process by the operating system. The working set consists of pages, which are sections of code and application data that have recently been used by the process.
2. Working Set Mechanics
When a process attempts to access data that is not in its working set, it incurs what is known as a page fault. The Virtual Memory Manager (VMM) then retrieves the data from disk storage, swapping the needed pages into the working set. While occasional page faults are normal, excessive page faults indicate that the working set is not adequately sized for the process’s needs, which can lead to performance degradation.
If memory becomes scarce, the Virtual Memory Manager moves the memory pages referenced less recently from the working sets in order to recover memory for the system. Older pages are also removed as new pages are added. Efficient applications use and store data in sequence, with the result that fewer memory pages are needed by the application, allowing a small working set. Adding more RAM is one solution to this problem while increasing the maximum working set size can also be helpful.
3. Dynamic Management of Working Sets
The Windows operating system dynamically manages the working sets of all running processes. The system’s Virtual Memory Manager continually adjusts the working set sizes based on current usage patterns and overall system load. When available memory is low, the VMM may shrink working sets, moving less recently referenced pages back to disk. This mechanism helps to recover memory for the system but can also lead to increased page faults if too many necessary pages are moved out of the working set.
4. Impact on Application Performance
The efficiency of an application is, in part, determined by how it manages its working set. Applications that access memory in a sequential and predictable pattern can operate with a smaller working set, as they minimize the number of page faults. On the other hand, applications with random or sparse memory access patterns may require a larger working set to maintain performance.
Administrators and developers can monitor and analyze the working set behaviors using tools like Windows Performance Monitor and Process Explorer. These tools provide insights into the working set sizes, page fault rates, and other memory-related metrics. Process Explorer, in particular, can display real-time data on the working set of each process, helping to diagnose memory usage issues.
Using Performance Monitor to Analyze Working Sets
Performance Monitor, a Windows utility, allows users to view real-time data about their system, which is essential for diagnosing performance issues related to working sets. Here’s a step-by-step guide on using Performance Monitor to analyze working sets:
Step 1: Launch Performance Monitor
- Press
Win + R
to open the Run dialog. - Type
perfmon.msc
and press Enter. - The Performance Monitor window will open.
Step 2: Add Counters for Working Set Monitoring
- In the Performance Monitor utility, navigate to the “Monitoring Tools” section and select “Performance Monitor”.
- Click on the green plus sign (+) to “Add Counters” from the toolbar.
- From the list of available counters, expand the “Process” category.
Step 3: Select Working Set Counters
- Scroll through the counters and select “Working Set – Private”, “Working Set – Peak”, and “Working Set” to monitor private, peak, and shared working sets respectively.
- Choose the instances of the processes you want to monitor or select
<All instances>
for a comprehensive view.
Step 4: Viewing and Analyzing Data
- Click on the “Add” button to include the selected counters to the monitoring display.
- The selected metrics will now appear as graphs on the main Performance Monitor screen.
- Observe the real-time data to understand how the working set changes over time.
Step 5: Interpret the Results
- A steady graph suggests that the working set is stable. Sudden spikes may indicate that a process required additional memory that was not in its working set.
- If the “Working Set – Private” counter shows consistent growth, the application might be consuming more memory than usual, which could be a sign of a memory leak.
Step 6: Adjust the Sampling Interval
- To adjust how frequently data is sampled and graphs are updated, right-click on the graph area, select “Properties”, and then go to the “General” tab.
- Set the “Sample every” option to the desired interval for more or less detailed data collection.
Step 7: Collecting and Analyzing Logs Over Time
- To create a log over a longer period, switch to the “Data Collector Sets” and create a new set with the working set counters.
- Once collected, this data can be analyzed to determine patterns or issues that only manifest over extended usage.
Step 8: Export or Save Data for Further Analysis
- To save the captured data for later analysis, you can right-click on the graph, and select “Save Data As” to export the data to a file format of your choice.
6. Optimization Strategies
Optimizing an application’s working set involves careful coding and memory management. Developers must ensure that their memory access patterns are as efficient as possible. Additionally, Windows allows the adjustment of the working set size programmatically through API functions, providing developers with control over their applications’ memory usage.
7. Evolution and Relevance Today
As Windows has evolved, so has its memory management capabilities, including the working set strategies. Modern versions of Windows feature more advanced algorithms for working set management, considering factors such as process priority and the system’s state. In contemporary high-memory systems, the impact of working set tuning may be less pronounced, yet it remains a critical consideration for ensuring optimal application performance.
8. Conclusion
The working set is a fundamental aspect of a process’s memory management in Windows operating systems. Understanding and managing the working set is crucial for maintaining system performance and responsiveness. As systems grow more complex and applications become more memory-intensive, the importance of efficient working set management continues to be paramount for both users and developers alike.
9. References
- Russinovich, Mark E., David A. Solomon, and Alex Ionescu. “Windows Internals, Part 2 (7th Edition).” Microsoft Press, 2021.
- Microsoft. “Memory Management.” Windows Dev Center, https://docs.microsoft.com/en-us/windows/win32/memory/memory-management.
- Bott, Ed, Carl Siechert, and Craig Stinson. “Windows 10 Inside Out (4th Edition).” Microsoft Press, 2020.
Если бы меня спросили, как до предела увеличить скорость работы Windows NT Workstation 4.0, я бы ответила так: выгрузите оболочку Explorer и работайте только с командной строкой, не запуская надоевшие графические приложения, — и Windows NT Workstation 4.0 понесется вскачь.
Обходные пути повышения производительности Windows NT Workstation 4.0
Если бы меня спросили, как до предела увеличить скорость работы Windows NT Workstation 4.0, я бы ответила так: выгрузите оболочку Explorer и работайте только с командной строкой, не запуская надоевшие графические приложения, — и Windows NT Workstation 4.0 понесется вскачь. Такой подход, который я называю «методика запрета излишеств», широко распространен среди поклонников UNIX, полагающих, и иногда справедливо, что графические приложения замедляют работу системы. Однако независимо от того, насколько быстро функционирует ядро операционной системы, Windows NT Workstation 4.0 все-таки работает в графической среде и запускает графические приложения. Поэтому в большинстве случаев отключить оболочку Windows Explorer, не жертвуя функциональностью, нельзя.
Исходя из этого, предлагаю подойти к проблемам оценки и настройки скорости работы приложений Windows NT Workstation 4.0 с другой стороны и попытаться получить от операционной системы максимум производительности иным способом. Так, для выявления ресурсоемких приложений можно использовать счетчики Performance Monitor, а также прибегнуть к помощи ряда утилит из Resource Kit, позволяющих оценить уровень производительности.
Почему именно Performance Monitor?
Performance Monitor — отличный инструмент для оценки производительности NT Workstation или NT Server. О том, как работать с ним в целях сбора информации о компьютере, можно узнать из документации Microsoft и специальной литературы. Однако я хочу обратить внимание на средства Performance Monitor, позволяющие оценивать производительность приложений. Особенно полезными мне представляются два его объекта: Process и Memory. Объект Process собирает информацию обо всех работающих процессах, будь то системные процессы, пользовательские процессы или службы Windows NT. Объект Memory собирает данные, описывающие компоненты подсистемы управления памятью Windows NT, включая файловый кэш, физическую память, а также несколько выгружаемых и невыгружаемых пулов памяти, которые Windows NT использует для системных процессов.
Хотя при оценке производительности Windows NT Workstation может возникнуть необходимость в определении скорости дисковых подсистем, я не буду останавливаться подробно на этой теме. Гораздо важнее разобраться, в чем корень проблемы и рассмотреть порядок использования приложением процессорных ресурсов и памяти, а также того, как этот порядок влияет на общую производительность системы.
Такие проблемы, как «пробуксовка» диска, зачастую являются симптомами некорректной работы самих приложений. Хотя интенсивный рост файла подкачки отчасти можно увязывать с медленной дисковой подсистемой или чрезмерно фрагментированным диском, в первую очередь следует выяснить первопричину этого явления. В Таблице 1 приведены объекты и счетчики, которые позволяют следить за работой приложения, и здесь же кратко описано их назначение. Если на основании показателей этих счетчиков создать файл рабочей области Performance Monitor (.pmw), то всегда можно быстро загрузить его для осуществления контроля за работой приложения. Однако следует помнить, что файлы рабочих областей содержат имя рабочей станции или сервера, на которых они были созданы. Поэтому после загрузки таких файлов на другом компьютере это имя необходимо скорректировать.
Контроль утечки памяти
Первые два счетчика в Таблице 1, Process:Working Set и Process: Pagefile Bytes, дают возможность контролировать уровень потребления памяти приложением. Счетчик Working Set — важный показатель эффективности работы программы, поскольку позволяет определить, сколько физической памяти (т. е. реальных страниц ОЗУ) она занимает. Постоянно отслеживая значение счетчика, можно обнаружить вызываемые приложением утечки памяти. Если, как показано на Экране 1, наблюдается постоянное увеличение показаний счетчика, значит, приложение не обеспечивает корректного освобождения ранее выделенной для него памяти. Однако при этом важно понимать алгоритм работы приложения. Например, если я оставлю в памяти компьютера не выполняющую никаких задач копию Microsoft Word, а ее рабочее пространство будет непрерывно увеличиваться, то можно быть уверенным, что Word инициирует утечки памяти. Однако, когда на моем компьютере функционирует программа сбора данных, размещающая информацию в массивах увеличивающихся размеров, прирост значения Working Set ее процесса будет вполне оправданным (хотя, возможно, и нежелательным).
|
ЭКРАН 1. Наблюдение за счетчиком Working Set. |
Значение счетчика Process:Pagefile Bytes весьма точно следует за изменением размера рабочего пространства приложения, увеличиваясь по мере роста объема потребляемой приложением памяти. Например, при использовании Working Set для контроля за работой приложения, порождающего утечки памяти, видно, что значение счетчика PageFile Bytes будет находиться в близкой к линейной зависимости от Working Set.
Выделенная виртуальная память и файл подкачки
Счетчики Commited Bytes, Commit Limit и %Commited Bytes In Use — очень удобный инструментарий для определения интенсивности использования памяти в Windows NT Workstation. Однако, прежде чем говорить о них, необходимо разобраться с принципами формирования файла подкачки.
В документации Microsoft указано, что минимальный размер этого файла должен быть равен объему физической памяти плюс еще примерно 12 Мбайт. Однако это значение может быть оптимизировано, исходя из реальных требований системы. О максимальном же размере беспокоиться нечего, ибо во всех исследованных мною версиях Windows NT, включая NT 4.0 с установленным Service Pack 6 (SP6), операционная система игнорировала любое задаваемое значение. Она увеличивает файл подкачки настолько, насколько это необходимо для удовлетворения потребностей в памяти, вплоть до исчерпания места на диске. Чтобы проверить, как Windows NT реагирует на возрастающий дефицит памяти, во вкладке Performance значка System на Control Panel введем значение максимального размера файла подкачки. Затем возьмем из каталога perftoolmeastool пакета Windows NT Resource Kit файл leakyapp.exe. Это созданное программистами Microsoft приложение My Leaky App, предназначенное для тестирования поведения системы в процессе непрекращающегося выделения памяти. После запуска этой программы размер занимаемой ею системной памяти будет постоянно расти.
|
ЭКРАН 2. Приложение MyLeakyApp отображает использование файла подкачки. |
Запустим приложение и, для инициации тестового процесса, нажмем кнопку Start Leaking. Приложение будет непрерывно отображать уровень использования файла подкачки, позволяя, как показано на Экране 2, останавливать и снова запускать процедуру запроса на дополнительную память. Если программа будет работать достаточно долго, размер файла подкачки начнет увеличиваться сверх предельного уровня, указанного во вкладке Performance. После превышения файлом подкачки установленного минимального значения для его возврата к исходной величине необходимо перезагрузить компьютер.
Когда Windows NT начнет, в целях удовлетворения растущих запросов на память, увеличивать файл подкачки, производительность резко упадет, особенно если файл размещен на медленном или сильно фрагментированном диске. С помощью счетчиков Committed Bytes и Commit Limit можно фиксировать рост потребления памяти, что провоцирует резкий рост файла подкачки. Упрощенно говоря, Windows NT записывает в Commit Limit значение, равное сумме объема установленной на компьютере физической памяти и размера файла подкачки, заданного во вкладке Performance. Значение счетчика Committed Bytes равняется общему размеру выделенной работающему процессу памяти. По мере роста Committed Bytes происходит приближение к предельному значению Commit Limit, достичь которого можно в случае выполнения следующих один за другим запросов на память со стороны одного или нескольких приложений. Следя за счетчиком %Committed Bytes In Use, можно увидеть, что при достижении им величины 100% система начнет увеличивать размеры файла подкачки, пытаясь удовлетворить растущие запросы на память.
Чтобы система могла справиться с этими запросами, увеличение файла подкачки будет производиться до тех пор, пока позволяет размер дискового пространства. При этом возможно появление показанного на Экране 3 сообщения Out of Virtual Memory. Если это произойдет, следует запустить Performance Monitor. Для всех работающих приложений нужно выбрать объект Process, счетчики Working Set и Pagefile Bytes. Довольно скоро выяснится, какое приложение отвечает за столь резкий рост потребления памяти. Следует помнить, что с помощью счетчика %Committed Bytes In Use можно определить оптимальный размер файла подкачки. Если постоянно следить за показаниями этого счетчика, то всегда можно подогнать минимальный размер файла под требования имеющегося на компьютере набора приложений.
|
ЭКРАН 3. Сообщение Out Of Virtual Memory |
Использование процессора
Счетчик Process:%Processor Time оценивает степень загрузки процессора приложением. Это важно знать при выявлении узких мест в работе операционной системы. Вместе с тем, при использовании данного счетчика надо быть очень внимательным. Например, некоторые приложения могут находиться в цикле ожидания в преддверии наступления определенных системных событий. Такие циклы способны вызывать стопроцентную загрузку процессора, что, однако, не мешает рабочей станции запускать другие процессы.
В большинстве случаев эти циклы имеют низкий приоритет и уступают процессорные ресурсы другим приложениям, которые после старта требуют исполнения своего кода. Когда ранние версии Web-браузера Netscape отрабатывали циклы, обеспечивающие стопроцентную загрузку процессора, никто не мог точно сказать, что делает программа: интенсивно обрабатывает данные или ожидает наступления события. Конечно, при активном использовании диска, дефиците свободной памяти и общем снижении производительности, сопровождаемом стопроцентной загрузкой процессора со стороны приложения, можно сказать, что в таком приложении, скорее всего, кроется ошибка.
Включенная в Windows NT Resource Kit утилита CPU Stress позволяет искусственно загрузить процессор и выяснить, как система должна себя вести в подобных ситуациях. С помощью этого средства можно настраивать приоритеты и уровень активности четырех потоков, контролировать степень загруженности процессора приложениями и определять значимость каждого потока для операционной системы (например, оценивать, какие потоки с низким приоритетом уступают управление более приоритетным потокам).
Утилиты Windows NT Resource Kit для управления производительностью
Помимо My Leaky App и CPU Stress, Resource Kit содержит ряд полезных утилит управления производительностью компьютеров, работающих под Windows NT. Большая часть их размещена в папке Perftool, а список некоторых из них приведен во врезке «Утилиты управления производительностью». Секрет успеха при управлении производительностью Windows NT Workstation — отличное знание своих приложений и порядка использования ими ресурсов операционной системы.
После освоения работы со счетчиками Performance Monitor я рекомендую обратить внимание на утилиту Response Probe из Resource Kit, которая дает возможность создавать скоростные приложения, заранее оценивая их характеристики. С ее помощью можно генерировать искусственные нагрузки, позволяющие моделировать уровень загруженности системы. Вместе с тем, оптимизация производительности при работе с одним приложением — не самая трудная задача. Самое интересное начинается тогда, когда в работу включаются одновременно 20 приложений и целый набор служб и утилит.
ОБ АВТОРЕ:
Даррен Мар-Элиа — внештатный редактор журнала Windows NT Magazine. Специалист по архитектуре NT; занимается планированием развертывания сетей NT 4.0 и Windows 2000 в масштабах США. С автором можно связаться по адресу: dmarelia@earthlink.net.
Таблица 1. Объекты и счетчики.
Объект: Счетчик | Объект контроля | Порядок использования |
Process: Working Set (Процесс: Рабочее пространство) | Количество потребляемой процессом физической оперативной памяти. | Непрерывный контроль использования приложением памяти и выявление утечки памяти. |
Process: Pagefile Bytes (Процесс: Байт файла подкачки) | Количество памяти, которое процесс использует в файле подкачки. | Непрерывный контроль использования приложением всей доступной памяти. |
Memory: Committed Bytes (Память: Байт выделенной виртуальной памяти) | Общий размер выделенной виртуальной памяти, которую в данный момент занимают все пользовательские процессы. | Позволяет установить момент начала роста файла подкачки (путем сравнения с Commit Limit). |
Memory: Commit Limit (Память: Предел выделенной виртуальной памяти) | Расчетная величина, которая определяет, какое количество виртуальной памяти система может выделить, не увеличивая размера файла подкачки. | Помогает выяснить, насколько размер файла подкачки соответствует системным требованиям. Используется для расчета значений счетчика %Commited Bytes In Use. |
Memory: % Committed Bytes In Use (Память: %использования выделенной памяти) | Соотношение величин счетчиков Commited Bytes и Commit Limit. | Позволяет установить момент начала роста файла подкачки. |
Process: % Processor Time (Процесс: %загруженности процессора) | Степень использования процессора заданным процессом. | Позволяет выявить наиболее загружающие процессор приложения. |
Утилиты управления производительностью
В состав Microsoft Windows NT Workstation 4.0 Resource Kit входят несколько утилит для настройки производительности. В каталоге Perftool можно найти следующие файлы.
cntrtoolcounters.hlp. В этом файле подсказки перечислены и описаны стандартные счетчики Performance Monitor.
logtools ypeperf.exe. Это утилита командной строки, аналогичная Performance Monitor, которая выводит на экран значения параметров, разделенные запятыми, как показано на Экране А. Typeperf отслеживает параметры, соответствующие объектам и счетчикам Performance Monitor, и возвращает их текущие значения. На Экране А выведены значения, полученные при мониторинге Memory: Committed Bytes и Microsoft Internet Explorer Working Set с интервалом в 1 секунду. Задавая путь в формате UNC перед объектом, за которым проводится наблюдение (например, «machinememorycommitted bytes»), можно следить за производительностью удаленных машин.
|
ЭКРАН A. Результаты работы утилиты logtools ypeperf.exe. |
meastoolempty.exe. Это утилита удаляет набор счетчиков Working Set работающего приложения. Используется для освобождения большего количества физической памяти непосредственно приложению.
meastool
timer.exe. Данная утилита показывает время работы конкретного приложения, включая время работы в привилегированном и пользовательском режимах (в процентах).
meastoolpview.exe. Данная утилита выводит детальную информацию о памяти, выделенной процессу системой. Кроме этого она помогает узнать маркер безопасности, который система назначила данному процессу и его потокам.
meastool op.exe. Утилита командной строки, которая в непрерывном режиме выводит основные процессы, занимающие процессор.
meastoolwperf.exe. Графическая утилита, показывающая значения большого числа параметров, характеризующих использование памяти и процессора. Напоминает утилиту Perfmeter в UNIX.