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? = р
Я смотрел на этот вопрос, потому что у меня был тот же вопрос. У меня были некоторые мысли, чтобы дать более строгий тест.
- Как отметил Родригоб, в порядке следования есть недостатки, так что любые результаты в коде, который вы изначально прикрепили, будут давать вводящие в заблуждение данные
- Кроме того, существуют довольно небольшие по размеру массивы, которые устанавливаются с помощью констант. Компилятор может оптимизировать циклы, когда на самом деле компилятор не будет знать размер массивов. Размеры массивов и количество итераций должны быть входными данными времени выполнения на всякий случай.
На 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;
}
Один с одним одномерным массивом, использующим [] с целочисленной математикой и двойным циклом
Один с тем же самым одномерным массивом, использующим приращение указателя
Многомерный массив C
Буст мульти-массив
так что беги из командной строки, беги
./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