Резюме
При обработке большого текстового файла я столкнулся с следующей (неожиданной) деградацией производительности, которую я не могу объяснить. Мои цели для этого вопроса:
- Понять, что вызывает замедление, описанное ниже
- Узнайте, как быстро инициализировать большие непримитивные массивы.
Проблема
- Массив содержит не примитивные ссылочные позиции
- Не
int[]
, ноMyComplexType[]
-
MyComplexType
- это класс, а не структура -
MyComplexType
содержит некоторые свойстваstring
- Не
- Массив предварительно выделен
- Массив большой
- Если элемент создан и используется без назначения массиву, программа выполняется быстро
- Если элемент создан, а затем назначен элементу массива, программа значительно замедляется
- Я ожидал, что назначение элемента массива будет простым ссылочным назначением, но это не похоже на случай, основанный на моих результатах для моей тестовой программы ниже
Программа тестирования
Рассмотрим следующую программу C#
:
namespace Test
{
public static class Program
{
// Simple data structure
private sealed class Item
{
public Item(int i)
{
this.Name = "Hello " + i;
//this.Name = "Hello";
//this.Name = null;
}
public readonly string Name;
}
// Test program
public static void Main()
{
const int length = 1000000;
var items = new Item[length];
// Create one million items but don't assign to array
var w = System.Diagnostics.Stopwatch.StartNew();
for (var i = 0; i < length; i++)
{
var item = new Item(i);
if (!string.IsNullOrEmpty(item.Name)) // reference the item and its Name property
{
items[i] = null; // do not remember the item
}
}
System.Console.Error.WriteLine("Without assignment: " + w.Elapsed);
// Create one million items and assign to array
w.Restart();
for (var i = 0; i < length; i++)
{
var item = new Item(i);
if (!string.IsNullOrEmpty(item.Name)) // reference the item and its Name property
{
items[i] = item; // remember the item
}
}
System.Console.Error.WriteLine(" With assignment: " + w.Elapsed);
}
}
}
Он содержит две почти одинаковые петли. Каждый цикл создает миллион экземпляров класса Item
. Первый цикл использует созданный элемент, а затем отбрасывает ссылку (не сохраняя ее в массиве items
). Второй цикл использует созданный элемент, а затем сохраняет ссылку в массиве items
. Назначение элемента массива - это единственное отличие между циклами.
Мои результаты
-
Когда я запускаю
Release
build (оптимизация включена) на моем компьютере, я получаю следующие результаты:Without assignment: 00:00:00.2193348 With assignment: 00:00:00.8819170
Цикл с назначением массива значительно медленнее, чем тот, у которого нет назначения (~ 4 раза медленнее).
-
Если я изменяю конструктор
Item
, чтобы назначить константную строку свойствуName
:public Item(int i) { //this.Name = "Hello " + i; this.Name = "Hello"; //this.Name = null; }
Получаю следующие результаты:
Without assignment: 00:00:00.0228067 With assignment: 00:00:00.0718317
Петля с назначением все еще ~ 3 раза медленнее, чем без
-
Наконец, если я присвою
null
свойствуName
:public Item(int i) { //this.Name = "Hello " + i; //this.Name = "Hello"; this.Name = null; }
Получаю следующий результат:
Without assignment: 00:00:00.0146696 With assignment: 00:00:00.0105369
После того, как строка не выделена, версия без назначения, наконец, немного медленнее (я предполагаю, что все эти экземпляры выпущены для сбора мусора)
Вопросы
-
Почему назначение элемента массива так сильно замедляет тестовую программу?
-
Есть ли конструкция атрибута/языка/etc, которая ускорит назначение?
PS: Я попытался изучить замедление с помощью dotTrace, но это было неубедительно. Одна вещь, которую я видел, - это намного больше операций копирования строк и сбора мусора в цикле с назначением, чем в цикле без назначения (хотя я и ожидал обратного).