В каких случаях НЕ рекомендуется использовать Eigen::Ref для параметров?

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

В то же время я предполагаю, что должен быть какой-то недостаток, иначе подхода передачи аргументов с использованием шаблона MatrixBase<Derived> не существовало бы. Тем не менее, я не смог найти ни одного сообщения, обсуждающего эту тему.

Поэтому я спрашиваю: каковы практические недостатки использования Refs вместо шаблонных параметров функции?

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

Ответы 1

Ответ принят как подходящий

Обычно используются два типа Ref: изменяемые Ref<Matrix<…>> для выходных или входных параметров и неизменяемые Ref<const Matrix<…>> для входных параметров. На самом деле они ведут себя немного по-другому.

Внутренний шаг

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

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

Учти это:


double sum(const Eigen::Ref<const Eigen::VectorXd>& in)
{ return in.sum(); }

double foo()
{
    Eigen::VectorXcd x = …;
    return sum(x.real());
}

x.real() создает представление комплексной матрицы. Поскольку комплексные значения хранятся в чередующемся формате реальных и мнимых компонентов, это представление имеет внутренний шаг, равный 2. Поэтому конструктор объекта Ref выделит VectorXd внутри и скопирует в него значения.

То же самое происходит здесь:

double foo()
{
    Eigen::MatrixXd x = …;
    return sum(x.row(0));
}

но этого не произойдет для x.col(0), поскольку Eigen по умолчанию использует формат по столбцам.

Если бы вместо этого вы написали ту же функцию с использованием MatrixBase<Derived>, сама сумма была бы специализирована для фиксированного или переменного внутреннего шага выражений x.real() или x.row(). Это предотвратило бы векторизацию (ну, может быть, частичную векторизацию для x.real().sum() все же можно было бы достичь), но также позволило бы избежать копирования.

В большинстве случаев потеря векторизации, вероятно, предпочтительнее создания копии, но это потребует тестирования.

Произвольные выражения

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

double baz()
{
    Eigen::VectorXd x = …;
    return sum(x * 2.);
}

Здесь необходимо создать временный вектор со значением x * 2., поскольку преобразование нельзя передать в функцию sum. Если бы sum принял MatrixBase<Derived>, код вместо этого был бы специализирован для объекта выражения и работал бы так же быстро и эффективно, как (x * 2.).sum().

(Частично) фиксированные размеры

В общем, когда вы используете Ref, ни Eigen, ни компилятор не могут использовать информацию о конкретном типе или выражении, используемом в качестве параметра. Другим примером может быть передача Vector4d в функцию sum. Здесь теряется информация о том, что записей всегда 4, а значит цикл суммирования можно было развернуть.

В некоторых местах Eigen использует специализированные пути кода, если во время компиляции известно, что тип имеет фиксированный размер хотя бы в одном измерении, например, вычисление обратной небольшой матрицы или выполнение умножения матрицы с небольшой матрицей или вектором на одна сторона.

Внешний шаг

Когда вы используете простую матрицу или блок столбцов простой матрицы (например, matrix.middleCols(start, n) в выражении), Эйген обычно может использовать информацию о том, что между одним столбцом нет пробела. Для простых скалярных операций вместо двойного значения цикл for(col = 0; col < cols; ++col) for(row = 0; row < rows; ++row), код будет оптимизирован в один цикл for(i = 0; i < rows * cols; ++i). Это может улучшить векторизацию, особенно для матриц с небольшим количеством строк и большим количеством столбцов.

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

Выравнивание

Незначительная проблема с производительностью заключается в том, что Ref не гарантирует выравнивание начального адреса. Эйген предположит, что содержимое смещено. В основном это проблема, если вы компилируете без расширений AVX, поскольку SSE может складывать загрузку и сохранение памяти в арифметические операции только в том случае, если они гарантированно выровнены. Очень старое оборудование, предназначенное только для SSE, также было очень медленным для невыровненных инструкций загрузки/сохранения памяти, даже если они были выровнены во время выполнения.

Подробности см. в разделе Странное поведение выравнивания и SSE.

Висячие указатели

Что касается корректности, эти временные копии могут вызвать проблемы, если вы решите сохранить объект Ref (или указатель на содержимое Ref) дольше, чем сам вызов функции. Раньше мой код содержал такую ​​функцию:


using ConstMapType = Eigen::Map<const Eigen::MatrixXd, Eigen::OuterStride<>>;

// Never do this!
ConstMapType block_to_map(const Eigen::Ref<const Eigen::MatrixXd>& block)
{
    return ConstMapType(block.data(), block.rows(), block.cols(),
                        Eigen::OuterStride<>(block.outerStride()));
}

Если вы забудете, как это работает, и случайно вызовете это с каким-нибудь выражением, вызывающим временную копию, указатель data() будет висеть в конце вызова функции.

Краткое содержание

К сожалению, вам придется оценивать влияние на производительность в каждом конкретном случае. Скорее всего, вы передадите простую матрицу/вектор или подблок? Будут ли они иметь динамический размер или можно будет включить фиксированный размер в Ref, например. Ref<const Matrix4Xd>? Тогда накладные расходы, вероятно, будут незначительными.

Спасибо за подробное объяснение. У меня вопрос: есть ли способ гарантировать, что const Ref не будет выполнять копии, а вместо этого не будет компилироваться, как и его неконстантный аналог (если, например, шаги не совпадают)?

Svalorzen 10.04.2024 22:42

@Svalorzen Я не думаю, что это поддерживается. По крайней мере, я не вижу в коде ничего (например, макроса препроцессора), предназначенного для отключения копирования. Однако изменяемый класс Ref не очень большой и его не очень сложно читать. Думаю, можно было бы скопировать его и создать свой собственный шрифт. gitlab.com/libeigen/eigen/-/blob/master/Eigen/src/Core/…

Homer512 10.04.2024 22:59

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