почему так медленно?
От: CodeMonkey  
Дата: 07.06.19 17:02
Оценка:
  Скрытый текст
    class Program
    {
        static void Main(string[] args)
        {
            var num = 10_000_000;

            {
                var watch = Stopwatch.StartNew();

                var data = new TestClass[num];
                for (var i = 0; i < data.Length; i++)
                {
                    data[i] = new TestClass
                    {
                        Number0 = 10,
                        Number1 = 20,
                    };
                }

                watch.Stop();
                Console.WriteLine(watch.Elapsed.TotalSeconds);
            }

            {
                var watch = Stopwatch.StartNew();

                var data = new TestStruct[num];
                for (var i = 0; i < data.Length; i++)
                {
                    data[i].Number0 = 10;
                    data[i].Number1 = 20;
                }

                watch.Stop();
                Console.WriteLine(watch.Elapsed.TotalSeconds);
            }
        }

        struct TestStruct
        {
            public long Number0;
            public long Number1;
            public long Temp0;
            public long Temp1;
            public long Temp2;
            public long Temp3;
            public long Temp4;
            public long Temp5;
            public long Temp6;
            public long Temp7;
        }

        class TestClass
        {
            public long Number0;
            public long Number1;
            public long Temp0;
            public long Temp1;
            public long Temp2;
            public long Temp3;
            public long Temp4;
            public long Temp5;
            public long Temp6;
            public long Temp7;
        }
    }


Получается:

1.6395053
0.2299897

Откуда здесь разница аж в 8 раз?
Re: почему так медленно?
От: vorona  
Дата: 07.06.19 17:46
Оценка:
Здравствуйте, CodeMonkey, Вы писали:

ref struct
Re[2]: почему так медленно?
От: CodeMonkey  
Дата: 07.06.19 17:50
Оценка:
Здравствуйте, vorona, Вы писали:

V>ref struct


Ты точно уверен, что посмотрел код и понял, что он делает?
Re[3]: почему так медленно?
От: vorona  
Дата: 07.06.19 20:04
Оценка: 5 (1)
Здравствуйте, CodeMonkey, Вы писали:

GC.TryStartNoGCRegion(10_000_000 * 128, true);
var watch = Stopwatch.StartNew();

var data = new TestClass[num];
for (var i = 0; i < data.Length; i++)
{
    data[i] = new TestClass
    {
        Number0 = 10,
        Number1 = 20,
    };
}

watch.Stop();
Console.WriteLine(watch.Elapsed.TotalSeconds);
GC.EndNoGCRegion();


У меня получилось
0,4047961
0,2600024
Re[4]: почему так медленно?
От: CodeMonkey  
Дата: 07.06.19 20:29
Оценка:
Здравствуйте, vorona, Вы писали:

V>У меня получилось

V>0,4047961
V>0,2600024

А у меня получилось

System.ArgumentOutOfRangeException: 'totalSize is too large. For more information about setting the maximum size, see "Latency Modes" in http://go.microsoft.com/fwlink/?LinkId=522706
Parameter name: totalSize'

Re: почему так медленно?
От: ylem  
Дата: 07.06.19 22:28
Оценка: :)
Профайлер вам в помощь.
Но если гадать, ставлю на очевидное -- выделение места по экземпляр ссылочного типа.
Еще, вероятно, запись в поле элемента массива быстрее, чем в объект. Оно вроде бы делается через "ссылку на экзепляр" ссылочного типа.
Отредактировано 07.06.2019 22:34 ylem . Предыдущая версия .
Re[2]: почему так медленно?
От: CodeMonkey  
Дата: 07.06.19 23:03
Оценка:
Здравствуйте, ylem, Вы писали:

Y>Профайлер вам в помощь.


Профайлеру низкоуровневые вещи вообще неведомы.

Y>Но если гадать, ставлю на очевидное -- выделение места по экземпляр ссылочного типа.


А чего там делать то так долго? Обнулить нужное количество памяти, поинкрементить указатель на свободное место. Всё.
Re[3]: почему так медленно?
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 08.06.19 09:01
Оценка:
Здравствуйте, CodeMonkey, Вы писали:

CM>А чего там делать то так долго? Обнулить нужное количество памяти, поинкрементить указатель на свободное место. Всё.

Не все. У тебя 10_000_000 * 80 байт. Вспоминаем про виртуальную память, так как реальная память выделяется по мере необходимости.

Второй прогон по идее должен быть быстрее после сборки мусора, если GC оставит память зарезервированной.
и солнце б утром не вставало, когда бы не было меня
Re: почему так медленно?
От: okon  
Дата: 08.06.19 09:19
Оценка:
CM>Откуда здесь разница аж в 8 раз?

Помоему очевидно, в первом цикле есть постоянное дергание new что достаточно дорогая операция по сравнению с доступом по индексу и копированием long, во втором нету.
Второй момент структуры оптимизируются под стек насколько мне известно и даже если структура будет во втором цикле не явно создаваться, то пара процессорных операций со стеком аля push будет дешевле, чем целая логика менеджера памяти который работает с кучей. Не утверждаю что все именно так, не проверял.
”Жить стало лучше... но противнее. Люди которые ставят точку после слова лучше становятся сторонниками Путина, наши же сторонники делают акцент на слове противнее ( ложь, воровство, лицемерие, вражда )." (с) Борис Немцов
Re[5]: почему так медленно?
От: vorona  
Дата: 08.06.19 10:54
Оценка:
Здравствуйте, CodeMonkey, Вы писали:

CM>

CM>System.ArgumentOutOfRangeException: 'totalSize is too large. For more information about setting the maximum size, see "Latency Modes" in http://go.microsoft.com/fwlink/?LinkId=522706
CM>Parameter name: totalSize'


В файле проекта нужно написать

<PropertyGroup>
<ServerGarbageCollection>true</ServerGarbageCollection>
</PropertyGroup>

и сделать ребилд
Re[2]: почему так медленно?
От: Sharov Россия  
Дата: 08.06.19 12:51
Оценка:
Здравствуйте, okon, Вы писали:

O>Второй момент структуры оптимизируются под стек насколько мне известно и даже если структура будет во втором цикле не явно создаваться, то пара процессорных операций со стеком аля push будет дешевле, чем целая логика менеджера памяти который работает с кучей. Не утверждаю что все именно так, не проверял.


Массив все равно будет в куче.
Кодом людям нужно помогать!
Re[3]: почему так медленно?
От: ylem  
Дата: 08.06.19 17:04
Оценка:
CM>А чего там делать то так долго? Обнулить нужное количество памяти, поинкрементить указатель на свободное место. Всё.
Это не долго. Но в первом варианте не надо делать _вообще_ ничего.
Re[4]: почему так медленно?
От: CodeMonkey  
Дата: 08.06.19 17:18
Оценка:
Здравствуйте, ylem, Вы писали:

Y>Это не долго.


А ты на цифры посмотри. Вариант с классами в разы медленнее, чем со структурами.
Re[6]: почему так медленно?
От: CodeMonkey  
Дата: 08.06.19 17:27
Оценка:
Здравствуйте, vorona, Вы писали:

V>В файле проекта нужно написать

V>

V><PropertyGroup>
V> <ServerGarbageCollection>true</ServerGarbageCollection>
V></PropertyGroup>


А, так это Core. Важное уточнение.
Получается

0.4799392
0.1881155

даже без вызова TryStartNoGCRegion
Re[4]: почему так медленно?
От: CodeMonkey  
Дата: 08.06.19 17:29
Оценка:
Здравствуйте, Serginio1, Вы писали:

S> Второй прогон по идее должен быть быстрее после сборки мусора, если GC оставит память зарезервированной.


Ни фига подобного, получается даже медленнее.
Re[5]: почему так медленно?
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 08.06.19 18:48
Оценка:
Здравствуйте, CodeMonkey, Вы писали:

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


S>> Второй прогон по идее должен быть быстрее после сборки мусора, если GC оставит память зарезервированной.


CM>Ни фига подобного, получается даже медленнее.


Тогда проблема со сборкой мусора как правильно указал vorona GC.TryStartNoGCRegion(10_000_000 * 128, true);
и солнце б утром не вставало, когда бы не было меня
Re[6]: почему так медленно?
От: CodeMonkey  
Дата: 08.06.19 19:17
Оценка:
Здравствуйте, Serginio1, Вы писали:

S> Тогда проблема со сборкой мусора как правильно указал vorona GC.TryStartNoGCRegion(10_000_000 * 128, true);


Вполне возможно, но почему?
Re[7]: почему так медленно?
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 08.06.19 19:24
Оценка:
Здравствуйте, CodeMonkey, Вы писали:

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


S>> Тогда проблема со сборкой мусора как правильно указал vorona GC.TryStartNoGCRegion(10_000_000 * 128, true);


CM>Вполне возможно, но почему?


https://docs.microsoft.com/ru-ru/dotnet/standard/garbage-collection/fundamentals
Условия для сборки мусора

Сборка мусора возникает при выполнении одного из следующих условий:

Память, используемая объектами, выделенными в управляемой куче, превышает допустимый порог. Этот порог непрерывно корректируется во время выполнения процесса.

Там же про Эфемерные поколения и сегменты

и еще про параллелизмом

Для компьютера с одним процессором всегда используется сборка мусора рабочей станции, независимо от значения элемента <gcServer>. Если задана сборка мусора сервера, среда CLR использует сборку мусора рабочей станции с выключенным параллелизмом.

и солнце б утром не вставало, когда бы не было меня
Re[8]: почему так медленно?
От: CodeMonkey  
Дата: 08.06.19 20:11
Оценка:
Здравствуйте, Serginio1, Вы писали:

S>Условия для сборки мусора


S>

S>Сборка мусора возникает при выполнении одного из следующих условий:

S>Память, используемая объектами, выделенными в управляемой куче, превышает допустимый порог. Этот порог непрерывно корректируется во время выполнения процесса.

S>Там же про Эфемерные поколения и сегменты

S> и еще про параллелизмом


S>

S>Для компьютера с одним процессором всегда используется сборка мусора рабочей станции, независимо от значения элемента <gcServer>. Если задана сборка мусора сервера, среда CLR использует сборку мусора рабочей станции с выключенным параллелизмом.



И что? Где тут написано, почему оно так тормозит?
Re[9]: почему так медленно?
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 08.06.19 20:42
Оценка:
Здравствуйте, CodeMonkey, Вы писали:



CM>И что? Где тут написано, почему оно так тормозит?


Ну тормозит из-за сборки мусора. И написано когда сборка возникает.
и солнце б утром не вставало, когда бы не было меня
Отредактировано 08.06.2019 20:52 Serginio1 . Предыдущая версия .
Re[10]: почему так медленно?
От: CodeMonkey  
Дата: 08.06.19 21:34
Оценка:
Здравствуйте, Serginio1, Вы писали:

S> Ну тормозит из-за сборки мусора.


Просто не должна она так тормозить. Вот в корке даже пофиксили немного.
Re[11]: почему так медленно?
От: samius Япония http://sams-tricks.blogspot.com
Дата: 09.06.19 01:18
Оценка: +1
Здравствуйте, CodeMonkey, Вы писали:

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


S>> Ну тормозит из-за сборки мусора.


CM>Просто не должна она так тормозить. Вот в корке даже пофиксили немного.

Вот тут интересно. Почему это не должна она тормозить? А как же ей без тормозов проверять достижимости такого кол-ва объектов?
И не понятно, что именно в "корке" пофиксили, если TryStartNoGCRegion призван всего лишь предотвратить сборку.
Re[12]: почему так медленно?
От: CodeMonkey  
Дата: 09.06.19 03:46
Оценка:
Здравствуйте, samius, Вы писали:

S>Вот тут интересно. Почему это не должна она тормозить? А как же ей без тормозов проверять достижимости такого кол-ва объектов?


Ну во первых — не факт, что это именно сама сборка.

S>И не понятно, что именно в "корке" пофиксили, если TryStartNoGCRegion призван всего лишь предотвратить сборку.


Читать умеешь? Я чуть выше писал, что даже без вызова TryStartNoGCRegion в корке этот код работает намного быстрее.
Re[13]: почему так медленно?
От: samius Япония http://sams-tricks.blogspot.com
Дата: 09.06.19 04:31
Оценка:
Здравствуйте, CodeMonkey, Вы писали:

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


S>>Вот тут интересно. Почему это не должна она тормозить? А как же ей без тормозов проверять достижимости такого кол-ва объектов?


CM>Ну во первых — не факт, что это именно сама сборка.

Я вопрос задал, почему она (сборка) не должна тормозить в ответ на утверждение что она не должна так тормозить? Почему? Какие причины?

S>>И не понятно, что именно в "корке" пофиксили, если TryStartNoGCRegion призван всего лишь предотвратить сборку.


CM>Читать умеешь? Я чуть выше писал, что даже без вызова TryStartNoGCRegion в корке этот код работает намного быстрее.

А с вызовом еще быстрее.
Re[11]: почему так медленно?
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 09.06.19 07:04
Оценка:
Здравствуйте, CodeMonkey, Вы писали:

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


S>> Ну тормозит из-за сборки мусора.


CM>Просто не должна она так тормозить. Вот в корке даже пофиксили немного.

Ну есть факт, что тормозит из-за сборки мусора. Соответственно различные алгоритмы этой сборки существуют.
Самый эффективный это серверный вариант с параллельной сборкой и размером Эфемерных сегментов.
Сейчас развивает в основном Core там и GC оптимизируют.
Но суть в том, что если сборку не исползовать через TryStartNoGCRegion, то разница то небольшая
и солнце б утром не вставало, когда бы не было меня
Re[3]: почему так медленно?
От: vfedosov  
Дата: 09.06.19 07:15
Оценка: +3
Здравствуйте, Sharov, Вы писали:

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


O>>Второй момент структуры оптимизируются под стек насколько мне известно и даже если структура будет во втором цикле не явно создаваться, то пара процессорных операций со стеком аля push будет дешевле, чем целая логика менеджера памяти который работает с кучей. Не утверждаю что все именно так, не проверял.


S>Массив все равно будет в куче.


Массив то будет в куче, да только память будет выделена только один раз. В случае с классом, внутри массива будут лежать ссылки на объекты класса. Память под каждый объект класса будет выделена отдельно, что очень не быстро. В случае структур, в масссиве будут лежать сами инстанцы структур — никаких ссылок. Соответственно нет дополнительных аллокаций.
Re[12]: почему так медленно?
От: CodeMonkey  
Дата: 09.06.19 15:54
Оценка:
Здравствуйте, Serginio1, Вы писали:

S> Но суть в том, что если сборку не исползовать через TryStartNoGCRegion, то разница то небольшая


В два с лишним раза. Это "небольшая"?
Re[14]: почему так медленно?
От: CodeMonkey  
Дата: 09.06.19 15:54
Оценка:
Здравствуйте, samius, Вы писали:

S>Я вопрос задал, почему она (сборка) не должна тормозить в ответ на утверждение что она не должна так тормозить? Почему? Какие причины?


См выше.
Re: почему так медленно?
От: karbofos42 Россия  
Дата: 09.06.19 16:04
Оценка: 1 (1) +1
Здравствуйте, CodeMonkey, Вы писали:

CM>
  Скрытый текст
CM>    class Program
CM>    {
CM>        static void Main(string[] args)
CM>        {
CM>            var num = 10_000_000;

CM>            {
CM>                var watch = Stopwatch.StartNew();

CM>                var data = new TestClass[num];
CM>                for (var i = 0; i < data.Length; i++)
CM>                {
CM>                    data[i] = new TestClass
CM>                    {
CM>                        Number0 = 10,
CM>                        Number1 = 20,
CM>                    };
CM>                }

CM>                watch.Stop();
CM>                Console.WriteLine(watch.Elapsed.TotalSeconds);
CM>            }

CM>            {
CM>                var watch = Stopwatch.StartNew();

CM>                var data = new TestStruct[num];
CM>                for (var i = 0; i < data.Length; i++)
CM>                {
CM>                    data[i].Number0 = 10;
CM>                    data[i].Number1 = 20;
CM>                }

CM>                watch.Stop();
CM>                Console.WriteLine(watch.Elapsed.TotalSeconds);
CM>            }
CM>        }

CM>        struct TestStruct
CM>        {
CM>            public long Number0;
CM>            public long Number1;
CM>            public long Temp0;
CM>            public long Temp1;
CM>            public long Temp2;
CM>            public long Temp3;
CM>            public long Temp4;
CM>            public long Temp5;
CM>            public long Temp6;
CM>            public long Temp7;
CM>        }

CM>        class TestClass
CM>        {
CM>            public long Number0;
CM>            public long Number1;
CM>            public long Temp0;
CM>            public long Temp1;
CM>            public long Temp2;
CM>            public long Temp3;
CM>            public long Temp4;
CM>            public long Temp5;
CM>            public long Temp6;
CM>            public long Temp7;
CM>        }
CM>    }
CM>


CM>Получается:


CM>1.6395053

CM>0.2299897

CM>Откуда здесь разница аж в 8 раз?


выделение одного большого куска памяти дешевле, чем миллионов мелких

непонятно вот почему на таком варианте уже класс выигрывает:
  Скрытый текст
        static void Main(string[] args)
        {
            var num = 10_000_000;

            {
                var data = new TestClass[num];
                for (var i = 0; i < data.Length; i++)
                {
                    data[i] = new TestClass();
                }

                var watch = Stopwatch.StartNew();
                
                for (var i = 0; i < data.Length; i++)
                {
                    data[i].Number0 = 10;
                    data[i].Number1 = 20;
                }

                watch.Stop();
                Console.WriteLine(watch.Elapsed.TotalSeconds);
            }

            {
                var data = new TestStruct[num];

                var watch = Stopwatch.StartNew();
                
                for (var i = 0; i < data.Length; i++)
                {
                    data[i].Number0 = 10;
                    data[i].Number1 = 20;
                }

                watch.Stop();
                Console.WriteLine(watch.Elapsed.TotalSeconds);
            }
        }
Re[2]: почему так медленно?
От: CodeMonkey  
Дата: 09.06.19 17:03
Оценка: +1
Здравствуйте, karbofos42, Вы писали:

K>непонятно вот почему на таком варианте уже класс выигрывает:


Дык у тебя выделение памяти не учитывается в замере.
Re[3]: почему так медленно?
От: karbofos42 Россия  
Дата: 09.06.19 17:34
Оценка:
Здравствуйте, CodeMonkey, Вы писали:

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


K>>непонятно вот почему на таком варианте уже класс выигрывает:


CM>Дык у тебя выделение памяти не учитывается в замере.


так в этом и смысл. Почему запись в объекты быстрее, чем в структуры?
UPD. Причём на небольшом массиве (1000 элементов, например) структуры быстрее
Отредактировано 09.06.2019 17:38 karbofos42 . Предыдущая версия .
Re[5]: почему так медленно?
От: ylem  
Дата: 09.06.19 17:36
Оценка:
Y>>Это не долго.

CM>А ты на цифры посмотри. Вариант с классами в разы медленнее, чем со структурами.


Перепутал номера вариантов.
На всякий случай: массив структур выделяется за раз одним куском памяти.
Re[3]: почему так медленно?
От: alexzzzz  
Дата: 10.06.19 20:30
Оценка:
Здравствуйте, CodeMonkey, Вы писали:

Y>>Профайлер вам в помощь.

CM>Профайлеру низкоуровневые вещи вообще неведомы.

Что 2/3 времени CPU проводит внутри GC, профайлер может показать. На GC тратится столько времени, потому что в процессе создания 10 миллионов объектов он запускается 150 раз. 150 раз текущего размера кучи перестаёт хватать для добавления новых объектов.
Отредактировано 10.06.2019 20:39 alexzzzz . Предыдущая версия . Еще …
Отредактировано 10.06.2019 20:33 alexzzzz . Предыдущая версия .
Re[4]: почему так медленно?
От: alexzzzz  
Дата: 10.06.19 20:38
Оценка: 8 (2) +2 :)
Здравствуйте, karbofos42, Вы писали:

K>так в этом и смысл. Почему запись в объекты быстрее, чем в структуры?

K>UPD. Причём на небольшом массиве (1000 элементов, например) структуры быстрее

Чтобы сравнение классов и структур было честное, померяй вот так:

        {
            var data = new TestClass[num];
            for (var i = 0; i < data.Length; i++)
            {
                data[i] = new TestClass();
            }

            var watch = Stopwatch.StartNew();

            for (var i = 0; i < data.Length; i++)
            {
                data[i].Number0 = 10;
                data[i].Number1 = 20;
            }

            watch.Stop();
            Console.WriteLine(watch.Elapsed.TotalSeconds);
        }

        {
            var data = new TestStruct[num];
            for (var i = 0; i < data.Length; i++)
            {
                data[i] = default;
            }

            var watch = Stopwatch.StartNew();

            for (var i = 0; i < data.Length; i++)
            {
                data[i].Number0 = 10;
                data[i].Number1 = 20;
            }

            watch.Stop();
            Console.WriteLine(watch.Elapsed.TotalSeconds);
        }


0,1233852
0,1100268
Отредактировано 10.06.2019 20:40 alexzzzz . Предыдущая версия .
Re[5]: почему так медленно?
От: alexzzzz  
Дата: 10.06.19 21:00
Оценка: 6 (1) +1
Здравствуйте, alexzzzz, Вы писали:

A>
0,1233852
A>0,1100268


Хотя теперь у меня вопрос. Почему предварительная очистка массива структур в два раза ускоряет последующие присваивания полям этих структур?

Я сначала предположил, что после обнуления массив оказывается в кэше, поэтому потом всё идёт быстрее. Но 800 Мб многовато для кэша.

Потом предположил, что дело в работе виртуальной памяти ― 800 Мб типа виртуально «выделились», но страницам ОЗУ пока не сопоставлены. Но ведь конструктор массива должен гарантировать, что по умолчанию значения всех элементов в нём будут «по умолчанию». Память должна была и так занулиться при создании массива. Что такого даёт повторное ручное зануление, что последующие манипуляции с массивом становятся в два раза быстрее?

PS
Всё-таки это проделки виртуальной памяти:
        {
            var processName = Process.GetCurrentProcess().ProcessName;
            var pageFaultsCounter = new PerformanceCounter("Process", "Page Faults/sec", processName);

            var data = new TestStruct[num];
            Console.WriteLine($"Initial: {pageFaultsCounter.RawValue} page faults");

            Array.Clear(data, 0, data.Length);
            Console.WriteLine($"Intermediate: {pageFaultsCounter.RawValue} page faults");

            var watch = Stopwatch.StartNew();

            for (var i = 0; i < data.Length; i++)
            {
                data[i].Number0 = 10;
                data[i].Number1 = 20;
            }

            watch.Stop();
            Console.WriteLine($"Final: {pageFaultsCounter.RawValue} page faults");
            Console.WriteLine(watch.Elapsed.TotalSeconds);
        }

Initial: 344202 page faults
Intermediate: 540040 page faults
Final: 540544 page faults


Array.Clear приводит к 200 тысячам ошибок страниц. Помножаем на 4 кб на страницу, получаем наши 800 Мб. Если Array.Clear закомментировать, эти же 200 тысяч ошибок страниц приходятся на работу цикла. Если Array.Clear сделать два раза, второй раз ничего не меняет.

PPS
Получается, рантайм NET Framework такой умный, что знает, что Windows отдаёт процессу уже занулённые страницы, поэтому самостоятельно массив нулями не инициализирует.
Отредактировано 10.06.2019 21:53 alexzzzz . Предыдущая версия . Еще …
Отредактировано 10.06.2019 21:50 alexzzzz . Предыдущая версия .
Отредактировано 10.06.2019 21:48 alexzzzz . Предыдущая версия .
Отредактировано 10.06.2019 21:47 alexzzzz . Предыдущая версия .
Отредактировано 10.06.2019 21:37 alexzzzz . Предыдущая версия .
Отредактировано 10.06.2019 21:31 alexzzzz . Предыдущая версия .
Отредактировано 10.06.2019 21:03 alexzzzz . Предыдущая версия .
Re[4]: почему так медленно?
От: okon  
Дата: 11.06.19 08:04
Оценка:
Здравствуйте, karbofos42, Вы писали:

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


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


K>>>непонятно вот почему на таком варианте уже класс выигрывает:


CM>>Дык у тебя выделение памяти не учитывается в замере.


K>так в этом и смысл. Почему запись в объекты быстрее, чем в структуры?

K>UPD. Причём на небольшом массиве (1000 элементов, например) структуры быстрее

Вероятно боксинг + копирование данных структуры.
Т.е. a[i].Number = 1 может выполняться как temp = a[i]; temp.Number = 1; a[i] = temp; где temp = это временная локальная структура в которую копируются данные a[i]
”Жить стало лучше... но противнее. Люди которые ставят точку после слова лучше становятся сторонниками Путина, наши же сторонники делают акцент на слове противнее ( ложь, воровство, лицемерие, вражда )." (с) Борис Немцов
Re[6]: почему так медленно?
От: pugv Россия  
Дата: 11.06.19 08:33
Оценка: 6 (1)
Здравствуйте, alexzzzz, Вы писали:

A> Получается, рантайм NET Framework такой умный, что знает, что Windows отдаёт процессу уже занулённые страницы, поэтому самостоятельно массив нулями не инициализирует.


Знает. В винде начиная с ядра NT в системном процессе есть специальный поток, обнуляющий свободные страницы в фоне. Это нужно, чтобы процесс, которому отдали страницу не мог достать данные, которые туда положил предыдущий собственник (класс защиты C2).
Re[6]: почему так медленно?
От: CodeMonkey  
Дата: 11.06.19 14:44
Оценка:
Здравствуйте, alexzzzz, Вы писали:

A>Получается, рантайм NET Framework такой умный, что знает, что Windows отдаёт процессу уже занулённые страницы, поэтому самостоятельно массив нулями не инициализирует.


Не такой и умный, если это просаживает производительность.
Re[4]: почему так медленно?
От: CodeMonkey  
Дата: 11.06.19 14:47
Оценка:
Здравствуйте, alexzzzz, Вы писали:

A>Что 2/3 времени CPU проводит внутри GC, профайлер может показать. На GC тратится столько времени, потому что в процессе создания 10 миллионов объектов он запускается 150 раз. 150 раз текущего размера кучи перестаёт хватать для добавления новых объектов.


Не 2/3, а 32% согласно профайлеру. Разницу в разы это не объясняет.
Re[13]: почему так медленно?
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 11.06.19 15:49
Оценка:
Здравствуйте, CodeMonkey, Вы писали:

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


S>> Но суть в том, что если сборку не исползовать через TryStartNoGCRegion, то разница то небольшая


CM>В два с лишним раза. Это "небольшая"?

Ну нужно время на размещение в куче обнуление полей, запись ссылки объекта в массив, сдвиг указателя. Это как раз сопоставимо с установкой двух полей
и солнце б утром не вставало, когда бы не было меня
Отредактировано 12.06.2019 18:46 Serginio1 . Предыдущая версия .
Re[5]: почему так медленно?
От: alexzzzz  
Дата: 11.06.19 16:24
Оценка:
Здравствуйте, CodeMonkey, Вы писали:

CM>Не 2/3, а 32% согласно профайлеру. Разницу в разы это не объясняет.


У меня заходит за 60%.

A>>Получается, рантайм NET Framework такой умный, что знает, что Windows отдаёт процессу уже занулённые страницы, поэтому самостоятельно массив нулями не инициализирует.

CM>Не такой и умный, если это просаживает производительность.

Так наоборот же. Рантайм не тратит время на то, что и так было сделано. В таком же тесте классов ошибок страниц в 1,5 раза больше, потому что памяти требуется больше. У классов они просто не включались в измерения, а у структур включались.

  C#
using System;
using System.Diagnostics;
using System.Linq;

class Program
{
    static void Main(string[] args)
    {
        var num = 10_000_000;
        var processName = Process.GetCurrentProcess().ProcessName;
        var pageFaultsCounter = new PerformanceCounter("Process", "Page Faults/sec", processName);

        {
            var data = new TestClass[num];
            Console.WriteLine($"1) {pageFaultsCounter.RawValue} page faults");

            //PrintGCollections();
            for (var i = 0; i < data.Length; i++)
            {
                data[i] = new TestClass();
            }
            //PrintGCollections();
            Console.WriteLine($"2) {pageFaultsCounter.RawValue} page faults");

            var watch = Stopwatch.StartNew();
            for (var i = 0; i < data.Length; i++)
            {
                data[i].Number0 = 10;
                data[i].Number1 = 20;
            }
            watch.Stop();
            Console.WriteLine($"3) {pageFaultsCounter.RawValue} page faults");
            Console.WriteLine(watch.Elapsed.TotalSeconds);
        }

        {
            var data = new TestStruct[num];
            Console.WriteLine($"1) {pageFaultsCounter.RawValue} page faults");

            Array.Clear(data, 0, data.Length);
            Console.WriteLine($"2) {pageFaultsCounter.RawValue} page faults");

            var watch = Stopwatch.StartNew();
            for (var i = 0; i < data.Length; i++)
            {
                data[i].Number0 = 10;
                data[i].Number1 = 20;
            }
            watch.Stop();
            Console.WriteLine($"3) {pageFaultsCounter.RawValue} page faults");
            Console.WriteLine(watch.Elapsed.TotalSeconds);
        }
    }

    private static void PrintGCollections()
    {
        Console.WriteLine("GC: " + string.Join(", ", Enumerable.Range(0, GC.MaxGeneration + 1).Select(GC.CollectionCount)));
    }

    struct TestStruct
    {
        public long Number0;
        public long Number1;
        public long Temp0;
        public long Temp1;
        public long Temp2;
        public long Temp3;
        public long Temp4;
        public long Temp5;
        public long Temp6;
        public long Temp7;
    }

    class TestClass
    {
        public long Number0;
        public long Number1;
        public long Temp0;
        public long Temp1;
        public long Temp2;
        public long Temp3;
        public long Temp4;
        public long Temp5;
        public long Temp6;
        public long Temp7;
    }
}

1) 36837 page faults
2) 345167 page faults
3) 345640 page faults
0,1460079
1) 350326 page faults
2) 546109 page faults
3) 546580 page faults
0,1018719


Структуры стабильно быстрее.
Отредактировано 11.06.2019 16:28 alexzzzz . Предыдущая версия . Еще …
Отредактировано 11.06.2019 16:28 alexzzzz . Предыдущая версия .
Re[6]: почему так медленно?
От: Sharov Россия  
Дата: 11.06.19 16:30
Оценка:
Здравствуйте, alexzzzz, Вы писали:

A>Так наоборот же. Рантайм не тратит время на то, что и так было сделано. В таком же тесте классов ошибок страниц в 1,5 раза больше, потому что памяти требуется больше. У классов они просто не включались в измерения, а у структур включались.



A>}[/cs][/cut]

A>
1) 36837 page faults
A>2) 345167 page faults
A>3) 345640 page faults
A>0,1460079
A>1) 350326 page faults
A>2) 546109 page faults
A>3) 546580 page faults
A>0,1018719


А не меньше? А то цифры показывают другое?
Кодом людям нужно помогать!
Re[6]: почему так медленно?
От: CodeMonkey  
Дата: 11.06.19 16:38
Оценка:
Здравствуйте, alexzzzz, Вы писали:

A>У классов они просто не включались в измерения, а у структур включались.


Почему это не включались?
Re[7]: почему так медленно?
От: alexzzzz  
Дата: 11.06.19 16:54
Оценка: 12 (1)
Здравствуйте, Sharov, Вы писали:

S>А не меньше? А то цифры показывают другое?


Не. Цифры показывают количество ошибок, накопленное со старта процесса. Тут интересны скачки. Инициализация массива объектов добавила примерно 300 тысяч ошибок, инициализация массива структур ― примерно 200 тысяч.

PS
Строго говоря, счётчик показывает количество ошибок в секунду (Page Faults/sec), но так как вся работа занимает меньше секунды, то можно считать, что это накопленное количество ошибок со старта процесса.
Отредактировано 09.07.2019 8:34 alexzzzz . Предыдущая версия .
Re[7]: почему так медленно?
От: alexzzzz  
Дата: 11.06.19 17:04
Оценка:
Здравствуйте, CodeMonkey, Вы писали:

A>>У классов они просто не включались в измерения, а у структур включались.

CM>Почему это не включались?

Ну у karbofos42 так в тесте получилось: http://rsdn.org/forum/dotnet/7466200.1
Автор: karbofos42
Дата: 09.06.19


У классов все ошибки происходят в первом цикле в строке
data[i] = new TestClass();

до запуска таймера.
Отредактировано 11.06.2019 17:05 alexzzzz . Предыдущая версия .
Re[8]: почему так медленно?
От: CodeMonkey  
Дата: 11.06.19 17:15
Оценка:
Здравствуйте, alexzzzz, Вы писали:

A>Ну у karbofos42 так в тесте получилось: http://rsdn.org/forum/dotnet/7466200.1
Автор: karbofos42
Дата: 09.06.19


A>У классов все ошибки происходят в первом цикле в строке

A>
data[i] = new TestClass();

A>до запуска таймера.

А в моих замерах — включалось.
Re[9]: почему так медленно?
От: alexzzzz  
Дата: 11.06.19 20:39
Оценка: 6 (1)
Здравствуйте, CodeMonkey, Вы писали:

CM>А в моих замерах — включалось.


У тебя 150-160 пусков GC, которому требуется постоянно увеличивать кучу, помечать объекты живыми, перекидывать их из поколения в поколение (хотя сами объекты в памяти вроде не перемещаются). Процессорному кэшу при этом тоже вряд ли хорошо. Объектов в пике 10 миллионов.

Массив структур же достаточно один раз линейно пробежать. Половина времени тратится на soft page faults, вторая половина на собственно запись полей.

Скачал на попробовать dotTrace. Режим Sampling показывает, что внутри GC от запуска к запуску проводится 60-80% времени. На пользовательский код приходится что-то типа 15% времени.

В Mono оригинальный тест с классами работает на полсекунды быстрее, чем в NET Framework, хотя GC в Mono запускается >220 раз.
Отредактировано 11.06.2019 20:40 alexzzzz . Предыдущая версия .
Re[10]: почему так медленно?
От: CodeMonkey  
Дата: 12.06.19 15:03
Оценка:
Здравствуйте, alexzzzz, Вы писали:

A>Скачал на попробовать dotTrace. Режим Sampling показывает, что внутри GC от запуска к запуску проводится 60-80% времени. На пользовательский код приходится что-то типа 15% времени.


Ага, у меня он самый. В этом режиме показывает 46% на сборку мусора и еще 33% какой-то Special. Что это за зверь?
Re: почему так медленно?
От: alexzzzz  
Дата: 12.06.19 16:58
Оценка:
Цикл с классами:



Первый call создаёт объект.
Второй call пишет его в массив. Пишет как-то слишком сложно. Подозреваю, что происходит какая-то проверка типов. Была мысль про ARRAY COVARIANCE: NOT JUST UGLY, BUT SLOW TOO, но решение из статьи заметно на скорость не повлияло.

Цикл со структурами:

Отредактировано 12.06.2019 19:08 alexzzzz . Предыдущая версия . Еще …
Отредактировано 12.06.2019 19:05 alexzzzz . Предыдущая версия .
Re[2]: почему так медленно?
От: CodeMonkey  
Дата: 12.06.19 18:46
Оценка: :)
Здравствуйте, alexzzzz, Вы писали:

"You IP is blacklisted...."

A>Второй call пишет его в массив. Пишет как-то слишком сложно. Подозреваю, что происходит какая-то проверка типов.


Скорее всего, это write barrier. Который здесь пока никто не упоминал
Re[3]: почему так медленно?
От: alexzzzz  
Дата: 12.06.19 19:57
Оценка:
Здравствуйте, CodeMonkey, Вы писали:

CM>Скорее всего, это write barrier. Который здесь пока никто не упоминал


Непохоже. Там проверки на выход на границы массива, ещё какие-то проверки, запись в массив и, кажется, ещё какая-то проверка.
Отредактировано 12.06.2019 19:59 alexzzzz . Предыдущая версия .
Re[4]: почему так медленно?
От: CodeMonkey  
Дата: 12.06.19 20:17
Оценка:
Здравствуйте, alexzzzz, Вы писали:

A>Там проверки на выход на границы массива, ещё какие-то проверки, запись в массив и, кажется, ещё какая-то проверка.


Нет, это не то.
Re: почему так медленно?
От: itmanager85  
Дата: 13.06.19 00:32
Оценка: 1 (1) -1 :)
Здравствуйте, CodeMonkey, Вы писали:

CM>Получается:


CM>1.6395053

CM>0.2299897

CM>Откуда здесь разница аж в 8 раз?


аххаха, смешно чувак открыл для себя что выделение памяти — не бесплатная операция .. (а тем более создание экземляра класса) ..

вкратце, в первом случае ты изначально выделил место под массив указателей на объекты класса, а потом выделяешь память (в цикле) — под каждый объект класса и присваиваешь указателю .. (ссылку на него)

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


вот тут показательный пример уже привели
http://rsdn.org/forum/dotnet/7467268.1
Автор: alexzzzz
Дата: 10.06.19
Отредактировано 13.06.2019 0:34 itmanager85 . Предыдущая версия .
Re[2]: почему так медленно?
От: itmanager85  
Дата: 13.06.19 07:04
Оценка: +1 -1 :)
Здравствуйте, itmanager85, Вы писали:

CodeMonkey, и за что же минус позвольте полюбопытствовать — или как то подгадить хочется а по сути возразить и нечего ?
Re: почему так медленно?
От: vvv848165@ya.ru  
Дата: 28.06.19 09:00
Оценка: :)
Здравствуйте, CodeMonkey, Вы писали:

new TestClass

new вызывает GC сборщик мусора до выделения памяти и обходит все переменные
Если б ты его распараллелил то разница была бы больше чем в 100 раз...
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.