Минутка хардкора-2: Alois Kraus: что с памятью твоей?
От: Sinix  
Дата: 04.10.16 07:44
Оценка: 19 (3) +1
Продолжаем начатое
Автор: Sinix
Дата: 15.09.16
.

На этот раз — Windows 10 Memory Compression And More от Alois Kraus.

Название имхо неудачное, т.к. этому самому And More посвящена бОльшая (и самая интересная) часть статьи, "In memories of SetProcessWorkingSetSize" подошло бы лучше. Почему так и просто освежить память про "что все эти столбцы в TaskManager значат???" — тынц по ссылке, если ещё не.

Статья не затрагивает ничего специфичного для шарпа / CLR, так что слегка неформат. Но поправить легко

          Alloc 1 mb:     0ms, ips:       1 032 062 992,13 | Mem:     1 032,05 kb, GC 0/1/2: 0/0/0 => 131072
          Alloc 1 gb:     0ms, ips:     171 021 569 826,71 | Mem: 1 048 584,05 kb, GC 0/1/2: 0/0/0 => 134217728
          Alloc 4 gb:     3ms, ips:     141 897 954 803,75 | Mem: 4 194 312,05 kb, GC 0/1/2: 0/0/0 => 536870912
Done.

  Где подвох?
using System;
using System.Diagnostics;

static class Program
{
    static void Main(string[] args)
    {
        Console.WindowWidth = 120;

        long[] data = null;
        Measure("Alloc 1 mb", () =>
        {
            data = new long[1024 * 1024 / 8];
            return data.Length;
        });
        GC.KeepAlive(data);

        Measure("Alloc 1 gb", () =>
        {
            data = new long[1024 * 1024 * 1024 / 8];
            return data.Length;
        });
        GC.KeepAlive(data);

        // run as x64; set <gcAllowVeryLargeObjects> !!!
        Measure("Alloc 4 gb", () =>
        {
            data = new long[1024 * 1024 * 1024 / 2];
            return data.Length;
        });
        GC.KeepAlive(data);

        Console.WriteLine("Done.");
        Console.ReadKey();
    }


    static void Measure(string name, Func<long> callback)
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();

        var mem = GC.GetTotalMemory(true);
        var gc00 = GC.CollectionCount(0);
        var gc01 = GC.CollectionCount(1);
        var gc02 = GC.CollectionCount(2);

        var sw = Stopwatch.StartNew();
        var result = callback();
        sw.Stop();

        var mem2 = GC.GetTotalMemory(false);
        var gc10 = GC.CollectionCount(0);
        var gc11 = GC.CollectionCount(1);
        var gc12 = GC.CollectionCount(2);

        var memDelta = (mem2 - mem) / 1024.0;
        var gcDelta0 = gc10 - gc00;
        var gcDelta1 = gc11 - gc01;
        var gcDelta2 = gc12 - gc02;

        Console.WriteLine(
            "{0,20}: {1,5}ms, ips: {2,22:N} | Mem: {3,9:N2} kb, GC 0/1/2: {4}/{5}/{6} => {7,6}",
            name, sw.ElapsedMilliseconds, result / sw.Elapsed.TotalSeconds, memDelta, gcDelta0, gcDelta1, gcDelta2, result);
    }
}

app.config
<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <runtime>
        <gcAllowVeryLargeObjects enabled="true" />
    </runtime>
</configuration>


P.S. старый блог того же автора. Строго рекомендую всем, кто собирается правильно готовить WPA.
Для затравки: Is This A CPU Bug? Why Is The First Start Of An Application Slow?

P.P.S. Аналог для managed-процессов: perfView. Теперь и с возможностью встроить самодиагностику в приложение.

UPD Продолжение сериала. На этот раз действительно про Windows 10 Memory Compression.
Отредактировано 09.01.2017 11:52 Sinix . Предыдущая версия . Еще …
Отредактировано 10.10.2016 13:06 Sinix . Предыдущая версия .
Отредактировано 04.10.2016 8:14 Sinix . Предыдущая версия .
минутка хардкора
Re: Минутка хардкора-2: Alois Kraus: что с памятью твоей?
От: Sharov Россия  
Дата: 04.10.16 12:08
Оценка: +1
Здравствуйте, Sinix, Вы писали:

S>Продолжаем начатое
Автор: Sinix
Дата: 15.09.16
.


S>На этот раз — Windows 10 Memory Compression And More от Alois Kraus.


S>Название имхо неудачное, т.к. этому самому And More посвящена бОльшая (и самая интересная) часть статьи, "In memories of SetProcessWorkingSetSize" подошло бы лучше. Почему так и просто освежить память про "что все эти столбцы в TaskManager значат???" — тынц по ссылке, если ещё не.


S>Статья не затрагивает ничего специфичного для шарпа / CLR, так что слегка неформат. Но поправить легко

S>Где подвох?

На вскидку, выделенное
S>
S>          Alloc 1 mb:     0ms, ips:       1 032 062 992,13 | Mem:     1 032,05 kb, GC 0/1/2: 0/0/0 => 131072
S>          Alloc 1 gb:     0ms, ips:     171 021 569 826,71 | Mem: 1 048 584,05 kb, GC 0/1/2: 0/0/0 => 134217728
S>          Alloc 4 gb:     3ms, ips:     141 897 954 803,75 | Mem: 4 194 312,05 kb, GC 0/1/2: 0/0/0 => 536870912
S>Done.
S>


А дядька глубоко копает
Кодом людям нужно помогать!
Re[2]: Минутка хардкора-2: Alois Kraus: что с памятью твоей?
От: Sinix  
Дата: 04.10.16 12:17
Оценка:
Здравствуйте, Sharov, Вы писали:

S>На вскидку, выделенное

Ага. Осталось самое интересное: что не так с замерами и как сделать боль-менее корректно.

S>А дядька глубоко копает

За то и ценится.
Re[3]: Минутка хардкора-2: Alois Kraus: что с памятью твоей?
От: samius Япония http://sams-tricks.blogspot.com
Дата: 04.10.16 14:06
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Здравствуйте, Sharov, Вы писали:


S>>На вскидку, выделенное

S>Ага. Осталось самое интересное: что не так с замерами и как сделать боль-менее корректно.

А можно узнать, что именно значит "Ага"? Мне вот выделенное как-раз не странно. Мне странно что 1Гиг выделить быстрее чем 1Мб если измерять во времени на ячейку массива.

У меня так
          Alloc 1 mb:        0.11ms, ips:       1,228,416,119.96 | Mem:  1,032.05 kb, GC 0/1/2: 0/0/0 => 131072
          Alloc 1 gb:        0.61ms, ips:     219,274,183,956.87 | Mem: 1,048,584.05 kb, GC 0/1/2: 0/0/0 => 134217728
          Alloc 4 gb:       11.89ms, ips:      45,139,099,859.59 | Mem: 4,194,312.05 kb, GC 0/1/2: 0/0/0 => 536870912
Done.


З.Ы. статью не читал пока.
Re[4]: Минутка хардкора-2: Alois Kraus: что с памятью твоей?
От: Sinix  
Дата: 04.10.16 14:19
Оценка:
Здравствуйте, samius, Вы писали:

S>А можно узнать, что именно значит "Ага"? Мне вот выделенное как-раз не странно.

S>Мне странно что 1Гиг выделить быстрее чем 1Мб если измерять во времени на ячейку массива.

Дык ничего странного,
  спойлер
под капотом — самый обычный VirtualAlloc.
Т.е. физическая память мапится только при непосредственном обращении к странице.

  совсем спойлер
https://randomascii.wordpress.com/2014/12/10/hidden-costs-of-memory-allocation/
Re[3]: Минутка хардкора-2: Alois Kraus: что с памятью твоей?
От: Lexey Россия  
Дата: 04.10.16 14:43
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Здравствуйте, Sharov, Вы писали:


S>>На вскидку, выделенное

S>Ага. Осталось самое интересное: что не так с замерами и как сделать боль-менее корректно.

А что не так с выделенным, кроме того, что непонятно, в чем смысл такой метрики?
В простой модели время аллокации = (накладные расходы менеджера памяти и VirtualAlloc) + (время на коммит страниц) + (время на зануление) = C + mN + lN = C + kN. Если C >> k, то и получится, что чем большим куском выделяем, тем больше "попугаев" намеряем.
В реале, видимо, при больших размерах добавляются еще расходы на своп, отсюда и падение "попугаев" в 3-м тесте.
"Будь достоин победы" (c) 8th Wizard's rule.
Re[5]: Минутка хардкора-2: Alois Kraus: что с памятью твоей?
От: samius Япония http://sams-tricks.blogspot.com
Дата: 04.10.16 15:40
Оценка: 6 (1) +1
Здравствуйте, Sinix, Вы писали:

S>Здравствуйте, samius, Вы писали:


S>Дык ничего странного,
  спойлер
S>под капотом — самый обычный VirtualAlloc.
S>Т.е. физическая память мапится только при непосредственном обращении к странице.
Именно эта мысль побудила меня запустить пример у себя. При обращении к содержимому (что в начале массива, что в конце) заметных отличий в замере не обнаружил. То ли в середине массива не пробовал, то ли не умею обращаться к содержимому. Не исключаю что оптимизатор мои обращения соптимизячил.
Re[6]: Минутка хардкора-2: Alois Kraus: что с памятью твоей?
От: Sinix  
Дата: 04.10.16 15:44
Оценка: 18 (1)
Здравствуйте, samius, Вы писали:

  спойлер
S>>Т.е. физическая память мапится только при непосредственном обращении к странице.
S>При обращении к содержимому (что в начале массива, что в конце) заметных отличий в замере не обнаружил.
S>То ли в середине массива не пробовал.
Ага, оно. Размер страницы — 4кб. По ссылкам расписано лучше
Re[4]: Минутка хардкора-2: Alois Kraus: что с памятью твоей?
От: Sinix  
Дата: 04.10.16 15:48
Оценка:
Здравствуйте, Lexey, Вы писали:


L>А что не так с выделенным, кроме того, что непонятно, в чем смысл такой метрики?

L>В реале, видимо, при больших размерах добавляются еще расходы на своп, отсюда и падение "попугаев" в 3-м тесте.

Неа, всё куда проще. Меряется не то и не так. Вот тут подробней.
Re[5]: Минутка хардкора-2: Alois Kraus: что с памятью твоей?
От: Lexey Россия  
Дата: 05.10.16 09:08
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Неа, всё куда проще. Меряется не то и не так. Вот тут подробней.


Куда проще-то? Ты хочешь сказать, что new long[...] вообще память не трогает, а лишь резервируется страницы? В принципе, возможно, с учетом того, что ОС сама страницы зануляет. Правда, тогда уменьшение "попугаев" на 4Gb выглядит странно.
"Будь достоин победы" (c) 8th Wizard's rule.
Re[6]: Минутка хардкора-2: Alois Kraus: что с памятью твоей?
От: Sinix  
Дата: 05.10.16 09:27
Оценка:
Здравствуйте, Lexey, Вы писали:

L>Куда проще-то? Ты хочешь сказать, что new long[...] вообще память не трогает, а лишь резервируется страницы? В принципе, возможно, с учетом того, что ОС сама страницы зануляет. Правда, тогда уменьшение "попугаев" на 4Gb выглядит странно.


Да ёкылымыны! Я прям не знаю, как ещё подсказку дать Меряется _не_то_ и _не_так_.

MEM_COMMIT
Allocates memory charges (from the overall size of memory and the paging files on disk) for the specified reserved memory pages. The function also guarantees that when the caller later initially accesses the memory, the contents will be zero. Actual physical pages are not allocated unless/until the virtual addresses are actually accessed.

(c)

Погрешность измерения + константная стоимость резервирования _одного_ большого блока VA (для кучи последовательных аллокаций всё веселее) и имеем что имеем.

last hint: что, если обратиться к каждой странице?
Re[7]: Минутка хардкора-2: Alois Kraus: что с памятью твоей?
От: Lexey Россия  
Дата: 06.10.16 08:50
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Да ёкылымыны! Я прям не знаю, как ещё подсказку дать Меряется _не_то_ и _не_так_.


Мда... То, что меряется "хрен знает что", я еще в первом своем посте написал. Вопрос был только в том, почему в 3-м тесте результат хуже, чем во 2-м.
"Будь достоин победы" (c) 8th Wizard's rule.
Re[8]: Минутка хардкора-2: Alois Kraus: что с памятью твоей?
От: Sinix  
Дата: 06.10.16 08:58
Оценка:
Здравствуйте, Lexey, Вы писали:

L>Мда... То, что меряется "хрен знает что", я еще в первом своем посте написал. Вопрос был только в том, почему в 3-м тесте результат хуже, чем во 2-м.

Ну так ответы читать надо

Погрешность измерения + константная стоимость резервирования _одного_ большого блока VA ... и имеем что имеем.


Ссылку с пруфами давал дважды, вот кусок

In my tests, for sizes ranging from 8 MB to 32 MB, the cost for a new[]/delete[] pair averaged about 7.5 μs (microseconds), split into ~5.0 μs for the allocation and ~2.5 μs for the free. For the large allocations I was testing the size of the allocation did not seem to significantly affect the results.

Re[9]: Минутка хардкора-2: Alois Kraus: что с памятью твоей?
От: Lexey Россия  
Дата: 06.10.16 10:33
Оценка:
Здравствуйте, Sinix, Вы писали:

L>>Мда... То, что меряется "хрен знает что", я еще в первом своем посте написал. Вопрос был только в том, почему в 3-м тесте результат хуже, чем во 2-м.

S>Ну так ответы читать надо

С тем же успехом могу сказать, что вопросы читать надо. Я не вижу объяснения этого момента в тех ответах, которые были.

S>

S>Погрешность измерения + константная стоимость резервирования _одного_ большого блока VA ... и имеем что имеем.


3мс у тебя и 11 у samius'а — не дохрена ли для погрешности (если считать, что стоимость резервирования для 1 Gb и 4Gb одинакова)?

S>Ссылку с пруфами давал дважды, вот кусок

S>

S>In my tests, for sizes ranging from 8 MB to 32 MB, the cost for a new[]/delete[] pair averaged about 7.5 μs (microseconds), split into ~5.0 μs for the allocation and ~2.5 μs for the free. For the large allocations I was testing the size of the allocation did not seem to significantly affect the results.


Так это как раз противоречит наблюдаемой разнице для 1 и 4 Gb.
"Будь достоин победы" (c) 8th Wizard's rule.
Re[10]: Минутка хардкора-2: Alois Kraus: что с памятью твоей?
От: Sinix  
Дата: 06.10.16 11:52
Оценка:
Здравствуйте, Lexey, Вы писали:

L>С тем же успехом могу сказать, что вопросы читать надо.

Туше

L>Я не вижу объяснения этого момента в тех ответах, которые были.

L>3мс у тебя и 11 у samius'а — не дохрена ли для погрешности (если считать, что стоимость резервирования для 1 Gb и 4Gb одинакова)?
С учётом стоимости собственно обращений к страницам — разбросом можно смело пренебречь и брать за константу.

Готовые замеры (без учёта стоимости заполнения пула пустых страниц):
       Alloc 4,00 Gb:     2ms, ips:     191 261 457 784,11 | Mem: 4 194 312,05 kb, GC 0/1/2: 0/0/0 => 536870912
            Consume1:   167ms, ips:       3 208 933 331,98 | Mem:         8,00 kb, GC 0/1/2: 0/0/0 => 536870912
            Consume2:     8ms, ips:      66 762 533 358,20 | Mem:         8,00 kb, GC 0/1/2: 0/0/0 => 536870912


  Код
using System;
using System.Diagnostics;

static class Program
{
    private static void ForceMemAccess(ref long l)
    {
        l = 1;
    }

    static void Main(string[] args)
    {
        const int PageSize = 4096 / 2;
        Console.WindowWidth = 120;

        long[] data = null;

        for (int n = 1; n <= 8; n++)
        {
            data = null;

            var allocSize = n * 512 * (1024 * 1024 / sizeof(long));

            Measure($"Alloc {n * 0.5:N2} Gb", () =>
            {
                data = new long[allocSize];
                GC.KeepAlive(data[0]);
                return data.Length;
            });
            GC.KeepAlive(data);

            Measure("Consume1", () =>
            {
                var local = data;
                for (int i = 0; i < local.Length; i += PageSize)
                {
                    ForceMemAccess(ref local[i]);
                }
                return data.Length;
            });

            Measure("Consume2", () =>
            {
                var local = data;
                for (int i = 0; i < local.Length; i += PageSize)
                {
                    ForceMemAccess(ref local[i]);
                }
                return data.Length;
            });

            GC.KeepAlive(data);

            Console.WriteLine();
        }

        Console.WriteLine("Done.");
        Console.ReadKey();
    }


    static void Measure(string name, Func<long> callback)
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();

        var mem = GC.GetTotalMemory(true);
        var gc00 = GC.CollectionCount(0);
        var gc01 = GC.CollectionCount(1);
        var gc02 = GC.CollectionCount(2);

        var sw = Stopwatch.StartNew();
        var result = callback();
        sw.Stop();

        var mem2 = GC.GetTotalMemory(false);
        var gc10 = GC.CollectionCount(0);
        var gc11 = GC.CollectionCount(1);
        var gc12 = GC.CollectionCount(2);

        var memDelta = (mem2 - mem) / 1024.0;
        var gcDelta0 = gc10 - gc00;
        var gcDelta1 = gc11 - gc01;
        var gcDelta2 = gc12 - gc02;

        Console.WriteLine(
            "{0,20}: {1,5}ms, ips: {2,22:N} | Mem: {3,12:N2} kb, GC 0/1/2: {4}/{5}/{6} => {7,6}",
            name, sw.ElapsedMilliseconds, result / sw.Elapsed.TotalSeconds, memDelta, gcDelta0, gcDelta1, gcDelta2, result);
    }
}
Re[11]: Минутка хардкора-2: Alois Kraus: что с памятью твоей?
От: Lexey Россия  
Дата: 06.10.16 13:29
Оценка:
Здравствуйте, Sinix, Вы писали:

S>С учётом стоимости собственно обращений к страницам — разбросом можно смело пренебречь и брать за константу.


ОК. Не понял только, почему у тебя PageSize = 4096 / 2, а не 4096 / sizeof(long).
"Будь достоин победы" (c) 8th Wizard's rule.
Re[12]: Минутка хардкора-2: Alois Kraus: что с памятью твоей?
От: Sinix  
Дата: 06.10.16 13:38
Оценка:
Здравствуйте, Lexey, Вы писали:

L>ОК. Не понял только, почему у тебя PageSize = 4096 / 2, а не 4096 / sizeof(long).

А чтоб наверняка

Можешь уменьшить, принципиально ничего не меняется.
Re[12]: Минутка хардкора-2: Alois Kraus: что с памятью твоей?
От: Sharov Россия  
Дата: 06.10.16 16:13
Оценка: +1
Здравствуйте, Lexey, Вы писали:

L>Здравствуйте, Sinix, Вы писали:


S>>С учётом стоимости собственно обращений к страницам — разбросом можно смело пренебречь и брать за константу.


L>ОК. Не понял только, почему у тебя PageSize = 4096 / 2, а не 4096 / sizeof(long).


Можно еще проще -- += Environment.SystemPageSize
Кодом людям нужно помогать!
Re[13]: Минутка хардкора-2: Alois Kraus: что с памятью твоей?
От: Lexey Россия  
Дата: 06.10.16 17:32
Оценка: 54 (2)
Здравствуйте, Sinix, Вы писали:

L>>ОК. Не понял только, почему у тебя PageSize = 4096 / 2, а не 4096 / sizeof(long).

S>А чтоб наверняка

Бр... Что "наверняка"? При 4096 / 2 ты только каждую 4-ю страницу "трогаешь".

S>Можешь уменьшить, принципиально ничего не меняется.


Это да, но логика выбора /2 не ясна.
"Будь достоин победы" (c) 8th Wizard's rule.
Re[14]: Минутка хардкора-2: Alois Kraus: что с памятью твоей?
От: Sinix  
Дата: 06.10.16 17:58
Оценка: +1
Здравствуйте, Lexey, Вы писали:

L>Это да, но логика выбора /2 не ясна.


Миль пардон, размер от примера с массивом байт остался. / (2 * sizeof(long)) конечно.
Спасиб за поправку
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.