Что самый быстрый путь состоит в том, чтобы Инициализировать многомерный массив к значениям не по умолчанию в.NET?

Как я инициализирую многомерный массив типа примитива максимально быстро?

Я застреваю с использованием многомерных массивов. Моей проблемой является производительность. Следующая стандартная программа инициализирует 100x100 массив приблизительно в 500 галочках. Удаление интервала. Инициализация MaxValue приводит приблизительно к 180 галочкам только для цикличного выполнения. Приблизительно 100 галочек для создания массива без цикличного выполнения и не инициализируя к интервалу. MaxValue.

  • Стандартные программы, подобные этому, называют несколькими сотнями тысяч к нескольким миллионам раз во время "выполнения".
  • Размер массива не изменится во время выполнения, и массивы создаются по одному, используются, затем отбрасываются, и новый созданный массив.
  • "Выполнение", которое может продлиться с одной минуты (использующий 10x10 массивы) к сорока пяти минутам (100x100).
  • Приложение создает массивы интервала, bool, и структуру.
  • Может быть несколько "выполнений", выполняющихся в то же время, но - не потому что производительность ухудшается ужасно.
  • Я использую 100x100 в качестве базовой линии.

Я открыт для предложений о том, как оптимизировать эту инициализацию не по умолчанию массива. Одна идея, которую я имел, состоит в том, чтобы использовать меньший тип примитива, когда доступно. Например, с помощью байта вместо интервала, сохраняет 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;
    }

CreateArray5; Принятая Реализация: Ограничение: Не мог Изменить размер, может быть изменен

Вниз приблизительно к 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();
    }

CreateArray8; принятый Implemention; ограничение: требует полного доверия

Вниз приблизительно к 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 устранить массив инициализации к значениям по умолчанию не удалось, потому что выделенная стековая память должна быть скопирована из метода. Значение, я должен был бы создать другой массив для содержания результатов. Этот массив был бы инициализирован, победив целую цель использовать stackalloc.

CreateArray8; небезопасный/фиксированный метод

CLR только выполнится unsafe кодируйте, если это находится в полностью доверяемом блоке.

CreateArray5; метод клона

Требует, чтобы переменные определили, инициализируется ли массив и сохранить инициализированный массив. Производительность совпадает с небезопасным/фиксированным методом после инициализации. См. ответ Dan Tao для возможного решения.

300%-е увеличение производительности?

Я сосу на уровне процентов, но 300% - то, что я изобразил (от 500 до 165 галочек).


Заключительная реализация для приложения

Для этого приложения я обосновался на использовании метода "клона". Следующее является "минимизированной" Универсальной реализацией, используемой в приложении с образцами производительности.

Инициализация:

  • Grid<int>; универсальный класс клона initalize: 4348, 4336, 4339, 4654
  • Grid<bool>; универсальный класс клона initalize: 2692, 2684, 3916, 2680
  • Grid<Color>; универсальный класс клона initalize: 3747, 4630, 2702, 2708

Использование:

  • Grid<int>; универсальный класс клона: 185, 159, 152, 290
  • Grid<bool>; универсальный класс клона: 39, 36, 44, 46
  • Grid<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();
        }
    }
    
5
задан 11 revs, 2 users 100% 6 May 2010 в 00:30
поделиться

7 ответов

Замечание по поводу вашего подхода Clone: Я сомневаюсь, что вы сможете превзойти его в плане производительности. Тем не менее, он может быть изменен, учитывая, что после первой инициализации он игнорирует параметр Size и просто возвращает массив того же размера при каждом вызове. В зависимости от того, имеет ли это значение в вашем сценарии, вы можете либо:

  1. Придерживаться этого, потому что это не имеет значения.
  2. Создать Dictionaryполагаю, что Size будет правильно вести себя в качестве ключа - не проверял) для предварительной инициализации массива каждый раз, когда запрашивается уникальный Size. Накладные расходы на это я не знаю.
  3. Откажитесь от идеи клонирования.

В случае, если в конечном итоге вы остановитесь на 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 из функции, потому что эта память выделяется только на время жизни функции.

4
ответ дан 13 December 2019 в 22:03
поделиться

Неуправляемый массив для гранулярного управления

Создайте массив в неуправляемом ( небезопасном ) режиме C #, как показано здесь [проект кода] , и инициализируйте его вручную.

В управляемом режиме C # массив сначала инициализирует все элементы до значения по умолчанию, прежде чем ваш цикл начнет заполнять его. Вероятно, вы можете отключить удвоение в неуправляемом режиме. Это сэкономит много времени.

3
ответ дан 13 December 2019 в 22:03
поделиться

Вы можете использовать каждую строку параллельно, используя параллельную библиотеку для ее инициализации, это будет быстрее.

Тем не менее, я думаю, что для этого есть ограничение. 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.

0
ответ дан 13 December 2019 в 22:03
поделиться

Мне удалось снизить тики примерно до 165. Смотрите CreateArray8 ниже.

Я взял идею из раздела "Range Check Elimination" статьи CodeProject по адресу http://www.codeproject.com/KB/dotnet/arrays.aspx, предоставленной jdk. (@jdk, большое спасибо.) Идея заключалась в том, чтобы устранить проверку диапазона, используя указатель и инициализируя каждый элемент в одном цикле. Мне удалось снизить количество тиков примерно до 165. Так же хорошо, как клонирование, без задержки предварительной инициализации и поддержки статических переменных. (См. мой другой ответ.)

Спорим, я смогу сократить это вдвое, если только разберусь с возвратом CreateArray9.

  • CreateArray3; static unsafe: 501, 462, 464, 462
  • CreateArray7; static unsafe for(y,x): 452, 451, 444, 429
  • CreateArray8; static unsafe pointer single_for: 187, 173, 156, 145

[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");

    }

}
0
ответ дан 13 December 2019 в 22:03
поделиться

Это не тестировалось в этом сценарии, но работало в аналогичных. Иногда, замена порядок обхода массива ускоряет работу из-за локальности памяти.

Другими словами, вместо выполнения для (x) ...for (y) вместо for (y) ... for (x) .

1
ответ дан 13 December 2019 в 22:03
поделиться

Добавление static и unsafe обеспечивают некоторое сокращение клещей. Ниже приведены некоторые образцы.

  • CreateArray; нестатические небезопасные: 521, 464, 453, 474
  • CreateArray; статические: 430, 423, 418, 454
  • CreateArray; небезопасно: 485, 464, 435, 414
  • CreateArray; static unsafe: 476, 450, 433, 405

Я пытался использовать stackalloc . Моя идея заключалась в том, чтобы выделить массив, который не будет инициализирован, потому что это небезопасный код. Затем я бы застегнул массив, инициализируя его до int.MaxValue по мере продвижения, а затем Клонировать массив для возвращаемого результата. Но я был озадачен многомерным заявлением.

Затем я вспомнил об использовании Clone на массивах в другом проекте. Каждый Array.Clone экономил несколько секунд.Основываясь на этой идее, я создал следующую версию подпрограммы CreateArray , получив отличные результаты.

Теперь все, что мне нужно сделать, это получить stackalloc для работы с многомерными массивами.

  • CreateArray5; предварительная инициализация: 2663, 3036
  • 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");
2
ответ дан 13 December 2019 в 22:03
поделиться

Класс и общий с использованием метода «Клонировать».

  • MDArray; clone class initalize: 2444, 2587, 2421, 2406
  • MDArray; класс клона: 440, 362, 198, 139
  • 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");
    }
}
0
ответ дан 13 December 2019 в 22:03
поделиться
Другие вопросы по тегам:

Похожие вопросы: