Как я инициализирую многомерный массив типа примитива максимально быстро?
Я застреваю с использованием многомерных массивов. Моей проблемой является производительность. Следующая стандартная программа инициализирует 100x100 массив приблизительно в 500 галочках. Удаление интервала. Инициализация MaxValue приводит приблизительно к 180 галочкам только для цикличного выполнения. Приблизительно 100 галочек для создания массива без цикличного выполнения и не инициализируя к интервалу. MaxValue.
Я открыт для предложений о том, как оптимизировать эту инициализацию не по умолчанию массива. Одна идея, которую я имел, состоит в том, чтобы использовать меньший тип примитива, когда доступно. Например, с помощью байта вместо интервала, сохраняет 100 галочек. Я был бы доволен этим, но я надеюсь, что не должен изменять примитивный тип данных.
public int[,] CreateArray(Size size) {
int[,] array = new int[size.Width, size.Height];
for (int x = 0; x < size.Width; x++) {
for (int y = 0; y < size.Height; y++) {
array[x, y] = int.MaxValue;
}
}
return array;
}
Вниз к 450 галочкам со следующим:
public int[,] CreateArray1(Size size) {
int iX = size.Width;
int iY = size.Height;
int[,] array = new int[iX, iY];
for (int x = 0; x < iX; x++) {
for (int y = 0; y < iY; y++) {
array[x, y] = int.MaxValue;
}
}
return array;
}
Вниз приблизительно к 165 галочкам после одноразовой инициализации 2 800 галочек. (См. мой ответ ниже.), Если я могу добраться stackalloc
для работы с многомерными массивами я должен смочь получить ту же производительность, не имея необходимость инициализировать private static
массив.
private static bool _arrayInitialized5;
private static int[,] _array5;
public static int[,] CreateArray5(Size size) {
if (!_arrayInitialized5) {
int iX = size.Width;
int iY = size.Height;
_array5 = new int[iX, iY];
for (int x = 0; x < iX; x++) {
for (int y = 0; y < iY; y++) {
_array5[x, y] = int.MaxValue;
}
}
_arrayInitialized5 = true;
}
return (int[,])_array5.Clone();
}
Вниз приблизительно к 165 галочкам, не используя "метод клона" выше. (См. мой ответ ниже.) Я уверен, что могу получить галочки ниже, если я могу просто выяснить возврат CreateArray9
.
public unsafe static int[,] CreateArray8(Size size) {
int iX = size.Width;
int iY = size.Height;
int[,] array = new int[iX, iY];
fixed (int* pfixed = array) {
int count = array.Length;
for (int* p = pfixed; count-- > 0; p++)
*p = int.MaxValue;
}
return array;
}
Я предоставляю весь код и примечания относительно этого вопроса как ответы. Хотелось бы надеяться, это сэкономит чье-то время в будущем.
Массивы, выделенные на "Куче" для больших объектов (LOH), не являются частью этого обсуждения. Отмеченные повышения производительности слышат, только для массивов, выделенных на "куче".
Моя идея использовать stackalloc
устранить массив инициализации к значениям по умолчанию не удалось, потому что выделенная стековая память должна быть скопирована из метода. Значение, я должен был бы создать другой массив для содержания результатов. Этот массив был бы инициализирован, победив целую цель использовать stackalloc
.
CLR только выполнится unsafe
кодируйте, если это находится в полностью доверяемом блоке.
Требует, чтобы переменные определили, инициализируется ли массив и сохранить инициализированный массив. Производительность совпадает с небезопасным/фиксированным методом после инициализации. См. ответ Dan Tao для возможного решения.
Я сосу на уровне процентов, но 300% - то, что я изобразил (от 500 до 165 галочек).
Для этого приложения я обосновался на использовании метода "клона". Следующее является "минимизированной" Универсальной реализацией, используемой в приложении с образцами производительности.
Инициализация:
Grid<int>
; универсальный класс клона initalize: 4348, 4336, 4339, 4654Grid<bool>
; универсальный класс клона initalize: 2692, 2684, 3916, 2680Grid<Color>
; универсальный класс клона initalize: 3747, 4630, 2702, 2708Использование:
Grid<int>
; универсальный класс клона: 185, 159, 152, 290Grid<bool>
; универсальный класс клона: 39, 36, 44, 46Grid<Color>
; универсальный класс клона: 2229, 2431, 2460, 2496
public class Grid<T> {
private T[,] _array;
private T _value;
private bool _initialized;
private int _x;
private int _y;
public Grid(Size size, T value, bool initialize) {
_x = size.Width;
_y = size.Height;
_value = value;
if (initialize) {
InitializeArray();
}
}
private void InitializeArray() {
int iX = _x;
int iY = _y;
_array = new T[iX, iY];
for (int y = 0; y < iY; y++) {
for (int x = 0; x < iX; x++) {
_array[x, y] = _value;
}
}
_initialized = true;
}
public T[,] CreateArray() {
if (!_initialized) {
InitializeArray();
}
return (T[,])_array.Clone();
}
}
Замечание по поводу вашего подхода Clone
: Я сомневаюсь, что вы сможете превзойти его в плане производительности. Тем не менее, он может быть изменен, учитывая, что после первой инициализации он игнорирует параметр Size
и просто возвращает массив того же размера при каждом вызове. В зависимости от того, имеет ли это значение в вашем сценарии, вы можете либо:
Dictionary
(я полагаю, что Size
будет правильно вести себя в качестве ключа - не проверял) для предварительной инициализации массива каждый раз, когда запрашивается уникальный Size
. Накладные расходы на это я не знаю. клонирования
. В случае, если в конечном итоге вы остановитесь на 3 варианте, вот несколько граничащих с нелепостью предложений:
1. Кэшировать свойства Width
и Height
локально, а не обращаться к ним из Size
struct на каждой итерации.
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. Повышение производительности за счет использования небезопасного
указателя.
Теперь вот интересное открытие: оказывается, двумерный массив в .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
из функции, потому что эта память выделяется только на время жизни функции.
Создайте массив в неуправляемом ( небезопасном ) режиме C #, как показано здесь [проект кода] , и инициализируйте его вручную.
В управляемом режиме C # массив сначала инициализирует все элементы до значения по умолчанию, прежде чем ваш цикл начнет заполнять его. Вероятно, вы можете отключить удвоение в неуправляемом режиме. Это сэкономит много времени.
Вы можете использовать каждую строку параллельно, используя параллельную библиотеку для ее инициализации, это будет быстрее.
Тем не менее, я думаю, что для этого есть ограничение. For, только 64 операции могут быть поставлены в очередь, но в этом случае вы можете поставить в очередь от 0 до 63, от 64 до 127 и т. Д. В Parallel.For ..
public int[,] CreateArray(Size size) {
int[,] array = new int[size.Width, size.Height];
System.Threading.Paralle.For (0,size.Width,
x=>{
for (int y = 0; y < size.Height; y++) {
array[x, y] = int.MaxValue;
}
}
);
return array;
}
Это включено в .NEt 4, однако для .NET 3.51 вы можете получить ту же библиотеку из codeplex.
Мне удалось снизить тики примерно до 165. Смотрите CreateArray8
ниже.
Я взял идею из раздела "Range Check Elimination" статьи CodeProject по адресу http://www.codeproject.com/KB/dotnet/arrays.aspx, предоставленной jdk. (@jdk, большое спасибо.) Идея заключалась в том, чтобы устранить проверку диапазона, используя указатель и инициализируя каждый элемент в одном цикле. Мне удалось снизить количество тиков примерно до 165. Так же хорошо, как клонирование, без задержки предварительной инициализации и поддержки статических переменных. (См. мой другой ответ.)
Спорим, я смогу сократить это вдвое, если только разберусь с возвратом CreateArray9
.
[TestClass]
public class CreateArrayTest {
public static unsafe int[,] CreateArray3(Size size) {
int iX = size.Width;
int iY = size.Height;
int[,] array = new int[iX, iY];
for (int x = 0; x < iX; x++) {
for (int y = 0; y < iY; y++) {
array[x, y] = int.MaxValue;
}
}
return array;
}
public unsafe static int[,] CreateArray7(Size size) {
int iX = size.Width;
int iY = size.Height;
int[,] array = new int[iX, iY];
for (int y = 0; y < iY; y++) {
for (int x = 0; x < iX; x++) {
array[x, y] = int.MaxValue;
}
}
return array;
}
public unsafe static int[,] CreateArray8(Size size) {
int iX = size.Width;
int iY = size.Height;
int[,] array = new int[iX, iY];
fixed (int* pfixed = array) {
int count = array.Length;
for (int* p = pfixed; count-- > 0; p++)
*p = int.MaxValue;
}
return array;
}
public unsafe static int[,] CreateArray9(Size size) {
int iX = size.Width;
int iY = size.Height;
void* array = stackalloc int[iX * iY];
int count = iX * iY;
for (int* p = (int*)array; count-- > 0; p++)
*p = int.MaxValue;
//return (int[,])array; //how to return?
return new int[1, 1];
}
[TestMethod()]
public void CreateArray_Test() {
int[,] actual;
int iHi = 10000 * 10 * 2;
//'absolute minimum times array will be created (200,000)
//'could be as high as 10000 * 10 * 20? * 50? (100,000,000?)
Stopwatch o;
o = Stopwatch.StartNew();
for (int i = 0; i < iHi; i++) { actual = CreateArray3(new Size(100, 100)); }
o.Stop();
Trace.WriteLine(o.ElapsedTicks / iHi, "CreateArray3; static unsafe");
o = Stopwatch.StartNew();
for (int i = 0; i < iHi; i++) { actual = CreateArray7(new Size(100, 100)); }
o.Stop();
Trace.WriteLine(o.ElapsedTicks / iHi, "CreateArray7; static unsafe for(y,x)");
o = Stopwatch.StartNew();
for (int i = 0; i < iHi; i++) { actual = CreateArray8(new Size(100, 100)); }
o.Stop();
Trace.WriteLine(o.ElapsedTicks / iHi, "CreateArray8; static unsafe pointer single_for");
}
}
Это не тестировалось в этом сценарии, но работало в аналогичных. Иногда, замена порядок обхода массива ускоряет работу из-за локальности памяти.
Другими словами, вместо выполнения для (x) ...for (y)
вместо for (y) ... for (x)
.
Добавление static
и unsafe
обеспечивают некоторое сокращение клещей. Ниже приведены некоторые образцы.
Я пытался использовать stackalloc
. Моя идея заключалась в том, чтобы выделить массив, который не будет инициализирован, потому что это небезопасный
код. Затем я бы застегнул массив, инициализируя его до int.MaxValue
по мере продвижения, а затем Клонировать
массив для возвращаемого результата. Но я был озадачен многомерным заявлением.
Затем я вспомнил об использовании Clone
на массивах в другом проекте. Каждый Array.Clone
экономил несколько секунд.Основываясь на этой идее, я создал следующую версию подпрограммы CreateArray
, получив отличные результаты.
Теперь все, что мне нужно сделать, это получить stackalloc
для работы с многомерными массивами.
CreateArray5; clone: 157, 172
private static bool _arrayInitialized5;
private static int [,] _array5;
public static int [,] CreateArray5 (размер размер) {
if (! _ArrayInitialized5) {
int iX = size.Width;
int iY = size.Height;
_array5 = new int [iX, iY] ;
для (int x = 0; x
int[,] actual;
int iHi = 10000 * 10 * 2;
//'absolute minimum times array will be created (200,000)
//'could be as high as 10000 * 10 * 20? * 50? (100,000,000?)
Stopwatch o;
//'pre-initialize
o = Stopwatch.StartNew();
actual = CreateArray5(new Size(100, 100));
o.Stop();
Trace.WriteLine(o.ElapsedTicks, "CreateArray5; pre-initialize");
o = Stopwatch.StartNew();
for (int i = 0; i < iHi; i++) { actual = CreateArray5(new Size(100, 100)); }
o.Stop();
Trace.WriteLine(o.ElapsedTicks / iHi, "CreateArray5; static unsafe clone");
Класс и общий с использованием метода «Клонировать».
Grid
; общий класс клона инициализируется: 5344, 5334, 5693, 5272 Grid
; общий класс клона: 187, 204, 199, 288 Grid
; общий класс клона инициализировать: 3585, 3537, 3552, 3569 Grid
; общий класс клона: 37, 44, 36, 43 Grid
; общий класс клона инициализируется: 4139, 3536, 3503, 3533 Grid
; общий класс клонов: 2737, 3137, 2414, 2171 [TestClass]
public class CreateArrayTest {
public class MDArray {
private bool _initialized;
private int[,] _array;
private int _x;
private int _y;
private int _value;
public MDArray(Size size, int value, bool initialize) {
_x = size.Width;
_y = size.Height;
_value = value;
if (initialize) {
InitializeArray();
}
}
private void InitializeArray() {
int iX = _x;
int iY = _y;
_array = new int[iX, iY];
for (int y = 0; y < iY; y++) {
for (int x = 0; x < iX; x++) {
_array[x, y] = _value;
}
}
_initialized = true;
}
public int[,] CreateArray() {
if (!_initialized) {
InitializeArray();
}
return (int[,])_array.Clone();
}
}
public class Grid<T> {
private T[,] _array;
private T _value;
private bool _initialized;
private int _x;
private int _y;
public Grid(Size size, T value, bool initialize) {
_x = size.Width;
_y = size.Height;
_value = value;
if (initialize) {
InitializeArray();
}
}
private void InitializeArray() {
int iX = _x;
int iY = _y;
_array = new T[iX, iY];
for (int y = 0; y < iY; y++) {
for (int x = 0; x < iX; x++) {
_array[x, y] = _value;
}
}
_initialized = true;
}
public T[,] CreateArray() {
if (!_initialized) {
InitializeArray();
}
return (T[,])_array.Clone();
}
}
[TestMethod()]
public void CreateArray_Test() {
int[,] actual;
int iHi = 10000 * 10 * 2; //'absolute minimum times array will be created (200,000)
// 'could be as high as 10000 * 10 * 20? * 50? (100,000,000?)
Stopwatch o;
o = Stopwatch.StartNew();
MDArray oMDArray = new MDArray(new Size(100, 100), int.MaxValue, true);
o.Stop();
Trace.WriteLine(o.ElapsedTicks, " MDArray; clone class initalize");
o = Stopwatch.StartNew();
for (int i = 0; i < iHi; i++) { actual = oMDArray.CreateArray(); }
o.Stop();
Trace.WriteLine(o.ElapsedTicks / iHi, " MDArray; clone class");
o = Stopwatch.StartNew();
Grid<int> oIntMap = new Grid<int>(new Size(100, 100), int.MaxValue, true);
o.Stop();
Trace.WriteLine(o.ElapsedTicks, " Grid<int>; generic clone class initalize");
o = Stopwatch.StartNew();
for (int i = 0; i < iHi; i++) { actual = oIntMap.CreateArray(); }
o.Stop();
Trace.WriteLine(o.ElapsedTicks / iHi, " Grid<int>; generic clone class");
bool[,] fActual;
o = Stopwatch.StartNew();
Grid<bool> oBolMap = new Grid<bool>(new Size(100, 100), true, true);
o.Stop();
Trace.WriteLine(o.ElapsedTicks, " Grid<bool>; generic clone class initalize");
o = Stopwatch.StartNew();
for (int i = 0; i < iHi; i++) { fActual = oBolMap.CreateArray(); }
o.Stop();
Trace.WriteLine(o.ElapsedTicks / iHi, " Grid<bool>; generic clone class");
Color[,] oActual;
o = Stopwatch.StartNew();
Grid<Color> oColMap = new Grid<Color>(new Size(100, 100), Color.AliceBlue, true);
o.Stop();
Trace.WriteLine(o.ElapsedTicks, " Grid<Color>; generic clone class initalize");
o = Stopwatch.StartNew();
for (int i = 0; i < iHi; i++) { oActual = oColMap.CreateArray(); }
o.Stop();
Trace.WriteLine(o.ElapsedTicks / iHi, " Grid<Color>; generic clone class");
}
}