Почему Armadillo такой медленный по сравнению с массивом в стиле C в простой построчной вычислительной задаче - PullRequest
0 голосов
/ 23 ноября 2018

В настоящее время я вычисляю небольшое количество для каждого значения большой матрицы (миллионы строк, количество столбцов <1000), рассматривая каждую строку независимо.</p>

Точнее, для каждого значения M ( i , j ) в каждой строке i , столбец j этой матрицы, количество просто [ M ( i , j ) - среднее значение ( i , s )] / std ( i , s ), где s - это подмножество s in M ( i , :) - j другими словами, s является подмножеством всех значений строки i без значения j .

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

Есть ли какая-то конкретная причина или что-то, что я где-то упустил?Вот пример, скомпилированный с: -O2 -lstdc++ -DARMA_DONT_USE_WRAPPER -lopenblas -llapack -lm.Также пытался использовать ARMA_NO_DEBUG безуспешно.

#include <string>
#include <vector>
#include <iostream>
#include <fstream>
#include <algorithm>
#include <armadillo>
#include <chrono>

using namespace std::chrono;

/***************************
 * main()
 ***************************/
int main( int argc, char *argv[] )
{
    unsigned nrows = 2000000; //number of rows
    unsigned ncols = 100; //number of cols

    const arma::mat huge_mat = arma::randn(nrows, ncols); //create huge matrix

    const arma::uvec vec = arma::linspace<arma::uvec>( 0, huge_mat.n_cols-1, huge_mat.n_cols); //create a vector of [0,...,n]
    arma::rowvec inds = arma::zeros<arma::rowvec>( huge_mat.n_cols-1 ); //-1 since we remove only one value at each step.
    arma::colvec simuT = arma::zeros<arma::colvec>( ncols ); //let's store the results in this simuT vector.

    high_resolution_clock::time_point t1 = high_resolution_clock::now();

    //compute some normalization over each value of line of this huge matrix:
    for(unsigned i=0; i < nrows; i++) {
        const arma::rowvec current_line = huge_mat.row(i); //extract current line

        //for each observation in current_line:
        for(unsigned j=0; j < ncols; j++) {

            //Take care of side effects first:
            if( j == 0 )
                inds = current_line(arma::span(1, ncols-1));
            else
                if( j == 1 ) {
                    inds(0) = current_line(0);
                    inds(arma::span(1, ncols-2)) = current_line( arma::span(2, ncols-1) );
                } else
                    inds(arma::span(0, j-1)) = current_line( arma::span(0, j-1) );

            //Let's do some computation: huge_mat(i,j) - mean[huge_mat(i,:)] / std([huge_mat(i,:)]) //can compute the mean and std first... for each line.
            simuT(j) = (current_line(j) - arma::mean(inds))  / ( std::sqrt( 1+1/((double) ncols-1) ) * arma::stddev(inds) );
        }
    }

    high_resolution_clock::time_point t2 = high_resolution_clock::now();
    auto duration = duration_cast<seconds>( t2 - t1 ).count();
    std::cout << "ARMADILLO: " << duration << " secs\n";

    //------------------PLAIN C Array
    double *Mat_full;
    double *output;
    unsigned int i,j,k;
    double mean=0, stdd=0;
    double sq_diff_sum = 0, sum=0;
    double diff = 0;

    Mat_full = (double *) malloc(ncols * nrows * sizeof(double));
    output = (double *) malloc(nrows * ncols * sizeof(double));

    std::vector< std::vector<double> > V(huge_mat.n_rows);

    //Some UGLY copy from arma::mat to double* using a vector:
    for (size_t i = 0; i < huge_mat.n_rows; ++i)
        V[i] = arma::conv_to< std::vector<double> >::from(huge_mat.row(i));

    //then dump to Mat_full array:
    for (i=0; i < V.size(); i++)
        for (j=0; j < V[i].size(); j++)
            Mat_full[i + huge_mat.n_rows * j] = V[i][j];

    t1 = high_resolution_clock::now();

    for(i=0; i < nrows; i++)
        for(j=0; j < ncols; j++)
        {
            //compute mean of subset-------------------
            sum = 0;
            for(k = 0; k < ncols; k++)
                if(k!=j)
                {
                    sum = sum + Mat_full[i+k*nrows];
                }
            mean = sum / (ncols-1);

            //compute standard deviation of subset-----
            sq_diff_sum = 0;
            for(k = 0; k < ncols; k++)
                if(k!=j)
                {
                    diff = Mat_full[i+k*nrows] - mean;
                    sq_diff_sum += diff * diff;
                }
            stdd = sqrt(sq_diff_sum / (ncols-2));

            //export to plain C array:
            output[i*ncols+j] = (Mat_full[i+j*nrows] - mean) / (sqrt(1+1/(((double) ncols)-1))*stdd);
        }

    t2 = high_resolution_clock::now();
    duration = duration_cast<seconds>( t2 - t1 ).count();
    std::cout << "C ARRAY: " << duration << " secs\n";
}

В частности, вызовы arma :: mean и arma :: stddev, кажется, плохо работают при сравнении времени выполнения.Я не проводил углубленного анализа влияния размера на производительность, но кажется, что для небольших значений nrows обычный C имеет тенденцию (очень) быстрее.Для простого теста с использованием этой настройки я получил:

ARMADILLO: 111 secs
C ARRAY: 79 secs

во время выполнения.

EDIT Вот модификация, в которой мы работаем по столбцам, а не по строками обрабатывать каждый столбец независимо, как это предлагается @rubenvb и @mtall.Результирующее время выполнения немного уменьшается (ARMADILLO: 104 secs сейчас), что показывает некоторые улучшения по сравнению с работой по строкам:

#include <string>
#include <vector>
#include <iostream>
#include <fstream>
#include <algorithm>
#include <armadillo>
#include <chrono>

using namespace std::chrono;

/***************************
 * main()
 ***************************/
int main( int argc, char *argv[] )
{
    unsigned nrows = 100; //number of rows
    unsigned ncols = 2000000; //number of cols

    const arma::mat huge_mat = arma::randn(nrows, ncols); //create huge matrix

    const arma::uvec vec = arma::linspace<arma::uvec>( 0, huge_mat.n_rows-1, huge_mat.n_rows); //create a vector of [0,...,n]
    arma::colvec inds = arma::zeros<arma::colvec>( huge_mat.n_rows-1 ); //-1 since we remove only one value at each step.
    arma::rowvec simuT = arma::zeros<arma::rowvec>( nrows ); //let's store the results in this simuT vector.

    high_resolution_clock::time_point t1 = high_resolution_clock::now();

    //compute some normalization over each value of line of this huge matrix:
    for(unsigned i=0; i < ncols; i++) {
        const arma::colvec current_line = huge_mat.col(i); //extract current line

        //for each observation in current_line:
        for(unsigned j=0; j < nrows; j++) {

            //Take care of side effects first:
            if( j == 0 )
                inds = current_line(arma::span(1, nrows-1));
            else
                if( j == 1 ) {
                    inds(0) = current_line(0);
                    inds(arma::span(1, nrows-2)) = current_line( arma::span(2, nrows-1) );
                } else
                    inds(arma::span(0, j-1)) = current_line( arma::span(0, j-1) );

            //Let's do some computation: huge_mat(i,j) - mean[huge_mat(i,:)] / std([huge_mat(i,:)]) //can compute the mean and std first... for each line.
            simuT(j) = (current_line(j) - arma::mean(inds))  / ( std::sqrt( 1+1/((double) nrows-1) ) * arma::stddev(inds) );
        }
    }

    high_resolution_clock::time_point t2 = high_resolution_clock::now();
    auto duration = duration_cast<seconds>( t2 - t1 ).count();
    std::cout << "ARMADILLO: " << duration << " secs\n";
}

1 Ответ

0 голосов
/ 27 ноября 2018

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

Чтобы проверить, является ли это причиной, выполнитетот же расчет, но для столбцов вместо строк, и проверьте разницу.

...