Производительность умножения матрицы Eigen3

Примечание. Я разместил это также на форуме Eigen здесь

Я хочу предварительно умножить матрицы 3xN на матрицу 3x3, т.е. преобразовать 3D-точки, например p_dest = T * p_source

после инициализации матриц:

Eigen::Matrix<double, 3, Eigen::Dynamic> points = Eigen::Matrix<double, 3, Eigen::Dynamic>::Random(3, NUMCOLS);
Eigen::Matrix<double, 3, Eigen::Dynamic> dest = Eigen::Matrix<double, 3, Eigen::Dynamic>(3, NUMCOLS);
int NT = 100;

Я оценил эти две версии

// eigen direct multiplication
for (int i = 0; i < NT; i++){
  Eigen::Matrix3d T = Eigen::Matrix3d::Random();
  dest.noalias() = T * points;
}

и

// col multiplication
for (int i = 0; i < NT; i++){
  Eigen::Matrix3d T = Eigen::Matrix3d::Random();
  for (int c = 0; c < points.cols(); c++){
    dest.col(c) = T * points.col(c);
  }
}

повторение NT выполняется только для вычисления среднего времени

Я удивлен, что умножение столбца на столбец примерно в 4/5 раз быстрее, чем прямое умножение (а прямое умножение еще медленнее, если я не использую .noalias(), но это нормально, так как это делает временную копию) Я пытался изменить NUMCOLS с 0 на 1000000, и соотношение было линейным.

Я использую Visual Studio 2013 и компилирую в выпуске

На следующем рисунке по X показано количество столбцов матрицы, а по Y среднее время для одной операции, синим цветом показано умножение столбцов на столбцы, красным — умножение матрицы.

img

Любое предложение, почему это происходит?


person user153816    schedule 24.06.2015    source источник


Ответы (1)


Краткий ответ

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

Длинный ответ

Вместо фрагментов кода давайте рассмотрим полный MCVE. Первая, "ты" версия:

void ColMult(Matrix3Xd& dest, Matrix3Xd& points)
{
    Eigen::Matrix3d T = Eigen::Matrix3d::Random();
    for (int c = 0; c < points.cols(); c++){
        dest.col(c) = T * points.col(c);
    }
}

void EigenDirect(Matrix3Xd& dest, Matrix3Xd& points)
{
    Eigen::Matrix3d T = Eigen::Matrix3d::Random();
    dest.noalias() = T * points;
}

int main(int argc, char *argv[])
{
    srand(time(NULL));

    int NUMCOLS = 100000 + rand();

    Matrix3Xd points = Matrix3Xd::Random(3, NUMCOLS);
    Matrix3Xd dest   = Matrix3Xd(3, NUMCOLS);
    Matrix3Xd dest2  = Matrix3Xd(3, NUMCOLS);
    int NT = 200;
    // eigen direct multiplication
    auto beg1 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < NT; i++)
    {
        EigenDirect(dest, points);
    }
    auto end1 = std::chrono::high_resolution_clock::now();

    std::chrono::duration<double> elapsed_seconds = end1-beg1;

    // col multiplication
    auto beg2 = std::chrono::high_resolution_clock::now();
    for(int i = 0; i < NT; i++)
    {
        ColMult(dest2, points);
    }

    auto end2 = std::chrono::high_resolution_clock::now();

    std::chrono::duration<double> elapsed_seconds2 = end2-beg2;
    std::cout << "Direct time: " << elapsed_seconds.count() << "\n";
    std::cout << "Col time: " << elapsed_seconds2.count() << "\n";

    std::cout << "Eigen speedup: " << elapsed_seconds2.count() / elapsed_seconds.count() << "\n\n";
    return 0;
}

С этим кодом (и включенным SSE) я получаю:

Direct time: 0.449301
Col time: 0.10107
Eigen speedup: 0.224949

То же самое замедление 4-5, на которое вы жаловались. Почему?!?! Прежде чем мы перейдем к ответу, давайте немного изменим код, чтобы матрица dest отправлялась в матрицу ostream. Добавьте std::ostream outPut(0); в начало main() и перед окончанием таймеров добавьте outPut << dest << "\n\n"; и outPut << dest2 << "\n\n";. std::ostream outPut(0); ничего не выводит (я уверен, что установлен плохой бит), но Eigens operator<< становится 1DenseBase.html#a3806d3f42de165878dace160e6aba40a" rel="nofollow noreferrer">вызывается, что приводит к принудительному вычислению матрицы.

ПРИМЕЧАНИЕ: если бы мы использовали outPut << dest(1,1), то dest оценивалось бы ровно столько, сколько необходимо для вывода одного элемента в методе умножения col.

Затем мы получаем

Direct time: 0.447298
Col time: 0.681456
Eigen speedup: 1.52349

в результате ожидаемо. Обратите внимание, что прямой метод Eigen занял точно такое же время (это означает, что оценка происходила даже без добавления ostream), тогда как метод col неожиданно занял гораздо больше времени.

person Avi Ginsburg    schedule 29.06.2015