Учитывая задачу улучшить производительность фрагмента кода, я столкнулся со следующим явлением.У меня есть большая коллекция ссылочных типов в общей очереди, и я удаляю и обрабатываю элемент один за другим, а затем добавляю их в другую общую коллекцию.
Кажется, чем больше элементы, тем больше времени у неготребуется, чтобы добавить элемент в коллекцию.
Пытаясь сузить проблему до соответствующей части кода, я написал тест (исключая обработку элементов, просто делая вставку):
class Small
{
public Small()
{
this.s001 = "001";
this.s002 = "002";
}
string s001;
string s002;
}
class Large
{
public Large()
{
this.s001 = "001";
this.s002 = "002";
...
this.s050 = "050";
}
string s001;
string s002;
...
string s050;
}
static void Main(string[] args)
{
const int N = 1000000;
var storage = new List<object>(N);
for (int i = 0; i < N; ++i)
{
//storage.Add(new Small());
storage.Add(new Large());
}
List<object> outCollection = new List<object>();
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = N-1; i > 0; --i)
{
outCollection.Add(storage[i];);
}
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
}
НаДля тестовой машины, использующей класс Small, для ее запуска требуется около 25-30 мс, а для Large - 40-45 мс.Я знаю, что время от времени должна увеличиваться коллекция outCollection, чтобы иметь возможность хранить все элементы, поэтому существует некоторое динамическое распределение памяти.Но с учетом начального размера коллекции разница становится еще более очевидной: 11–12 мс для объектов Small и 35–38 мс для объектов Large.
Я несколько удивлен, поскольку это ссылочные типы, поэтому я ожидалколлекции работают только со ссылками на экземпляры Small / Large.Я прочитал соответствующую статью Эрика Липперта и знаю, что ссылки не должны рассматриваться как указатели.В то же время, AFAIK в настоящее время они реализованы в виде указателей, и их размер и производительность коллекции должны быть независимы от размера элемента.
Я решил задать здесь вопрос, надеясь, что кто-то сможет объяснить или помочь мнечтобы понять, что здесь происходит.Помимо повышения производительности, мне действительно любопытно, что происходит за кулисами.
Обновление: профилирование данных с использованием диагностических инструментов мне не сильно помогло, хотя я должен признать, что я не эксперт, использующийпрофилировщик.Позже сегодня я соберу больше данных, чтобы выяснить, где находится узкое место.
Конечно, давление на ГХ довольно высокое, особенно в случаях Large
.Но как только экземпляры созданы и сохранены в коллекции storage
, и программа входит в цикл, коллекция больше не запускается, и использование памяти существенно не увеличилось (outCollction
уже предварительно выделено).
Большая часть процессорного времени, конечно, тратится на выделение памяти (JIT_New), около 62%, и единственной другой важной записью является Имя функции.List`1 [System .__ Canon] .Добавить около 7%.
Для 1 миллиона элементов предварительно выделенный размер outCollection
составляет 8 миллионов байтов (аналогично размеру storage
);можно заподозрить, что в коллекциях хранятся 64-битные адреса.
Возможно, я не использую инструменты должным образом или не имею опыта правильно интерпретировать результаты, но профилировщик не помог мне получитьближе к делу.Если цикл не запускает коллекции и копирует только указатели между 2 предварительно выделенными коллекциями, как может размер элемента вызывать какие-либо различия?Предполагается, что соотношение попаданий / промахов кэша в обоих случаях будет более или менее одинаковым, поскольку в обоих случаях цикл представляет собой итерацию по списку «адресов».
Спасибо за всю помощь, покасоберет больше данных и разместит здесь обновление, если что-нибудь найдет.