Оптимизация двумерных массивов C++

Мне нужен способ представить двумерный массив (плотную матрицу) двойников в C++ с минимальными накладными расходами на доступ.

Я проверил время на различных машинах linux / unix и версиях gcc. Вектор векторов в формате STL, объявленный как:

vector<vector<double> > matrix(n,vector<double>(n));

и доступ через matrix[i][j] на 5-100% медленнее, чем массив, объявленный как:

double *matrix = new double[n*n];

доступ к нему осуществляется через встроенную индексную функцию matrix[index(i,j)], где index(i,j) оценивается как i + n * j. Другие способы организации двумерного массива без STL - массив из n указателей на начало каждой строки или определение всего объекта в стеке как постоянного размера matrix[n][n] - работают почти с той же скоростью, что и метод индексной функции .

Последние версии GCC (> 4.0), похоже, могут компилировать вектор-вектор STL с почти такой же эффективностью, что и код не-STL, когда включена оптимизация, но это в некоторой степени зависит от машины.

Я хотел бы использовать STL, если это возможно, но мне придется выбрать самое быстрое решение. Есть ли у кого-нибудь опыт оптимизации STL с помощью GCC?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
7
0
2 506
10
Перейти к ответу Данный вопрос помечен как решенный

Ответы 10

Я предполагаю, что самым быстрым для матрицы будет использование одномерного массива STL и переопределение оператора (), чтобы использовать его как двухмерную матрицу.

Однако STL также определяет тип специально для числовых массивов без изменения размера: valarray. У вас также есть различные оптимизации для операций на месте.

valarray принимает в качестве аргумента числовой тип:

valarray<double> a;

Затем вы можете использовать срезы, косвенные массивы ... и, конечно же, вы можете унаследовать valarray и определить свой собственный operator () (int i, int j) для 2D-массивов ...

Мое голосование за valarray, не обязательно для создания пользовательского типа матрицы. Ну, настраиваемый тип матрицы может работать, но все равно должен основываться на valarray вместо вектора (valarray поддерживает нарезку, что делает получение столбца таким же простым, как получение строки).

Chris Jester-Young 30.09.2008 16:12

Осторожное наследование от std :: valarray; он не предназначен для наследования, как большая часть «STL».

Patrick Johnmeyer 30.09.2008 17:15

Вы можете наследовать любой класс STL до тех пор, пока вы не добавляете в них данные, поскольку конструктор не будет вызываться. Однако нет никаких методов добавления pb.

PierreBdR 30.09.2008 17:33

Вы можете наследовать классы «не предназначенные для наследования», если вы используете наследование частный, если я правильно понимаю. например, матрица классов: private std :: valarray <double> или подобное. По сути, вы не полагаетесь ни на какое виртуальное поведение.

Chris Jester-Young 01.10.2008 00:50

Однако публичное или защищенное наследование классов, не предназначенных для наследования, недопустимо.

Chris Jester-Young 01.10.2008 00:51

Я не согласен с последним утверждением. Проблема с классами, не предназначенными для наследования, заключается в том, что деструктор не является виртуальным => производный деструктор может не вызываться. Таким образом, никакая переменная, выделенная в производном классе, не будет освобождена => вы не должны добавлять никаких переменных.

PierreBdR 02.10.2008 01:33

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

Iraimbilanja 19.01.2009 21:53

Однако, если вы наследуете поведение, а не указание / апкастинг, это не будет проблемой. Однако для таких целей композиция работает лучше (за исключением необходимости повторно декларировать весь интерфейс класса).

Luis Machuca 14.03.2012 22:09

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

PierreBdR 22.03.2012 19:46

Я бы рекомендовал использовать Boost.UBLAS, который предоставляет быстрые классы матриц / векторов.

Я должен был пояснить, что пока я имею дело с матрицами, выполняемые мной операции не являются типичной линейной алгеброй. UBLAS выглядит очень хорошо для линейной алгебры, но, возможно, излишний, если я использую его только как хранилище 2D-массивов.

Chris Johnson 30.09.2008 16:22

Я пробовал различные библиотеки линейной алгебры для использования в качестве 2-мерных данных (карт), но они неудобны для использования в целях нелинейной алгебры и не быстрее, чем вектор векторов. UBLAS (и другие) быстр только для умножения и других «типичных» матриц, а не для доступа.

Roel 30.09.2008 16:33

Скорее всего, это проблема местонахождения справки. vector использует new для выделения своего внутреннего массива, поэтому каждая строка будет хотя бы немного обособлена в памяти из-за заголовка каждого блока; они могут быть на большом расстоянии друг от друга, если память уже фрагментирована, когда вы их распределяете. Различные строки массива, вероятно, по крайней мере вызовут сбой строки кэша и могут вызвать сбой страницы; если вам действительно не повезло, две соседние строки могут быть на линиях памяти, которые разделяют слот TLB, и доступ к одной приведет к вытеснению другой.

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

vector предназначен для массивов изменяемый размер. Если вам не нужно изменять размер массивов, используйте обычный массив C++. Операции STL обычно могут работать с массивами C++.

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

Я не думал о заголовках блоков в векторном решении. Я знал о потенциальном замедлении ходьбы из-за неправильной ходьбы: мои тесты скорости показывают, что неправильная ходьба может быть в четыре раза медленнее, чем правильная!

Chris Johnson 30.09.2008 16:28
Ответ принят как подходящий

Если вы используете GCC, компилятор может анализировать доступ к вашей матрице и в некоторых случаях изменять порядок в памяти. Флаг компилятора magic определяется как:

-fipa-matrix-reorg

Perform matrix flattening and transposing. Matrix flattening tries to replace a m-dimensional matrix with its equivalent n-dimensional matrix, where n < m. This reduces the level of indirection needed for accessing the elements of the matrix. The second optimization is matrix transposing that attemps to change the order of the matrix's dimensions in order to improve cache locality. Both optimizations need fwhole-program flag. Transposing is enabled only if profiling information is avaliable.

Обратите внимание, что эта опция не включена -O2 или -O3. Вы должны пройти это сами.

Было бы действительно удивительно и страшно.

peterchen 12.08.2010 14:32

Честно говоря, это зависит от алгоритмов, которые вы используете для матрицы.

Формат двойного имени [n * m] очень быстр, когда вы обращаетесь к данным по строкам, потому что почти не имеет накладных расходов, кроме умножения и сложения, и потому, что ваши строки представляют собой упакованные данные, которые будут согласованы в кеше.

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

Попробуйте провести небольшое исследование, направленное на тип использования и алгоритмы, которые вы используете. Это особенно важно, если матрица очень большая, поскольку промахи в кеше могут повредить вашей производительности больше, чем необходимость 1 или 2 дополнительных математических операций для доступа к каждому адресу.

Вы могли бы так же легко сделать vector <double> (n * m);

В Boost есть реализация uBLAS. Стоит посмотреть.

http://www.boost.org/doc/libs/1_36_0/libs/numeric/ublas/doc/matrix.htm

Вы можете посмотреть библиотеку шаблонов Eigen C++ по адресу http://eigen.tuxfamily.org/. Он генерирует код AltiVec или sse2 для оптимизации векторных / матричных вычислений.

Другая связанная библиотека - Blitz ++: http://www.oonumerics.org/blitz/docs/blitz.html

Blitz ++ разработан для оптимизации работы с массивами.

Некоторое время назад я сделал это для необработанных изображений, объявив свои собственные классы двумерных массивов.

В обычном 2D-массиве вы получаете доступ к таким элементам, как:

массив [2] [3]. Теперь, чтобы получить этот эффект, у вас будет массив классов с перегруженным [] метод доступа к массиву. Но это, по сути, вернет другой массив, тем самым давая вы второе измерение.

Проблема с этим подходом состоит в том, что у него накладные расходы на двойной вызов функции.

Я использовал перегрузку стиля ().

Так что вместо array [2] [3], изменить я сделал это в стиле array (2,3).

Эта функция () была очень маленькой, и я убедился, что она встроена.

См. Эту ссылку для получения общей концепции этого: http://www.learncpp.com/cpp-tutorial/99-overloading-the-parenthesis-operator/

При необходимости вы можете создать шаблон типа. Разница была в том, что мой массив был динамическим. Я объявил, что у меня был блок символьной памяти. И я использовал кеш столбцов, поэтому я знал, где в моей последовательности байтов начинается следующая строка. Доступ был оптимизирован для доступа к соседним значениям, потому что я использовал его для обработки изображений.

Трудно объяснить без кода, но, по сути, результат был таким же быстрым, как и C, и его было намного проще понять и использовать.

Другие вопросы по теме