Spec-Zone.ru › Eigen3

Ленивое вычисление и алиасинг

Резюме: Eigen имеет интеллектуальные механизмы компиляции для поддержки ленивого вычисления и удаления временных переменных при необходимости. В большинстве случаев он автоматически обрабатывает алиасинг, например, с матричными произведениями. Автоматическое поведение можно переопределить вручную, используя методы MatrixBase::eval() и MatrixBase::noalias().

Когда вы пишете строку кода, включающую сложное выражение, например

mat1 = mat2 + mat3 * (mat4 + mat5);

Eigen автоматически определяет для каждого подвыражения, следует ли вычислять его во временную переменную. Действительно, в некоторых случаях вычисление подвыражения во временную переменную предпочтительнее, в то время как в других случаях это можно избежать.

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

vec1 = vec2 + vec3;

традиционная библиотека вычислила бы vec2 + vec3 во временную переменную vec4, а затем скопировала бы vec4 в vec1. Это, конечно, неэффективно: массивы проходятся дважды, поэтому есть множество бесполезных операций загрузки/хранения.

Библиотеки на основе шаблонов выражений могут избегать вычисления подвыражений во временные переменные, что во многих случаях приводит к значительному увеличению скорости. Это называется ленивым вычислением, так как выражение вычисляется как можно позже. В Eigen все выражения вычисляются лениво. Точнее, выражение начинает вычисляться, как только оно присваивается матрице. До этого момента ничего не происходит, кроме построения абстрактного дерева выражений. Однако, в отличие от большинства других библиотек на основе шаблонов выражений, Eigen может выбрать вычисление некоторых подвыражений во временные переменные. Причин две: во-первых, чистое ленивое вычисление не всегда является оптимальным с точки зрения производительности; во-вторых, чистое ленивое вычисление может быть очень опасным, например, с матричными произведениями: выполнение mat = mat*mat даст неправильный результат, если матричное произведение вычисляется непосредственно в целевой матрице, из-за способа работы матричного произведения.

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

Итак, в базовом примере

mat1 = mat2 + mat3;

Eigen выбирает не вводить никаких временных переменных. Таким образом, массивы проходятся только один раз, генерируя оптимизированный код. Если вы действительно хотите принудительно выполнить немедленное вычисление, используйте eval():

mat1 = (mat2 + mat3).eval();

Вот теперь более сложный пример:

mat1 = -mat2 + mat3 + 5 * mat4;

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

Какие подвыражения вычисляются во временные переменные?

По умолчанию стратегия вычисления заключается в объединении операций в едином цикле, и Eigen будет выбирать её за исключением нескольких случаев.

Первый случай, в котором Eigen выбирает вычисление подвыражения, заключается в том, что он видит присваивание a = b; и выражение b имеет флаг вычисления перед присваиванием флаг. Наиболее важным примером такого выражения является выражение матричного произведения. Например, когда вы делаете

mat = mat * mat;

Eigen вычислит mat * mat в временную матрицу, а затем скопирует её в исходную mat. Это гарантирует правильный результат, так как мы видели выше, что ленивое вычисление дает неправильные результаты с матричными произведениями. Также это не очень дорого, так как стоимость самого матричного произведения значительно выше. Обратите внимание, что эта временная переменная вводится только во время вычисления, то есть внутри operator= в данном примере. Выражение mat * mat по-прежнему возвращает абстрактный тип произведения.

Что если вы знаете, что результат не связан с операндом произведения и хотите принудительно использовать ленивое вычисление? Тогда используйте .noalias() вместо этого. Вот пример:

mat1.noalias() = mat2 * mat2;

Здесь, поскольку мы знаем, что mat2 не совпадает с матрицей mat1, мы знаем, что ленивое вычисление не опасно, поэтому мы можем принудительно использовать ленивое вычисление. Конкретно, эффект noalias() здесь заключается в обходе флага вычисления перед присваиванием флаг.

Второй случай, в котором Eigen выбирает вычисление подвыражения, заключается в том, что он видит вложенное выражение, такое как a + b , где b уже является выражением, имеющим флаг вычисления перед вложением флаг. Опять же, наиболее важным примером такого выражения является выражение матричного произведения. Например, когда вы делаете

mat1 = mat2 * mat3 + mat4 * mat5;

произведения mat2 * mat3 и mat4 * mat5 вычисляются отдельно во временные матрицы, прежде чем быть суммированы в mat1. Действительно, для эффективности матричные произведения должны вычисляться в целевой матрице, а не как простые "скалярные произведения". Однако для небольших матриц вы можете захотеть принудительно использовать ленивое вычисление "скалярного произведения" с помощью lazyProduct(). Опять же, важно понять, что эти временные переменные создаются только во время вычисления, то есть в operator=.

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

mat1 = mat2 * (mat3 + mat4);

Здесь, при условии, что матрицы имеют по крайней мере 2 строки и 2 столбца, каждый коэффициент выражения mat3 + mat4 будет использоваться несколько раз в матричном произведении. Вместо того, чтобы каждый раз вычислять сумму, гораздо лучше вычислить ее один раз и сохранить во временной переменной. Eigen понимает это и вычисляет mat3 + mat4 во временную переменную перед вычислением произведения.

© Eigen.
Licensed under the MPL2 License.
https://eigen.tuxfamily.org/dox/TopicLazyEvaluation.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API