Development for Windows
6 November 2010

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


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

μTorrent


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


Поток 4600 открывает для раздачи файл с отключенным системным кешем. Причем все выполняется без снижения приоритета:


Я не пользуюсь торрентами и файлов для раздачи у меня не оказалось, да и каналы на отдачу в местных широтах довольно узкие, так что мне пришлось написать «эмулятор» мюторрента. Для начала «старая версия», не выключавшая кеширование (написал на сишарпе, потому что не у всех может оказаться установленная Visual Studio):
using System;
using System.IO;
using System.Threading;

namespace abuseio
{
    class Program
    {
        static void Main(string[] args)
        {
            for (var i = 0; i < 10; i++)
            {
                (new Thread(() => {
                    var file = File.OpenRead(args[0]);
                    var chunkSize = 16384;
                    var chunks = (int)(file.Length / chunkSize);
                    var rnd = new Random();
                    var buffer = new byte[chunkSize];
                    while (true) {
                        long index = rnd.Next(chunks);
                        file.Seek(index * chunkSize, SeekOrigin.Begin);
                        file.Read(buffer, 0, chunkSize);
                    }
                })).Start();
            }
        }
    }
}

В несколько потоков читаем случайные места предоставленного файла (лучше использовать что нибудь большое). Несколько минут после старта:

Видим довольно много кешированных файлов. Несколько часов после старта:

В памяти остался только хром (в основном потому, что я им активно пользовался) и ругие Active страницы файлов, означающие недавнее использование. В Standby остались в основном те самые priority 7 страницы:
Вот, к примеру, небольшая часть shell32.dll:

Ну и «общий вид»:

Почти все закешировалось на пятый уровень, вытеснив всех, кто находился там до этого. Более того, из-за частых обращений к «раздаваемым» файлам, суперфетч динамически повысил приоритет частей этих файлов и вытеснил часть кеша даже оттуда. Короче, все плохо.

Вот «эмулятор» нового мюторрента:
using System;
using System.Runtime.InteropServices;
using System.IO;
using System.Threading;
using Microsoft.Win32.SafeHandles;

namespace abuseio
{
    class Program
    {
        [Flags]
        enum FileAttributes : uint
        {
            Readonly = 0x00000001,
            Hidden = 0x00000002,
            System = 0x00000004,
            Directory = 0x00000010,
            Archive = 0x00000020,
            Device = 0x00000040,
            Normal = 0x00000080,
            Temporary = 0x00000100,
            SparseFile = 0x00000200,
            ReparsePoint = 0x00000400,
            Compressed = 0x00000800,
            Offline = 0x00001000,
            NotContentIndexed = 0x00002000,
            Encrypted = 0x00004000,
            Write_Through = 0x80000000,
            Overlapped = 0x40000000,
            NoBuffering = 0x20000000,
            RandomAccess = 0x10000000,
            SequentialScan = 0x08000000,
            DeleteOnClose = 0x04000000,
            BackupSemantics = 0x02000000,
            PosixSemantics = 0x01000000,
            OpenReparsePoint = 0x00200000,
            OpenNoRecall = 0x00100000,
            FirstPipeInstance = 0x00080000
        }

        [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        static extern SafeFileHandle CreateFile(
           string fileName,
           [MarshalAs(UnmanagedType.U4)] FileAccess fileAccess,
           [MarshalAs(UnmanagedType.U4)] FileShare fileShare,
           IntPtr securityAttributes,
           [MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
           [MarshalAs(UnmanagedType.U4)] FileAttributes flags,
           IntPtr template);

        static void Main(string[] args)
        {
            for (var i = 0; i < 10; i++)
            {
                (new Thread(() => {
                    var fileHandle = CreateFile(args[0], FileAccess.Read, FileShare.Read, IntPtr.Zero, FileMode.Open, FileAttributes.NoBuffering, IntPtr.Zero);
                    var file = new FileStream(fileHandle, FileAccess.Read);
                    var chunkSize = 16384;
                    var chunks = (int)(file.Length / chunkSize);
                    var rnd = new Random();
                    var buffer = new byte[chunkSize];
                    while (true) {
                        long index = rnd.Next(chunks);
                        file.Seek(index * chunkSize, SeekOrigin.Begin);
                        file.Read(buffer, 0, chunkSize);
                    }
                })).Start();
            }
        }
    }
}

Отключаем буферизацию и делаем все «кеширование» сами (кавычки, потому что сложно назвать нормальным решение управлять физической памятью из пользовательского режима):
1. За счет все еще неоправданно высокого приоритета ввода-вывода это мешает нормальной работе с компьютером
2. Отсутствует глобальный кеш. Если кому то еще надо обратиться к файлу — кешированная информация продублируется еще и в системном кеше
3. Существуют проблемы с размером доступного кеша
4. Last but not least, это не особо помогает. Так как если мюторрент будет выделять гигабайты памяти, он окажется первым кандидатом на тримминг и весь его «кеш» уйдет в пейджфайл.

Самые любознательные к этому моменту уже прочитали документ, на который я ссылался выше и знают правильное решение. Вот его реализация на шарпе:
using System;
using System.Runtime.InteropServices;
using System.IO;
using System.Threading;
using Microsoft.Win32.SafeHandles;

namespace abuseio
{
    class Program
    {
        [DllImport("kernel32.dll")]
        static extern IntPtr GetCurrentThread();

        enum ThreadPriority
        {
            THREAD_MODE_BACKGROUND_BEGIN = 0x00010000,
            THREAD_MODE_BACKGROUND_END = 0x00020000,
            THREAD_PRIORITY_ABOVE_NORMAL = 1,
            THREAD_PRIORITY_BELOW_NORMAL = -1,
            THREAD_PRIORITY_HIGHEST = 2,
            THREAD_PRIORITY_IDLE = -15,
            THREAD_PRIORITY_LOWEST = -2,
            THREAD_PRIORITY_NORMAL = 0,
            THREAD_PRIORITY_TIME_CRITICAL = 15
        }

        [DllImport("kernel32.dll")]
        static extern bool SetThreadPriority(IntPtr hThread, ThreadPriority nPriority);

        static void Main(string[] args)
        {
            for (var i = 0; i < 10; i++)
            {
                (new Thread(() => {
                    SetThreadPriority(GetCurrentThread(), ThreadPriority.THREAD_MODE_BACKGROUND_BEGIN);
                    var file = File.OpenRead(args[0]);
                    var chunkSize = 16384;
                    var chunks = (int)(file.Length / chunkSize);
                    var rnd = new Random();
                    var buffer = new byte[chunkSize];
                    while (true) {
                        long index = rnd.Next(chunks);
                        file.Seek(index * chunkSize, SeekOrigin.Begin);
                        file.Read(buffer, 0, chunkSize);
                    }
                })).Start();
            }
        }
    }
}


Понижаются все, известные человечеству, приоритеты потока:

Несколько часов активной «раздачи» не приводят к вытеснению обычного кеша, но и от самого кеширования мы при этом не отказываемся:

Все красиво сложено на самом низком уровне приоритета



Теперь о том, как с жить с тем мюторрентом, который есть. Все проще, чем кажется и делается на 1-2-3.
1. Выключаем дурацкий кеш самого мюторрента и включаем кеш винды:


2. Импортируем в реестр следующий файл:
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\utorrent.exe]

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\utorrent.exe\PerfOptions]
"IoPriority"=dword:00000000
"PagePriority"=dword:00000001



3. Перезапускаем мюторрент

Результат:



Можно оставлять на ночь.

Пара слов о swappiness

Не знаю, я бы не назвал это «правильной работой с памятью». Вместо принятия решений на основе реального использования (aging/trimming, приоритеты в том числе динамические и пр) — какой то совершенно оторванный от жизни параметр. В общем то не только мне swappiness кажется не сильно хорошей идеей, сам автор low latency патча, реализовавшего swappiness говорит следующее:
Decrease /proc/sys/vm/swappiness?

Swapout is good. It frees up unused memory. I run my desktop machines at
swappiness=100.

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

Разбирать весь флейм по ссылке ни желания ни сил у меня уже нет — читайте сами.

На этом пока все.

+277
85.2k 396
Comments 171
Top of the day