Boost::multi_array вопрос производительности

Я пытаюсь сравнить производительность boost::multi_array с собственными динамически размещаемыми массивами с помощью следующей тестовой программы:

#include <windows.h>
#define _SCL_SECURE_NO_WARNINGS
#define BOOST_DISABLE_ASSERTS 
#include <boost/multi_array.hpp>

int main(int argc, char* argv[])
{
    const int X_SIZE = 200;
    const int Y_SIZE = 200;
    const int ITERATIONS = 500;
    unsigned int startTime = 0;
    unsigned int endTime = 0;

    // Create the boost array
    typedef boost::multi_array<double, 2> ImageArrayType;
    ImageArrayType boostMatrix(boost::extents[X_SIZE][Y_SIZE]);

    // Create the native array
    double *nativeMatrix = new double [X_SIZE * Y_SIZE];

    //------------------Measure boost----------------------------------------------
    startTime = ::GetTickCount();
    for (int i = 0; i < ITERATIONS; ++i)
    {
        for (int y = 0; y < Y_SIZE; ++y)
        {
            for (int x = 0; x < X_SIZE; ++x)
            {
                boostMatrix[x][y] = 2.345;
            }
        }
    }
    endTime = ::GetTickCount();
    printf("[Boost] Elapsed time: %6.3f seconds\n", (endTime - startTime) / 1000.0);

    //------------------Measure native-----------------------------------------------
    startTime = ::GetTickCount();
    for (int i = 0; i < ITERATIONS; ++i)
    {
        for (int y = 0; y < Y_SIZE; ++y)
        {
            for (int x = 0; x < X_SIZE; ++x)
            {
                nativeMatrix[x + (y * X_SIZE)] = 2.345;
            }
        }
    }
    endTime = ::GetTickCount();
    printf("[Native]Elapsed time: %6.3f seconds\n", (endTime - startTime) / 1000.0);

    return 0;
}

Я получаю следующие результаты:

[Boost] Elapsed time: 12.500 seconds
[Native]Elapsed time:  0.062 seconds

Я не могу поверить, что multi_arrays намного медленнее. Кто-нибудь может определить, что я делаю не так?

Я предполагаю, что кэширование не является проблемой, так как я делаю записи в память.

РЕДАКТИРОВАТЬ: Это была отладочная сборка. По словам Ласераллана, я выполнил сборку релиза:

[Boost] Elapsed time:  0.266 seconds
[Native]Elapsed time:  0.016 seconds

Намного ближе. Но 16 к 1 все еще кажется высоким.

Ну, нет однозначного ответа, но я собираюсь двигаться дальше и пока оставлю свой реальный код с нативными массивами.

Принимая ответ Ласераллана, потому что это был самый большой недостаток в моем тесте.

Спасибо всем.

17 ответов

Решение

Вы создаете релиз или отлаживаете?

При работе в режиме отладки массив надстройки может быть очень медленным, потому что их магия шаблона не встроена должным образом, что приводит к большим накладным расходам при вызовах функций. Я не уверен, как реализован мульти-массив, так что это может быть полностью отключено:)

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

Попробуйте изменить порядок петель X и Y и посмотрите, получите ли вы что-нибудь. Некоторая информация о порядке хранения здесь: http://www.boost.org/doc/libs/1_37_0/libs/multi_array/doc/user.html

РЕДАКТИРОВАТЬ: Так как вы, кажется, используете двумерный массив для обработки изображений, вы могли бы быть заинтересованы в улучшении библиотеки обработки изображений GIL.

У него могут быть массивы с меньшими накладными расходами, которые отлично подходят для вашей ситуации.

На моей машине использую

g++ -O3 -march=native -mtune=native --fast-math -DNDEBUG test.cpp -o test && ./test

я получил

[Boost] Elapsed time:  0.020 seconds
[Native]Elapsed time:  0.020 seconds

Однако меняется const int ITERATIONS в 5000 я получил

[Boost] Elapsed time:  0.240 seconds
[Native]Elapsed time:  0.180 seconds

затем с ITERATIONS вернуться к 500 но X_SIZE а также Y_SIZE установлен в 400 Я получаю гораздо более существенную разницу

[Boost] Elapsed time:  0.460 seconds
[Native]Elapsed time:  0.070 seconds

наконец, инвертируя внутренний цикл для [Boost] дело так выглядит

    for (int x = 0; x < X_SIZE; ++x)
    {
        for (int y = 0; y < Y_SIZE; ++y)
        {

и сохраняя ITERATIONS, X_SIZE а также Y_SIZE в 500, 400 а также 400 я получил

[Boost] Elapsed time:  0.060 seconds
[Native]Elapsed time:  0.080 seconds

Если я инвертировать внутренний цикл также для [Native] случай (так что это в неправильном порядке для этого случая), я получаю, что неудивительно,

[Boost] Elapsed time:  0.070 seconds
[Native]Elapsed time:  0.450 seconds

я использую gcc (Ubuntu/Linaro 4.4.4-14ubuntu5) 4.4.5 на Ubuntu 10.10

Итак, в заключение:

  • При правильной оптимизации boost::multi_array делает свою работу как положено
  • Порядок доступа к вашим данным имеет значение

Ваш тест ошибочен.

  • В сборке DEBUG, boost::MultiArray не хватает прохода оптимизации, который ему крайне необходим. (Гораздо больше, чем родной массив)
  • В сборке RELEASE ваш компилятор будет искать код, который можно сразу удалить, и большая часть вашего кода находится в этой категории.

Вероятно, вы увидите, что ваш оптимизирующий компилятор видит, что большинство или все ваши циклы "родного массива" могут быть удалены. То же самое теоретически верно для ваших циклов boost:: MultiArray, но MultiArray, вероятно, достаточно сложен, чтобы победить ваш оптимизатор.

Внесите это небольшое изменение в свой испытательный стенд, и вы увидите более правдоподобные результаты: измените оба случая "= 2.345 " с "*= 2.345 "и снова скомпилируйте с оптимизацией. Это не даст вашему компилятору обнаружить, что внешний цикл каждого теста является избыточным.

Я сделал это и получил сравнение скорости ближе к 2:1.

Мне интересно две вещи:

1) проверка границ: определите макрос препроцессора BOOST_DISABLE_ASSERTS до включения multi_array.hpp в ваше приложение. Это отключает обязательную проверку. не уверен, если это отключено, когда NDEBUG.

2) базовый индекс: MultiArray может индексировать массивы из баз, отличных от 0. Это означает, что multi_array хранит базовое число (в каждом измерении) и использует более сложную формулу для получения точного местоположения в памяти, мне интересно, все ли это тот.

В противном случае я не понимаю, почему мульти-массив должен быть медленнее, чем С-массивы.

Попробуйте использовать Blitz++. Я опробовал Blitz, и его производительность находится на одном уровне с массивом в стиле C!

Проверьте свой код с Blitz, добавленным ниже:


#include <windows.h>
#define _SCL_SECURE_NO_WARNINGS
#define BOOST_DISABLE_ASSERTS 
#include <boost/multi_array.hpp>
#include <blitz/array.h>

int main(int argc, char* argv[])
{
    const int X_SIZE = 200;
    const int Y_SIZE = 200;
    const int ITERATIONS = 500;
    unsigned int startTime = 0;
    unsigned int endTime = 0;

    // Create the boost array
    typedef boost::multi_array<double, 2> ImageArrayType;
    ImageArrayType boostMatrix(boost::extents[X_SIZE][Y_SIZE]);


    //------------------Measure boost----------------------------------------------
    startTime = ::GetTickCount();
    for (int i = 0; i < ITERATIONS; ++i)
    {
        for (int y = 0; y < Y_SIZE; ++y)
        {
            for (int x = 0; x < X_SIZE; ++x)
            {
                boostMatrix[x][y] = 2.345;
            }
        }
    }
    endTime = ::GetTickCount();
    printf("[Boost] Elapsed time: %6.3f seconds\n", (endTime - startTime) / 1000.0);

    //------------------Measure blitz-----------------------------------------------
    blitz::Array<double, 2> blitzArray( X_SIZE, Y_SIZE );
    startTime = ::GetTickCount();
    for (int i = 0; i < ITERATIONS; ++i)
    {
        for (int y = 0; y < Y_SIZE; ++y)
        {
            for (int x = 0; x < X_SIZE; ++x)
            {
                blitzArray(x,y) = 2.345;
            }
        }
    }
    endTime = ::GetTickCount();
    printf("[Blitz] Elapsed time: %6.3f seconds\n", (endTime - startTime) / 1000.0);


    //------------------Measure native-----------------------------------------------
    // Create the native array
    double *nativeMatrix = new double [X_SIZE * Y_SIZE];

    startTime = ::GetTickCount();
    for (int i = 0; i < ITERATIONS; ++i)
    {
        for (int y = 0; y < Y_SIZE; ++y)
        {
            for (int x = 0; x < X_SIZE; ++x)
            {
                nativeMatrix[x + (y * X_SIZE)] = 2.345;
            }
        }
    }
    endTime = ::GetTickCount();
    printf("[Native]Elapsed time: %6.3f seconds\n", (endTime - startTime) / 1000.0);



    return 0;
}

Вот результат в отладке и выпуске.

DEBUG:

Boost  2.093 secs 
Blitz  0.375 secs 
Native 0.078 secs

РЕЛИЗ:

Boost  0.266 secs
Blitz  0.016 secs
Native 0.015 secs

Для этого я использовал компилятор MSVC 2008 SP1.

Можем ли мы теперь попрощаться с массивом C-stlye? = р

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

  1. Как отметил Родригоб, в порядке следования есть недостатки, так что любые результаты в коде, который вы изначально прикрепили, будут давать вводящие в заблуждение данные
  2. Кроме того, существуют довольно небольшие по размеру массивы, которые устанавливаются с помощью констант. Компилятор может оптимизировать циклы, когда на самом деле компилятор не будет знать размер массивов. Размеры массивов и количество итераций должны быть входными данными времени выполнения на всякий случай.

На Mac следующий код настроен для предоставления более значимых ответов. Здесь есть 4 теста.

#define BOOST_DISABLE_ASSERTS
#include "boost/multi_array.hpp"
#include <sys/time.h>
#include <stdint.h>
#include<string>

uint64_t GetTimeMs64()
{
  struct timeval tv;

  gettimeofday( &tv, NULL );

  uint64_t ret = tv.tv_usec;
  /* Convert from micro seconds (10^-6) to milliseconds (10^-3) */
  ret /= 1000;

  /* Adds the seconds (10^0) after converting them to milliseconds (10^-3) */
  ret += ( tv.tv_sec * 1000 );

  return ret;

}


void function1( const int X_SIZE, const int Y_SIZE, const int ITERATIONS )
{

  double nativeMatrix1add[X_SIZE*Y_SIZE];

  for( int x = 0 ; x < X_SIZE ; ++x )
  {
    for( int y = 0 ; y < Y_SIZE ; ++y )
    {
      nativeMatrix1add[y + ( x * Y_SIZE )] = rand();
    }
  }

  // Create the native array
  double* __restrict const nativeMatrix1p = new double[X_SIZE * Y_SIZE];
  uint64_t startTime = GetTimeMs64();
  for( int i = 0 ; i < ITERATIONS ; ++i )
  {
    for( int xy = 0 ; xy < X_SIZE*Y_SIZE ; ++xy )
    {
      nativeMatrix1p[xy] += nativeMatrix1add[xy];
    }
  }
  uint64_t endTime = GetTimeMs64();
  printf( "[Native Pointer]    Elapsed time: %6.3f seconds\n", ( endTime - startTime ) / 1000.0 );

}

void function2( const int X_SIZE, const int Y_SIZE, const int ITERATIONS )
{

  double nativeMatrix1add[X_SIZE*Y_SIZE];

  for( int x = 0 ; x < X_SIZE ; ++x )
  {
    for( int y = 0 ; y < Y_SIZE ; ++y )
    {
      nativeMatrix1add[y + ( x * Y_SIZE )] = rand();
    }
  }

  // Create the native array
  double* __restrict const nativeMatrix1 = new double[X_SIZE * Y_SIZE];
  uint64_t startTime = GetTimeMs64();
  for( int i = 0 ; i < ITERATIONS ; ++i )
  {
    for( int x = 0 ; x < X_SIZE ; ++x )
    {
      for( int y = 0 ; y < Y_SIZE ; ++y )
      {
        nativeMatrix1[y + ( x * Y_SIZE )] += nativeMatrix1add[y + ( x * Y_SIZE )];
      }
    }
  }
  uint64_t endTime = GetTimeMs64();
  printf( "[Native 1D Array]   Elapsed time: %6.3f seconds\n", ( endTime - startTime ) / 1000.0 );

}


void function3( const int X_SIZE, const int Y_SIZE, const int ITERATIONS )
{

  double nativeMatrix2add[X_SIZE][Y_SIZE];

  for( int x = 0 ; x < X_SIZE ; ++x )
  {
    for( int y = 0 ; y < Y_SIZE ; ++y )
    {
      nativeMatrix2add[x][y] = rand();
    }
  }

  // Create the native array
  double nativeMatrix2[X_SIZE][Y_SIZE];
  uint64_t startTime = GetTimeMs64();
  for( int i = 0 ; i < ITERATIONS ; ++i )
  {
    for( int x = 0 ; x < X_SIZE ; ++x )
    {
      for( int y = 0 ; y < Y_SIZE ; ++y )
      {
        nativeMatrix2[x][y] += nativeMatrix2add[x][y];
      }
    }
  }
  uint64_t endTime = GetTimeMs64();
  printf( "[Native 2D Array]   Elapsed time: %6.3f seconds\n", ( endTime - startTime ) / 1000.0 );

}



void function4( const int X_SIZE, const int Y_SIZE, const int ITERATIONS )
{

  boost::multi_array<double, 2> boostMatrix2add( boost::extents[X_SIZE][Y_SIZE] );

  for( int x = 0 ; x < X_SIZE ; ++x )
  {
    for( int y = 0 ; y < Y_SIZE ; ++y )
    {
      boostMatrix2add[x][y] = rand();
    }
  }

  // Create the native array
  boost::multi_array<double, 2> boostMatrix( boost::extents[X_SIZE][Y_SIZE] );
  uint64_t startTime = GetTimeMs64();
  for( int i = 0 ; i < ITERATIONS ; ++i )
  {
    for( int x = 0 ; x < X_SIZE ; ++x )
    {
      for( int y = 0 ; y < Y_SIZE ; ++y )
      {
        boostMatrix[x][y] += boostMatrix2add[x][y];
      }
    }
  }
  uint64_t endTime = GetTimeMs64();
  printf( "[Boost Array]       Elapsed time: %6.3f seconds\n", ( endTime - startTime ) / 1000.0 );

}

int main( int argc, char* argv[] )
{

  srand( time( NULL ) );

  const int X_SIZE = std::stoi( argv[1] );
  const int Y_SIZE = std::stoi( argv[2] );
  const int ITERATIONS = std::stoi( argv[3] );

  function1( X_SIZE, Y_SIZE, ITERATIONS );
  function2( X_SIZE, Y_SIZE, ITERATIONS );
  function3( X_SIZE, Y_SIZE, ITERATIONS );
  function4( X_SIZE, Y_SIZE, ITERATIONS );

  return 0;
}
  1. Один с одним одномерным массивом, использующим [] с целочисленной математикой и двойным циклом

  2. Один с тем же самым одномерным массивом, использующим приращение указателя

  3. Многомерный массив C

  4. Буст мульти-массив

так что беги из командной строки, беги

./test_array xsize ysize iterations"

и вы можете получить хорошее представление о том, как эти подходы будут работать. Вот что я получил со следующими флагами компилятора:

g++4.9.2 -O3 -march=native -funroll-loops -mno-avx --fast-math -DNDEBUG  -c -std=c++11


./test_array 51200 1 20000
[Native 1-Loop ]    Elapsed time:  0.537 seconds
[Native 1D Array]   Elapsed time:  2.045 seconds
[Native 2D Array]   Elapsed time:  2.749 seconds
[Boost Array]       Elapsed time:  1.167 seconds

./test_array 25600 2 20000
[Native 1-Loop ]    Elapsed time:  0.531 seconds
[Native 1D Array]   Elapsed time:  1.241 seconds
[Native 2D Array]   Elapsed time:  1.631 seconds
[Boost Array]       Elapsed time:  0.954 seconds

./test_array 12800 4 20000
[Native 1-Loop ]    Elapsed time:  0.536 seconds
[Native 1D Array]   Elapsed time:  1.214 seconds
[Native 2D Array]   Elapsed time:  1.223 seconds
[Boost Array]       Elapsed time:  0.798 seconds

./test_array 6400 8 20000
[Native 1-Loop ]    Elapsed time:  0.540 seconds
[Native 1D Array]   Elapsed time:  0.845 seconds
[Native 2D Array]   Elapsed time:  0.878 seconds
[Boost Array]       Elapsed time:  0.803 seconds

./test_array 3200 16 20000
[Native 1-Loop ]    Elapsed time:  0.537 seconds
[Native 1D Array]   Elapsed time:  0.661 seconds
[Native 2D Array]   Elapsed time:  0.673 seconds
[Boost Array]       Elapsed time:  0.708 seconds

./test_array 1600 32 20000
[Native 1-Loop ]    Elapsed time:  0.532 seconds
[Native 1D Array]   Elapsed time:  0.592 seconds
[Native 2D Array]   Elapsed time:  0.596 seconds
[Boost Array]       Elapsed time:  0.764 seconds

./test_array 800 64 20000
[Native 1-Loop ]    Elapsed time:  0.546 seconds
[Native 1D Array]   Elapsed time:  0.594 seconds
[Native 2D Array]   Elapsed time:  0.606 seconds
[Boost Array]       Elapsed time:  0.764 seconds

./test_array 400 128 20000
[Native 1-Loop ]    Elapsed time:  0.536 seconds
[Native 1D Array]   Elapsed time:  0.560 seconds
[Native 2D Array]   Elapsed time:  0.564 seconds
[Boost Array]       Elapsed time:  0.746 seconds

Итак, я думаю, что можно с уверенностью сказать, что boost multi_array работает довольно хорошо. Ничто не сравнится с оценкой одного цикла, но в зависимости от размера массива boost::multi_array может превзойти стандартный c-массив с двойным циклом.

Еще одна вещь, которую стоит попробовать - это использовать итераторы вместо прямого индекса для массива boost.

Я ожидал бы, что многопользовательский массив будет столь же эффективным. Но я получаю похожие результаты на PPC Mac, используя gcc. Я также попробовал multiarrayref, чтобы обе версии использовали одно и то же хранилище без разницы. Это полезно знать, поскольку в некоторых моих кодах я использую multiarray и просто предположил, что это похоже на ручное кодирование.

Я думаю, я знаю, в чем проблема... возможно.

Для того, чтобы реализация boost имела такой синтаксис, как: matrix[x][y]. это означает, что matrix [x] должна возвращать ссылку на объект, который действует как столбец одномерного массива, и в этом случае ссылка [y] дает вам ваш элемент.

Проблема здесь в том, что вы выполняете итерацию в основном порядке строк (что типично для c/ C++, поскольку собственные массивы являются основными строками IIRC. В этом случае компилятор должен повторно выполнить матрицу [x] для каждого y. Если вы выполняли итерацию в Основной порядок столбца при использовании матрицы повышения, вы можете увидеть лучшую производительность.

Просто теория.

РЕДАКТИРОВАТЬ: в моей системе Linux (с некоторыми небольшими изменениями) я проверил свою теорию, и показал некоторые улучшения производительности путем переключения x и y, но это было все же медленнее, чем собственный массив. Это может быть простой проблемой того, что компилятор не может оптимизировать временный ссылочный тип.

Я тестировал на Snow Leopard Mac OS, используя gcc 4.2.1

Debug:
[Boost] Elapsed time:  2.268 seconds
[Native]Elapsed time:  0.076 seconds

Release:
[Boost] Elapsed time:  0.065 seconds
[Native]Elapsed time:  0.020 seconds

Вот код (модифицированный таким образом, чтобы его можно было скомпилировать в Unix):

#define BOOST_DISABLE_ASSERTS
#include <boost/multi_array.hpp>
#include <ctime>

int main(int argc, char* argv[])
{
    const int X_SIZE = 200;
    const int Y_SIZE = 200;
    const int ITERATIONS = 500;
    unsigned int startTime = 0;
    unsigned int endTime = 0;

    // Create the boost array
    typedef boost::multi_array<double, 2> ImageArrayType;
    ImageArrayType boostMatrix(boost::extents[X_SIZE][Y_SIZE]);

    // Create the native array
    double *nativeMatrix = new double [X_SIZE * Y_SIZE];

    //------------------Measure boost----------------------------------------------
    startTime = clock();
    for (int i = 0; i < ITERATIONS; ++i)
    {
        for (int y = 0; y < Y_SIZE; ++y)
        {
            for (int x = 0; x < X_SIZE; ++x)
            {
                boostMatrix[x][y] = 2.345;
            }
        }
    }
    endTime = clock();
    printf("[Boost] Elapsed time: %6.3f seconds\n", (endTime - startTime) / (double)CLOCKS_PER_SEC);

    //------------------Measure native-----------------------------------------------
    startTime = clock();
    for (int i = 0; i < ITERATIONS; ++i)
    {
        for (int y = 0; y < Y_SIZE; ++y)
        {
            for (int x = 0; x < X_SIZE; ++x)
            {
                nativeMatrix[x + (y * X_SIZE)] = 2.345;
            }
        }
    }
    endTime = clock();
    printf("[Native]Elapsed time: %6.3f seconds\n", (endTime - startTime) / (double)CLOCKS_PER_SEC);

    return 0;
}

Сборка в режиме релиза, использование objdump и просмотр сборки. Они могут делать совершенно разные вещи, и вы сможете увидеть, какие оптимизации использует компилятор.

Глядя на сборку, сгенерированную g++ 4.8.2 с -O3 -DBOOST_DISABLE_ASSERTS и используя оба operator() и [][] В способах доступа к элементам очевидно, что единственной дополнительной операцией по сравнению с собственными массивами и ручным вычислением индекса является добавление базы. Я не измерял стоимость этого все же.

Подобный вопрос был задан и дан ответ здесь:

http://www.codeguru.com/forum/archive/index.php/t-300014.html

Короткий ответ заключается в том, что компилятору проще всего оптимизировать простые массивы, а не оптимизировать версию Boost. Следовательно, конкретный компилятор может не дать версии Boost все те же преимущества оптимизации.

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

Я скомпилировал код (с небольшими изменениями) под VC++ 2010 с включенной оптимизацией ("Максимизировать скорость" вместе со встроенными функциями "Любая подходящая" и "Любящий быстрый код") и получил времена 0,015/0,391. Я сгенерировал список сборок и, хотя я ужасный сборщик новичков, внутри цикла измерения наддува есть одна строка, которая мне не нравится:

call    ??A?$multi_array_ref@N$01@boost@@QAE?AV?$sub_array@N$00@multi_array@detail@1@H@Z ; boost::multi_array_ref<double,2>::operator[]

Один из операторов [] не был встроен! Вызванная процедура делает еще один вызов, на этот раз multi_array::value_accessor_n<...>::access<...>():

call    ??$access@V?$sub_array@N$00@multi_array@detail@boost@@PAN@?$value_accessor_n@N$01@multi_array@detail@boost@@IBE?AV?$sub_array@N$00@123@U?$type@V?$sub_array@N$00@multi_array@detail@boost@@@3@HPANPBIPBH3@Z ; boost::detail::multi_array::value_accessor_n<double,2>::access<boost::detail::multi_array::sub_array<double,1>,double *>

В целом, две процедуры представляют собой довольно много кода для простого доступа к одному элементу в массиве. У меня сложилось общее впечатление, что библиотека настолько сложна и высокоуровнева, что Visual Studio не может оптимизировать ее столько, сколько нам хотелось бы (постеры, использующие gcc, по-видимому, получили лучшие результаты).

ИМХО, хороший компилятор действительно должен был встроить и оптимизировать две процедуры - обе они довольно короткие и простые, не содержат циклов и т. Д. Много времени может быть потрачено впустую просто на передачу своих аргументов и результатов.

Я изменил приведенный выше код в Visual Studio 2008 v9.0.21022 и применил подпрограммы контейнера из подпрограмм "Числовой рецепт" для C и C++.

http://www.nrbook.com/nr3/ используя свои лицензионные подпрограммы dmatrix и MatDoub соответственно

dmatrix использует устаревший синтаксический оператор malloc и не рекомендуется... MatDoub использует команду New

Скорость в секундах в версии Release:

Увеличение: 0,437

Родной: 0.032

Числовые рецепты C: 0.031

Числовые рецепты C++: 0.031

Так что из вышеперечисленного блиц выглядит как лучшая бесплатная альтернатива.

Я снова вернулся к соответствующему вопросу здесь: Сравните blitz++, броненосец, boost::MultiArray (подробности см. здесь).

Возвращаясь к этому вопросу и добавляя для сравнения еще одну библиотеку (MULTI), я получаю:

      $ sudo cpupower frequency-set --governor performance
$ g++ -O3 -DNDEBUG a.cpp && sudo nice -n -10 ./a.out
[Boost Loop] Elapsed time:  0.009 seconds
[Blitz Loop] Elapsed time:  0.007 seconds
[Native Loop] Elapsed time:  0.012 seconds
[MULTI Loop] Elapsed time:  0.007 seconds
      $ sudo cpupower frequency-set --governor performance
$ g++ a.cpp && sudo nice -n -10 ./a.out
[Boost Loop] Elapsed time:  1.516 seconds
[Blitz Loop] Elapsed time:  0.221 seconds
[Native Loop] Elapsed time:  0.062 seconds
[MULTI Loop] Elapsed time:  1.527 seconds

Полный код (адаптирован для Linux и std::chrono и не позволяет компилятору удалять неиспользуемые переменные)

      #include <iostream>
using namespace std;
#define _SCL_SECURE_NO_WARNINGS
#define BOOST_DISABLE_ASSERTS
#include <boost/multi_array.hpp>
#include <blitz/array.h>
#include <armadillo>
#include <eigen3/Eigen/Dense>
#include "/home/correaa/boost-multi/include/multi/array.hpp"

#include <chrono>
auto GetTickCount() {return chrono::steady_clock::now();}

template <class T>
void doNotOptimizeAway(T&& t) {
    __asm__ __volatile__ ("" :: "g" (t));
}

int main(int argc, char* argv[])
{
    const int X_SIZE = 200;
    const int Y_SIZE = 200;
    double factor = 10;  // had to do more iteration to get more steady results, the timings are normalized still
    const int ITERATIONS = 500*10;
    auto startTime = ::GetTickCount();
    auto endTime = ::GetTickCount();

    // Create the boost array


    //------------------Measure boost Loop------------------------------------------
    {
        typedef boost::multi_array<double, 2> ImageArrayType;
        ImageArrayType boostMatrix(boost::extents[X_SIZE][Y_SIZE]);
        startTime = ::GetTickCount();
        for (int i = 0; i < ITERATIONS; ++i)
        {
            for (int x = 0; x < X_SIZE; ++x)
            {
                for (int y = 0; y < Y_SIZE; ++y)
                {
                    boostMatrix[x][y] = 1.0001;
                }
            }
            doNotOptimizeAway(boostMatrix);
        }
        endTime = ::GetTickCount();
        printf("[Boost Loop] Elapsed time: %6.3f seconds\n", chrono::duration_cast<chrono::nanoseconds>(endTime - startTime).count() / 1000000000.0 / factor);
    }
    //------------------Measure blitz Loop-------------------------------------------
    {
        blitz::Array<double, 2> blitzArray( X_SIZE, Y_SIZE );
        startTime = ::GetTickCount();
        for (int i = 0; i < ITERATIONS; ++i)
        {
            for (int x = 0; x < X_SIZE; ++x)
            {
                for (int y = 0; y < Y_SIZE; ++y)
                {
                    blitzArray(x,y) = 1.0001;
                }
            }
            doNotOptimizeAway(blitzArray);
        }
        endTime = ::GetTickCount();
        printf("[Blitz Loop] Elapsed time: %6.3f seconds\n", chrono::duration_cast<chrono::nanoseconds>(endTime - startTime).count() / 1000000000.0 / factor);
    }

    //------------------Measure armadillo loop----------------------------------------
    {
        arma::mat matArray( X_SIZE, Y_SIZE );
        startTime = ::GetTickCount();
        for (int i = 0; i < ITERATIONS; ++i)
        {
            for (int y = 0; y < Y_SIZE; ++y)
            {
                for (int x = 0; x < X_SIZE; ++x)
                {
                    matArray(x,y) = 1.0001;
                }
            }
            doNotOptimizeAway(matArray);
        }
        endTime = ::GetTickCount();
        printf("[arma  Loop]  Elapsed time: %6.3f seconds\n", chrono::duration_cast<chrono::nanoseconds>(endTime - startTime).count() / 1000000000.0 / factor);
    }

    //------------------Measure native loop----------------------------------------
    // Create the native array
    {
        double *nativeMatrix = new double [X_SIZE * Y_SIZE];
        startTime = ::GetTickCount();
        for (int i = 0; i < ITERATIONS; ++i)
        {
            for (int y = 0; y < Y_SIZE*X_SIZE; ++y)
            {
                nativeMatrix[y] = 1.0001;
            }
            doNotOptimizeAway(nativeMatrix);
        }
        endTime = ::GetTickCount();
        printf("[Native Loop] Elapsed time: %6.3f seconds\n", chrono::duration_cast<chrono::nanoseconds>(endTime - startTime).count() / 1000000000.0 / factor);
        delete[] nativeMatrix;
    }
    //------------------Measure EIGEN Loop------------------------------------------
    {
        typedef Eigen::MatrixXd ImageArrayType;
        ImageArrayType eigenMatrix(X_SIZE, Y_SIZE);
        startTime = ::GetTickCount();
        for (int i = 0; i < ITERATIONS; ++i)
        {
            for (int x = 0; x < X_SIZE; ++x)
            {
                for (int y = 0; y < Y_SIZE; ++y)
                {
                    eigenMatrix(x, y) = 1.0001;
                }
            }
            doNotOptimizeAway(eigenMatrix);
        }
        endTime = ::GetTickCount();
        printf("[EIGEN Loop] Elapsed time: %6.3f seconds\n", chrono::duration_cast<chrono::nanoseconds>(endTime - startTime).count() / 1000000000.0 / factor);
    }
    //------------------Measure Multi Loop------------------------------------------
    {
        typedef boost::multi::array<double, 2> ImageArrayType;
        ImageArrayType multiMatrix({X_SIZE, Y_SIZE});
        startTime = ::GetTickCount();
        for (int i = 0; i < ITERATIONS; ++i)
        {
            for (int x = 0; x < X_SIZE; ++x)
            {
                for (int y = 0; y < Y_SIZE; ++y)
                {
                    multiMatrix[x][y] = 1.0001;
                }
            }
            doNotOptimizeAway(multiMatrix);
        }
        endTime = ::GetTickCount();
        printf("[MULTI Loop] Elapsed time: %6.3f seconds\n", chrono::duration_cast<chrono::nanoseconds>(endTime - startTime).count() / 1000000000.0 / factor);
    }

    return 0;
}

Как ответил Родригоб, активация правильной оптимизации (GCC по умолчанию -O0) является ключом к достижению хорошей производительности. Кроме того, я также тестировал Blaze DynamicMatrix, который дал дополнительное увеличение производительности в 2 раза с теми же флагами оптимизации. https://bitbucket.org/account/user/blaze-lib/projects/BLAZE

Другие вопросы по тегам