Замечание о вашем Clone
подходе: я сомневаюсь, что вы сможете превзойти его с точки зрения производительности. Однако это может быть критическое изменение, учитывая, что после первой инициализации, он игнорирует параметр Size
и просто возвращает массив одинакового размера при каждом вызове . В зависимости от того, имеет ли это значение в вашем сценарии, вы можете:
- Придерживайтесь этого, потому что это не имеет значения.
- Создать
Dictionary<Size, int[,]>
(я считаю, что Size
будет вести себя как ключ - не проверял) для предварительной инициализации массива каждый раз, когда запрашивается уникальный Size
. Накладные расходы в этом я не уверен.
- Откажитесь от идеи
Clone
.
В случае, если вам в конечном итоге придется перейти с 3 выше, вот несколько пограничных нелепых предложений:
1. Кэшируйте свои свойства Width
и Height
локально, а не обращайтесь к ним из структуры Size
на каждой итерации .
static int[,] CreateArray(Size size) {
int w = size.Width;
int h = size.Height;
int[,] array = new int[w, h];
for (int x = 0; x < w; x++) {
for (int y = 0; y < h; y++) {
array[x, y] = int.MaxValue;
}
}
return array;
}
Для создания массива 1000x1000 на моем компьютере среднее время выполнения составило около 120000 тиков против 140000 тиков.
2. Используйте несколько ядер, если они у вас есть, и параллельно инициализируйте массив.
static int[,] CreateArray(Size size) {
int w = size.Width;
int h = size.Height;
int[,] array = new int[w, h];
Action<int[,], int, int> fillFirstHalf = FillArray;
Action<int[,], int, int> fillSecondHalf = FillArray;
var firstResult = fillFirstHalf.BeginInvoke(array, 0, h / 2, null, null);
var secondResult = fillSecondHalf.BeginInvoke(array, h / 2, h, null, null);
fillFirstHalf.EndInvoke(firstResult);
fillSecondHalf.EndInvoke(secondResult);
return array;
}
static void FillArray(int[,] array, int ystart, int yend) {
int w = array.GetLength(0);
for (int x = 0; x < w; ++x) {
for (int y = ystart; y < yend; ++y) {
array[x, y] = int.MaxValue;
}
}
}
Это, вероятно, не очень реалистичное предложение в вашем сценарии, поскольку кажется, что вы создаете только массивы 100x100, и в этом случае накладные расходы на распараллеливание превышают выигрыш в производительности. Однако, для создания массива 1000x1000, я обнаружил, что этот подход уменьшил мое время выполнения в среднем до 70 тыс. Тиков (по сравнению с ~ 120 тыс. Тиков, которые я получил при первой предложенной оптимизации).
Кроме того, если вы создаете много массивов таким образом, я настоятельно рекомендую распараллелить , что (то есть, если вам нужно создать тысячу массивов, создайте 500 каждый из двух потоков ), при условии, что у вас есть несколько процессоров, чтобы сделать работу за вас. Без нескольких процессоров, забудьте об этом; добавление тем только ухудшит вашу производительность.
3. Увеличьте производительность, используя указатель unsafe
.
Теперь вот интересное открытие: кажется, что двумерный массив в .NET выделяется предсказуемым образом *: в основном, как одномерный блок памяти, где каждая «строка» смещение от начальной точки на величину, эквивалентную длине всех предыдущих строк. Другими словами, к массиву 10x2 можно получить доступ, используя указатель, как массив 20x1; к массиву 10x10 можно получить доступ, как к массиву 100x1 и т. д.
Понятия не имею, документировано ли это поведение или нет. Это может быть неопределенная деталь реализации, от которой вы не хотите зависеть. В любом случае, стоит посмотреть.
* Вполне возможно, что большинство других разработчиков .NET уже знали об этом, и я просто констатирую очевидное, и в этом случае я отменяю свой комментарий об этом "интересе".
В любом случае знание этого позволяет использовать ключевое слово fixed
в контексте unsafe
для значительного увеличения производительности:
static int[,] CreateArray(Size size) {
int w = size.Width;
int h = size.Height;
int[,] array = new int[w, h];
unsafe {
fixed (int* ptr = array) {
for (int i = 0; i < w * h; ++i)
ptr[i] = int.MaxValue;
}
}
return array;
}
Для инициализации массивов значительного размера, я бы даже рекомендовал объединить вышеупомянутый подход (распараллеливание) с этим - так, оставьте тот же CreateArray
из предложения # 2, а затем переписать FillArray
как:
static void FillArray(int[,] array, int ystart, int yend) {
int w = array.GetLength(0);
unsafe {
fixed (int* p = array) {
for (int i = w * ystart; i < w * yend; ++i)
p[i] = int.MaxValue;
}
}
}
На самом деле кажется, что вы уже разобрались с этой последней частью до того, как я ее опубликовал, но я решил, что все равно включу ее, главным образом, для того, чтобы объединить unsafe
с распараллеливанием.
Примечание по stackalloc
: Я думаю, что вы можете гоняться за гномом в конце радуги с этим. Согласно документации stackalloc
:
Блок памяти достаточного размера
содержать expr
элементы типа type
размещается в стеке, а не
куча; адрес блока
хранится в указателе ptr
. Эта память
не подлежит сборке мусора и
поэтому не должен быть закреплен(через fixed
). Время жизни
блок памяти ограничен
время жизни метода, в котором это
определено. (выделено мое)
Это заставляет меня поверить, что вы не можете вернуть объект, данные которого хранятся в памяти, выделенной stackalloc
из функции, потому что эта память выделяется только на время жизни функции.