Kerim
Kerim

Reputation: 199

Armadillo library difference between square brackets [] and parenthesis ()

Armadillo library provides 3 ways for element access:

  1. via ();
  2. via [];
  3. via at();

I noticed that there is difference in perfomance when using each of them. I tested the code below:

size_t n_row = 500, n_col = 500;

{ // ()
    wall_clock timer;
    mat matrix(n_row, n_col, fill::zeros);
    timer.tic();
    for (size_t i = 0; i < n_row; i++){
        for (size_t j = 0; j < n_col; j++){
            matrix(i, j) = i+j;
        }
    }
    std::cout << timer.toc() << std::endl;
}

{ // []
    wall_clock timer;
    mat matrix(n_row, n_col, fill::zeros);
    timer.tic();
    for (size_t i = 0; i < n_row; i++){
        for (size_t j = 0; j < n_col; j++){
            matrix[i, j] = i+j;
        }
    }
    std::cout << timer.toc() << std::endl;
}

{ // .at()
    wall_clock timer;
    mat matrix(n_row, n_col, fill::zeros);
    timer.tic();
    for (size_t i = 0; i < n_row; i++){
        for (size_t j = 0; j < n_col; j++){
            matrix.at(i, j) = i+j;
        }
    }
    std::cout << timer.toc() << std::endl;
}

The result is:

  1. via () 0.0008 sec;
  2. via [] 0.0003 sec;
  3. via at() 0.0007 sec;

Is anybody able to comment the given result? I use Windows 10 x64, MSVC 2017, Release mode, Qt 5.14.2 I set #define ARMA_NO_DEBUG in config.hpp

Upvotes: 1

Views: 236

Answers (1)

Arthur Tacca
Arthur Tacca

Reputation: 10018

The answer is mostly in the link you gave:

  • ".at(n) or [n] As for (n), but without a bounds check. Not recommended for use unless your code has been thoroughly debugged."
    • So the first part of the answer is that .at(n) and [n] are potentially slightly faster, but less safe, than (n), which is maybe why () access took longest in your tests.
    • But [n] and .at(n) are the same, and the difference in times you saw is just same artefact of the way you ran or timed the tests. This is not a surprise because benchmarking is notoriously difficult, it is not just a matter of running something in a loop with some timing as you're doing. (Actually this is probably even the real reason for the time difference you saw with ().)
    • This convention is confusingly different from STL containers: normally [] is unchecked and .at() is checked, while () for indexing doesn't exist at all.
  • There are also (i,j) and .at(i,j) and also (i,j,k) and .at(i,j,k), but no [i,j] or [i,j,k].
    • So the reason for the seemingly redudant existence of both .at() and [] is that [] is not available for more than one index (but is presumably still available for the one-index case because it's so much more natural than .at()).
    • This is not mentioned in the link, but that's because C++ does not allow overloading the operator[] function with multiple parameters, so this would always end up being interpreted as [(i,j)] which ends up being evaluated as [i] - the j is evaluated (e.g. if it is a function call) but then silently thrown away!

In reality, checked index access is highly unlikely to be a bottleneck in most applications, and in many cases might be totally optimised away by the compiler anyway (at least in release mode). So my suggestion is to just always use round bracket mat(ind) notation.

Upvotes: 1

Related Questions