Здравствуйте, 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;
}
}
Здравствуйте, alexzzzz, Вы писали:
A>Так наоборот же. Рантайм не тратит время на то, что и так было сделано. В таком же тесте классов ошибок страниц в 1,5 раза больше, потому что памяти требуется больше. У классов они просто не включались в измерения, а у структур включались.
Здравствуйте, Sharov, Вы писали:
S>А не меньше? А то цифры показывают другое?
Не. Цифры показывают количество ошибок, накопленное со старта процесса. Тут интересны скачки. Инициализация массива объектов добавила примерно 300 тысяч ошибок, инициализация массива структур ― примерно 200 тысяч.
PS
Строго говоря, счётчик показывает количество ошибок в секунду (Page Faults/sec), но так как вся работа занимает меньше секунды, то можно считать, что это накопленное количество ошибок со старта процесса.
Здравствуйте, CodeMonkey, Вы писали:
CM>А в моих замерах — включалось.
У тебя 150-160 пусков GC, которому требуется постоянно увеличивать кучу, помечать объекты живыми, перекидывать их из поколения в поколение (хотя сами объекты в памяти вроде не перемещаются). Процессорному кэшу при этом тоже вряд ли хорошо. Объектов в пике 10 миллионов.
Массив структур же достаточно один раз линейно пробежать. Половина времени тратится на soft page faults, вторая половина на собственно запись полей.
Скачал на попробовать dotTrace. Режим Sampling показывает, что внутри GC от запуска к запуску проводится 60-80% времени. На пользовательский код приходится что-то типа 15% времени.
В Mono оригинальный тест с классами работает на полсекунды быстрее, чем в NET Framework, хотя GC в Mono запускается >220 раз.
Здравствуйте, alexzzzz, Вы писали:
A>Скачал на попробовать dotTrace. Режим Sampling показывает, что внутри GC от запуска к запуску проводится 60-80% времени. На пользовательский код приходится что-то типа 15% времени.
Ага, у меня он самый. В этом режиме показывает 46% на сборку мусора и еще 33% какой-то Special. Что это за зверь?
Первый call создаёт объект.
Второй call пишет его в массив. Пишет как-то слишком сложно. Подозреваю, что происходит какая-то проверка типов. Была мысль про ARRAY COVARIANCE: NOT JUST UGLY, BUT SLOW TOO, но решение из статьи заметно на скорость не повлияло.
Здравствуйте, alexzzzz, Вы писали:
A>Там проверки на выход на границы массива, ещё какие-то проверки, запись в массив и, кажется, ещё какая-то проверка.
Здравствуйте, CodeMonkey, Вы писали:
CM>Получается:
CM>1.6395053 CM>0.2299897
CM>Откуда здесь разница аж в 8 раз?
аххаха, смешно чувак открыл для себя что выделение памяти — не бесплатная операция .. (а тем более создание экземляра класса) ..
вкратце, в первом случае ты изначально выделил место под массив указателей на объекты класса, а потом выделяешь память (в цикле) — под каждый объект класса и присваиваешь указателю .. (ссылку на него)
очевидно что это дикий жир (в т.ч. требующий вызова системных функций и т.д.) — по сравнению с тем чтобы просто выделить память под массив (одним куском) и лупить (в цикле) туда пару значений переменных ..