Быстрая адресация массива
Я запускаю код анализа изображения в массиве, хранящем информацию об изображении. К сожалению, код очень тяжелый и занимает в среднем 25 секунд для запуска через один кадр. Основная проблема, которую я вижу, - это адресация массива. Какой из них наиболее быстрый для запуска через 2d-массив и существуют ли какие-либо различия в
горизонтальный, а затем вертикальный
for (int y = 0; y < array.Length; ++y)
for (int x = 0; x < array[].Length; ++x)
//Code using array[y][x]
и вертикальный, затем горизонтальный?
for (int x = 0; x < array[].Length; ++x)
for (int y = 0; y < array.Length; ++y)
//Code using array[y][x]
Кроме того, я попытался избежать прямой адресации и использовать указатели вместо этого.
for (int y = 0; y < array.Length; ++y)
int* ptrArray = (int*)array[0];
for (int x = 0; x < array[].Length; ++x, ++ptrArray)
//Code using ptrArray for array[y][x]
или
for (int x = 0; x < array[].Length; ++x)
int* ptrArray = (int*)array[0];
for (int y = 0; y < array.Length; ++y, ptrArray += array[].Length)
//Code using ptrArray for array[y][x]
Любая помощь приветствуется.
Макс
Ответы
Ответ 1
Один из вариантов - использовать обратный цикл (запустите for() loop
от array.Length
до 0)
Это ускорит работу.
например,
for (int x = array[].Length-1; x >= 0; --x)
int* ptrArray = (int*)array[0];
for (int y = array.Length-1; y >= 0 ; --y, ptrArray += array[].Length)
//Code using ptrArray for array[y][x]
Ответ 2
Самое главное правило заключается в том, что вся эта теория до тех пор, пока вы не профилируете. Я не согласен с теми, кто настаивает на том, что профилирование - это все (без какой-либо теории вы не лучше, чем Cargo Cultist, кладя кокосовые орехи в уши и ожидая прибытия самолета), но ваша теория всегда может быть неправильной или неполной, поэтому профилирование имеет решающее значение.
Как правило, мы хотим, чтобы внутреннее сканирование было горизонтально (с точки зрения массива, а не изображения, хотя для большинства форматов это то же самое). Причина в том, что с массивом вроде:
00 01 02 03 04 05 06 07 08 09
10 11 12 13 14 15 16 17 18 19
20 21 22 23 24 25 26 27 28 29
Он будет выложен как:
00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
Вы хотите сканировать по смежным блокам, которые могут быть загружены в кэши ЦП, а затем использованы целиком, а не сканировать с блока на блок и нуждаться в регулярном изменении содержимого кэша ЦП.
Это еще более важно, если вы попытаетесь параллелизовать алгоритм. Вы хотите, чтобы каждый поток работал с его собственными смежными блоками памяти, насколько это касается как входа, так и выхода, а не не только страдает от того, как однопоточный код работает с плохим чередованием частоты, но также вызывает загрязнение других буферов и необходимо освежить. Это может быть разница между параллелизмом, ведущим к ускорению скорости и параллелизующим, фактически замедляющим работу.
Другое дело - разница между 2-мерным массивом byte[,]
, а не массивом массивов byte[][]
, который ваш комментарий в вашем вопросе "array [y] [x]" заставляет меня задаться вопросом, может быть, вы используете, С первым, чтобы получить arr [1,2], логика:
- Проверить границы
- Вычислить позицию (простая быстрая арифметика)
- Получить значение.
С последним логика такова:
- Проверить границы
- Получить массив через указатель.
- Проверить границы
- Получить значение.
Существует также менее хорошая частота кэширования памяти. Последнее имеет преимущества, когда нужны "зубчатые" структуры, но здесь это не так. 2D почти всегда быстрее массива массивов.
То, что я не вижу, скорее всего, поможет, но я бы, конечно, попробовал их в вашей ситуации:
Вы можете найти повышение от выполнения вашей логики 1d <= > 2d. Имейте одномерный массив, где idx = y * width + x. Это не должно иметь заметной разницы, но стоит попробовать.
Оптимизации стараются как вызвать вызовы на .Length
, так и опустить ненужную проверку границ, поэтому вы можете найти ручную подъемку и переключиться на арифметику указателя, ничего не получится, но в случае, когда вам действительно нужно сбить время это, безусловно, стоит профилировать.
Наконец-то. Профилировали ли вы, как быстро ваш код просматривает массив и ничего не делает? Возможно, что другая часть кода является настоящим узким местом, и вы исправляете неправильную вещь.
Ответ 3
Я понятия не имею, но вы уже придумали примеры. Таким образом, вы можете запускать свои образцы кода в цикле и сами профиль.
var sw = new Stopwatch();
sw.Start();
ExecuteMyCode();
sw.Stop();
Console.WriteLine("Time: " + sw.Elapsed);
Возможно, вы сможете ускорить обработку, используя многопоточную конструкцию как Parallel.ForEach
. Это будет хорошо работать, если код в вашем цикле избегает зависимостей между итерациями цикла.
Ответ 4
Можете ли вы гой небезопасно? Указатель. Проблема с массивом заключается в том, что вы STILL имеют граничные проверки при каждом доступе. Указатели удаляют это. Обратите внимание, что это полностью поддерживается С#, но вам нужно поместить его в небезопасный блок. Это также означает, что вы должны быть ABLE для запуска небезопасного кода, который не всегда является данным.
http://msdn.microsoft.com/en-us/library/28k1s2k6.aspx
имеет образец кода.
Ответ 5
Если это возможно, попробуйте перераспределить ваш массив, чтобы первое измерение было меньше второго. Это резко ускорит ситуацию.
Другим решением является перераспределение данных в одномерном массиве, как было предложено выше.
Ответ 6
Всегда следите за тем, чтобы ваш внутренний контур обращался к непрерывной памяти.
Обычно это строка вашего изображения. Обратите внимание, что в прямоугольных массивах вы должны сделать это последним индексом: array[y,x]
.
этот документ предполагает, что встроенные прямоугольные массивы С# (с несколькими индексами) довольно медленные. Я читал это раньше, но это единственная ссылка, которую я получил. Я бы начал с линейного массива и вычислил смещение один раз для каждой строки. Неуправляемый поможет вам в действительно тривиальных случаях.
Если один кадр занимает 25 секунд, то он либо huuuuge, либо очень сложная обработка. В этом случае интересно только потратить усилия на оптимизацию доступа к памяти, если вы получаете доступ ко многим входным пикселям для каждого выходного пикселя.