Из того, что я читал о Eigen (here), кажется, что operator=()
действует как «барьер» сортов для ленивой оценки - например, это приводит к тому, что Eigen перестает возвращать шаблоны выражений и фактически выполняет (оптимизированное) вычисление, сохраняя результат в левой части =
.Eigen: Влияние стиля кодирования на производительность
Это, по-видимому, означает, что «стиль кодирования» влияет на производительность, то есть использование именованных переменных для хранения результатов промежуточных вычислений может отрицательно повлиять на производительность, заставив некоторые части вычисления вычисляться "слишком рано".
Чтобы попытаться проверить свою интуицию, я написал пример и был удивлен результатами (full code here):
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
пакеты всей вычислений в одном выражении, Эйген сможет воспользоваться этим и глобально оптимизировать весь расчет, обеспечивая лучшую производительность.
Однако результаты оказались неожиданными (номера указаны в общих микросекунд по 1000 выполнений каждого теста): (. Это был составлен с г ++ -O3 - см the gist полную информацию)
test1_us: 154994
test2_us: 365231
test3_us: 36613
Версия, которую я ожидал быть самой быстрой (test2
), была фактически самой медленной. Кроме того, версия, которую я ожидал быть самой медленной (test1
), была фактически посередине.
Итак, мои вопросы:
- Почему
test3
выполнять намного лучше, чем альтернативные варианты? - Есть ли способ, которым можно использовать (не считая погружения в код сборки), чтобы получить представление о том, как Eigen фактически выполняет ваши вычисления?
- Есть ли набор руководящих принципов, которые следует соблюдать, чтобы найти хороший компромисс между производительностью и читабельностью (использование промежуточных переменных) в вашем Eigen-коде?
В более сложных вычислениях выполнение всего в одном выражении может затруднить читаемость, поэтому я заинтересован в поиске правильного способа написания кода, который является читабельным и совершенным.
Я не эксперт по оптимизации, но я бы с подозрением относился к вашим результатам, учитывая, что вы компилировали с '-O3' и не фиксировали ни один из результатов вычислений. Вполне возможно, что оптимизатор распознает отсутствие побочных эффектов 'funcN()' и оптимизирует весь расчет. Я считаю, что вы можете использовать 'volatile' для поддержки микро-бенчмаркинга. [релевантный вопрос SO] (http://stackoverflow.com/questions/6130100/using-volatile-to-prevent-compiler-optimization-in-benchmarking-code) –
Обратите внимание, что с недавним компилятором программа прерывается все время , Он передается только с более старыми компиляторами, потому что версия 'abs', которая вызывается, представляет собой целочисленную версию ... –