Как я узнаю о CLR, чтобы получить обоснованные предположения о проблемах с производительностью?

Да, я использую профилировщик (ANTS). Но на микроуровне он не может сказать вам, как исправить вашу проблему. И сейчас я на стадии микрооптимизации. Например, я профилировал это:

for (int x = 0; x < Width; x++)
{
    for (int y = 0; y < Height; y++)
    {
        packedCells.Add(Data[x, y].HasCar);
        packedCells.Add(Data[x, y].RoadState);
        packedCells.Add(Data[x, y].Population);
    }
}

ANTS показало, что линия y-loop занимает много времени. Я думал, что это связано с тем, что он постоянно должен называть Get get. Поэтому я создал локальный int height = Height; перед циклами и сделал проверку внутреннего цикла для y < height. Это действительно ухудшило производительность! ANTS теперь сказал мне, что проблема с x-loop-линией была проблемой. А? Это должно быть незначительным, это внешняя петля!

В конце концов у меня появилось откровение - возможно, с использованием свойства для привязки к внешнему циклу и локального для CLR-перехода, связанного с внутренним циклом, часто между кешем "locals" и кешем этого "указателя" (I 'm привык думать в терминах кэша CPU). Поэтому я создал локальную и для Width, и это исправило ее.

Оттуда было ясно, что я должен сделать локальный и для Data, даже несмотря на то, что Data не был даже свойством (это было поле). И действительно, это купило мне еще большую производительность.

Сбивчиво, однако, переупорядочение петель x и y (для улучшения использования кеша) сделало нулевую разницу, хотя массив огромен (3000x3000).

Теперь я хочу узнать, почему материал, который я сделал, улучшил производительность. Какую книгу вы предлагаете прочитать?

Ответ 1

CLR через С# Джеффри Рихтера.

Это такая замечательная книга, что кто-то украл ее в моей библиотеке вместе с С# in depth.

Ответ 2

CLR здесь вообще не задействован, все это должно быть переведено на прямой машинный код без вызовов в CLR. Компилятор JIT отвечает за создание этого машинного кода, у него есть оптимизатор, который пытается найти наиболее эффективный код. У этого есть ограничения, он не может потратить на это много времени.

Одна из важных вещей, которую он делает, - выяснить, какие локальные переменные должны храниться в регистрах CPU. Это то, что изменилось, когда вы поместили свойство Height в локальную переменную. Возможно, он решил сохранить эту переменную в регистре. Но теперь есть еще меньше доступных для хранения другой переменной. Как переменная x или y, критическая для скорости. Да, это замедлит его.

У вас плохая диагностика внешнего цикла. Это может быть вызвано оптимизатором JIT, переустанавливающим код цикла, что дает профайлеру более сложное отображение кода машины обратно в соответствующий оператор С#.

Аналогично, оптимизатор мог решить, что вы использовали массив неэффективно и переключили порядок индексирования. Не уверен, что это действительно так, но не невозможно.

Anyhoo, единственный способ получить представление о нем - посмотреть на сгенерированный машинный код. Есть много достойных книг об ассемблере x86, хотя в наши дни их немного сложно найти. Отправной точкой является Debug + Windows + Disassembly.

Имейте в виду, что даже машинный код не очень хороший предиктор того, как эффективный код будет запущен. Современные ядра процессора чрезвычайно сложны, и машинный код больше не является репрезентативным для того, что на самом деле происходит внутри ядра. Единственный проверенный способ - это то, что вы уже делали: пробная версия и ошибка.

Ответ 3

Альбин - нет. Честно говоря, я не думал, что работа вне профилировщика изменит разницу в производительности, поэтому я не стал беспокоиться. Думаешь, мне нужно? Это было проблемой для вас раньше? (Я компилирую с оптимизациями хотя)

Запуск под отладчиком изменяет производительность: когда он запускается под отладчиком, компилятор "точно в момент времени" автоматически отключает оптимизацию (чтобы упростить отладку)!

Если вам нужно, используйте отладчик для присоединения к уже запущенному уже JIT-процессу.

Ответ 4

Одна вещь, которую вы должны знать о работе с массивами, заключается в том, что CLR всегда будет следить за тем, чтобы индексы массивов не были вне пределов. Он имеет оптимизацию для одномерных массивов, но не для 2+ измерений.

Зная это, вы можете захотеть сравнить MyCell Data[][] вместо MyCell Data[,]

Ответ 5

Hm, я не думаю, что регистрация цикла является реальной проблемой. 1. Я попытался бы избежать доступа к массиву Data три раза за внутренний цикл. 2. Я также рекомендовал бы пересмотреть три оператора Add: вы, по-видимому, обращаетесь к коллекции три раза, чтобы добавить тривиальные некоторые данные. Сделайте его только одним доступом на итерацию и добавьте тип данных, содержащий три записи:

for (int y = 0; ... {
 tTemp = Data[x, y];
 packedCells.Add(new {
  tTemp.HasCar, tTemp.RoadState, tTemp.Population 
 });
}

Другой взгляд показывает, что вы в основном векторизуете матрицу, скопировав ее в массив (или какой-нибудь другой последовательный сбор)... Это вообще необходимо? Почему бы вам просто не определить специализированный индексатор, который имитирует этот линейный доступ? Еще лучше, если вам нужно только перечислить записи (в этом примере вы делаете, не требуется никакого произвольного доступа), почему бы вам не использовать адекватное выражение LINQ?

Ответ 6

Пункт 1) Образованные догадки - это не способ настройки производительности. В этом случае я могу догадаться, как и большинство, но угадать - неправильный способ сделать это.

Пункт 2) Профилировщики должны быть хорошо поняты, прежде чем вы узнаете, что они на самом деле говорят вам. Здесь обсуждаются проблемы. Например, то, что делают многие профилиры, говорит вам "где программа тратит свое время", то есть где счетчик программ тратит свое время, поэтому они почти абсолютно слепы к времени, запрошенному вызовами функций, что, по-видимому, состоит из вашего внутреннего цикла.

Я очень много настраиваю, и вот что я делаю. Я переключаюсь между двумя действиями:

  • Общее измерение времени. Это не требует специальных инструментов. Я не пытаюсь измерить отдельные процедуры.

  • "Узкое место". Это не требует запуска кода с любой скоростью, потому что я не измеряю. То, что я делаю, - это расположение строк кода, которые отвечают за значительный процент времени. Я знаю, какие строки они есть, потому что они находятся в стеке для этого процента, и образцы стека легко находят их.

Как только я нахожу "узкое место" и исправляю его, я возвращаюсь к первому шагу, измеряю, какой процент времени я сохранял, и делаю все это снова на следующем "узком месте", обычно от 2 до 6 раз. Мне помогает "эффект увеличения", в котором фиксированная проблема увеличивает процент, используемый оставшимися проблемами. Он работает как для макро-, так и для микро-оптимизации.

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

Ответ 7

Поскольку комментарий может зависеть, я повторяю: довольно громоздко оптимизировать код, который, по сути, излишний. Вам не нужно явно линеаризовать вашу матрицу вообще, см. Комментарий выше: Определите линеаризационный адаптер, который реализует IEnumerable<MyCell> и подает его в форматтер.

Я получаю предупреждение, когда я пытаюсь добавить еще один ответ, поэтому я собираюсь переработать это.:) После прочтения комментариев Стива и размышления об этом некоторое время я предлагаю следующее: Если сериализация многомерного массива слишком медленная (не пытайтесь, я просто считаю, что вы...) вообще не используете ее! Оказывается, ваша матрица не разрежена и имеет фиксированные размеры. Так что определите структуру, в которой ваши ячейки будут содержать простой линейный массив с индексом:

[Serializable()]
class CellMatrix {
 Cell [] mCells;

 public int Rows { get; }
 public int Columns { get; }

 public Cell this (int i, int j) {
  get {
   return mCells[i + Rows * j];    
  }   
  // setter...
 }

 // constructor taking rows/cols...
}

Вещество вроде этого должно сериализоваться так же быстро, как и собственный массив... Я не рекомендую жестко кодировать макет Cell, чтобы сохранить там несколько байтов...

Cheers, Пол