Из того, что я читал об Eigen (здесь), кажется, что operator=()
действует как "барьер" сортов для ленивой оценки - например. это приводит к тому, что Eigen перестает возвращать шаблоны выражений и фактически выполняет (оптимизированное) вычисление, сохраняя результат в левую сторону =
.
Это, по-видимому, означает, что один "стиль кодирования" влияет на производительность, то есть использование именованных переменных для хранения результатов промежуточных вычислений может отрицательно повлиять на производительность, заставив некоторые части вычисления вычисляться "слишком рано".
Чтобы проверить мою интуицию, я написал пример и был удивлен результатами (полный код здесь):
using ArrayXf = Eigen::Array <float, Eigen::Dynamic, Eigen::Dynamic>;
using ArrayXcf = Eigen::Array <std::complex<float>, Eigen::Dynamic, Eigen::Dynamic>;
float test1( const MatrixXcf & mat )
{
ArrayXcf arr = mat.array();
ArrayXcf conj = arr.conjugate();
ArrayXcf magc = arr * conj;
ArrayXf mag = magc.real();
return mag.sum();
}
float test2( const MatrixXcf & mat )
{
return ( mat.array() * mat.array().conjugate() ).real().sum();
}
float test3( const MatrixXcf & mat )
{
ArrayXcf magc = ( mat.array() * mat.array().conjugate() );
ArrayXf mag = magc.real();
return mag.sum();
}
Приведенное выше дает 3 различных способа вычисления коэффи- циентной величины величин в комплекснозначной матрице.
-
test1
отбирает каждую часть вычисления "один шаг за раз". -
test2
выполняет все вычисления в одном выражении. -
test3
принимает "смешанный" подход - с некоторым количеством промежуточных переменных.
Я ожидал, что, поскольку test2
упаковывает все вычисления в одно выражение, Eigen сможет воспользоваться этим и глобально оптимизировать весь расчет, обеспечивая максимальную производительность.
Однако результаты были неожиданными (номера показаны в общей сумме микросекунд в 1000 исполнений каждого теста):
test1_us: 154994
test2_us: 365231
test3_us: 36613
(Это было скомпилировано с g++ -O3 - см. gist для получения полной информации.)
Версия, которую я ожидал быть самой быстрой (test2
), была самой медленной. Кроме того, версия, которую я ожидал быть самой медленной (test1
), была фактически посередине.
Итак, мои вопросы:
- Почему
test3
работает намного лучше, чем альтернативы? - Есть ли метод, который можно использовать (за исключением погружения в код сборки), чтобы получить представление о том, как Eigen фактически выполняет ваши вычисления?
- Существует ли набор руководящих принципов, чтобы найти хороший компромисс между производительностью и читабельностью (использование промежуточных переменных) в коде Eigen?
В более сложных вычислениях выполнение всего в одном выражении может затруднить читаемость, поэтому я заинтересован в поиске правильного способа написания кода, который является читабельным и эффективным.