Является ли std::vector намного медленнее, чем простые массивы? - программирование
Подтвердить что ты не робот

Является ли std::vector намного медленнее, чем простые массивы?

Я всегда считал своей общей мудростью, что std::vector "реализован как массив", бла-бла-бла. Сегодня я спустился и протестировал его, и, похоже, это не так:

Вот некоторые результаты теста:

UseArray completed in 2.619 seconds
UseVector completed in 9.284 seconds
UseVectorPushBack completed in 14.669 seconds
The whole thing completed in 26.591 seconds

Что примерно в 3 - 4 раза медленнее! На самом деле не оправдывает, что комментарии "vector могут быть медленнее для нескольких наносекументов".

И код, который я использовал:

#include <cstdlib>
#include <vector>

#include <iostream>
#include <string>

#include <boost/date_time/posix_time/ptime.hpp>
#include <boost/date_time/microsec_time_clock.hpp>

class TestTimer
{
    public:
        TestTimer(const std::string & name) : name(name),
            start(boost::date_time::microsec_clock<boost::posix_time::ptime>::local_time())
        {
        }

        ~TestTimer()
        {
            using namespace std;
            using namespace boost;

            posix_time::ptime now(date_time::microsec_clock<posix_time::ptime>::local_time());
            posix_time::time_duration d = now - start;

            cout << name << " completed in " << d.total_milliseconds() / 1000.0 <<
                " seconds" << endl;
        }

    private:
        std::string name;
        boost::posix_time::ptime start;
};

struct Pixel
{
    Pixel()
    {
    }

    Pixel(unsigned char r, unsigned char g, unsigned char b) : r(r), g(g), b(b)
    {
    }

    unsigned char r, g, b;
};

void UseVector()
{
    TestTimer t("UseVector");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel> pixels;
        pixels.resize(dimension * dimension);

        for(int i = 0; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }
    }
}

void UseVectorPushBack()
{
    TestTimer t("UseVectorPushBack");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel> pixels;
            pixels.reserve(dimension * dimension);

        for(int i = 0; i < dimension * dimension; ++i)
            pixels.push_back(Pixel(255, 0, 0));
    }
}

void UseArray()
{
    TestTimer t("UseArray");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        Pixel * pixels = (Pixel *)malloc(sizeof(Pixel) * dimension * dimension);

        for(int i = 0 ; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }

        free(pixels);
    }
}

int main()
{
    TestTimer t1("The whole thing");

    UseArray();
    UseVector();
    UseVectorPushBack();

    return 0;
}

Я делаю это неправильно или что-то в этом роде? Или я просто разрушил этот миф о производительности?

Я использую режим Release в Visual Studio 2005.


В Visual С++, #define _SECURE_SCL 0 уменьшает UseVector наполовину (доведя его до 4 секунд). Это действительно огромно, ИМО.

4b9b3361

Ответ 1

Используя следующее:

g++ -O3 Time.cpp -I & MyBoost >
./a.out
UseArray завершено за 2.196 секунд
UseVector завершено за 4.412 секунд
UseVectorPushBack завершено за 8.017 секунд
Все закончилось за 14.626 секунд.

Итак, массив в два раза быстрее, чем вектор.

Но, посмотрев на код более подробно, это ожидается; поскольку вы дважды пробегаете вектор и массив только один раз. Примечание: когда вы resize() вектор, вы не только выделяете память, но также просматриваете вектор и вызываете конструктор на каждом члене.

Повторно упорядочивая код так, чтобы вектор только инициализировал каждый объект один раз:

 std::vector<Pixel>  pixels(dimensions * dimensions, Pixel(255,0,0));

Теперь повторим то же время:

g++ -O3 Time.cpp -I & MyBoost >
./a.out
UseVector завершено за 2.216 секунд

Теперь вектор теперь работает только немного хуже, чем массив. ИМО эта разница незначительна и может быть вызвана целым рядом вещей, не связанных с тестом.

Я бы также принял во внимание, что вы неправильно инициализируете/уничтожаете объект Pixel в методе UseArrray(), поскольку ни один конструктор/деструктор не вызывается (это может быть не проблема для этого простого класса, но что-то немного более сложное (т.е. с указателями или элементами с указателями) вызовут проблемы.

Ответ 2

Отличный вопрос. Я пришел сюда, ожидая найти какое-то простое исправление, которое ускорит векторные тесты. Это не получилось так, как я ожидал!

Оптимизация помогает, но этого недостаточно. С оптимизацией я все еще вижу разницу в производительности 2X между UseArray и UseVector. Интересно, что UseVector был значительно медленнее, чем UseVectorPushBack без оптимизации.

# g++ -Wall -Wextra -pedantic -o vector vector.cpp
# ./vector
UseArray completed in 20.68 seconds
UseVector completed in 120.509 seconds
UseVectorPushBack completed in 37.654 seconds
The whole thing completed in 178.845 seconds
# g++ -Wall -Wextra -pedantic -O3 -o vector vector.cpp
# ./vector
UseArray completed in 3.09 seconds
UseVector completed in 6.09 seconds
UseVectorPushBack completed in 9.847 seconds
The whole thing completed in 19.028 seconds

Идея # 1 - используйте новый [] вместо malloc

Я попытался изменить malloc() на new[] в UseArray, чтобы объекты были построены. И переход от отдельного назначения поля к назначению экземпляра Pixel. Ох, и переименуем внутреннюю переменную цикла в j.

void UseArray()
{
    TestTimer t("UseArray");

    for(int i = 0; i < 1000; ++i)
    {   
        int dimension = 999;

        // Same speed as malloc().
        Pixel * pixels = new Pixel[dimension * dimension];

        for(int j = 0 ; j < dimension * dimension; ++j)
            pixels[j] = Pixel(255, 0, 0);

        delete[] pixels;
    }
}

Удивительно (для меня), ни одно из этих изменений не имело никакого значения. Даже изменение на new[], которое будет по умолчанию сконструировать все пиксели. Кажется, что gcc может оптимизировать вызовы конструктора по умолчанию при использовании new[], но не при использовании vector.

Идея №2 - Удалить повторный оператор [] вызывает

Я также попытался избавиться от поиска triple operator[] и кешировать ссылку на pixels[j]. Это фактически замедлило использование UseVector! К сожалению.

for(int j = 0; j < dimension * dimension; ++j)
{
    // Slower than accessing pixels[j] three times.
    Pixel &pixel = pixels[j];
    pixel.r = 255;
    pixel.g = 0;
    pixel.b = 0;
}

# ./vector 
UseArray completed in 3.226 seconds
UseVector completed in 7.54 seconds
UseVectorPushBack completed in 9.859 seconds
The whole thing completed in 20.626 seconds

Идея № 3 - Удалить конструкторы

Как насчет полного удаления конструкторов? Тогда, возможно, gcc может оптимизировать построение всех объектов при создании векторов. Что произойдет, если мы изменим Pixel на:

struct Pixel
{
    unsigned char r, g, b;
};

Результат: примерно на 10% быстрее. Еще медленнее, чем массив. Hm.

# ./vector 
UseArray completed in 3.239 seconds
UseVector completed in 5.567 seconds

Идея # 4 - Использовать итератор вместо индекса цикла

Как использовать vector<Pixel>::iterator вместо индекса цикла?

for (std::vector<Pixel>::iterator j = pixels.begin(); j != pixels.end(); ++j)
{
    j->r = 255;
    j->g = 0;
    j->b = 0;
}

Результат:

# ./vector 
UseArray completed in 3.264 seconds
UseVector completed in 5.443 seconds

Нет, не иначе. По крайней мере, это не медленнее. Я думал, что это будет иметь производительность, аналогичную # 2, где я использовал ссылку Pixel&.

Заключение

Даже если какой-либо умный cookie определяет, как сделать векторный цикл так же быстро, как и массив, это не говорит о поведении std::vector по умолчанию. Так много для того, чтобы компилятор был достаточно умным, чтобы оптимизировать всю С++-версию и сделать контейнеры STL так же быстро, как сырые массивы.

Суть в том, что компилятор не может оптимизировать вызовы конструктора по умолчанию no-op при использовании std::vector. Если вы используете простой new[], он отлично оптимизирует их. Но не с std::vector. Даже если вы можете переписать свой код, чтобы устранить вызовы конструктора, которые летят перед мантрой: "Компилятор умнее вас. STL так же быстро, как и обычный C. Не беспокойтесь об этом".

Ответ 3

Это старый, но популярный вопрос.

На этом этапе многие программисты будут работать на С++ 11. И в С++ 11 код OP как написанный работает одинаково быстро для UseArray или UseVector.

UseVector completed in 3.74482 seconds
UseArray completed in 3.70414 seconds

Основная проблема заключалась в том, что, хотя ваша структура Pixel не была инициализирована, std::vector<T>::resize( size_t, T const&=T() ) принимает построенные по умолчанию Pixel и копии. Компилятор не заметил, что его просят скопировать неинициализированные данные, поэтому он действительно выполнил копию.

В С++ 11 std::vector<T>::resize имеет две перегрузки. Первая - std::vector<T>::resize(size_t), другая - std::vector<T>::resize(size_t, T const&). Это означает, что когда вы вызываете resize без второго аргумента, он просто создает по умолчанию конструкцию, а компилятор достаточно умен, чтобы понять, что конструкция по умолчанию ничего не делает, поэтому пропускает проход над буфером.

(Две перегрузки, в которые добавляются манипулируемые подвижные, конструктивные и не скопируемые типы - повышение производительности при работе с неинициализированными данными - это бонус).

Решение push_back также выполняет проверку fencepost, что замедляет его, поэтому оно остается медленнее, чем версия malloc.

живой пример (я также заменил таймер на chrono::high_resolution_clock).

Обратите внимание, что если у вас есть структура, которая обычно требует инициализации, но вы хотите обработать ее после роста вашего буфера, вы можете сделать это с помощью специализированного распределителя std::vector. Если вы хотите переместить его в более обычный std::vector, я считаю, что осторожное использование allocator_traits и переопределение == может вывести это, но я не уверен.

Ответ 4

Справедливости ради, вы не можете сравнить реализацию С++ с реализацией C, как я бы назвал вашу версию malloc. malloc не создает объекты - он выделяет только необработанную память. То, что вы тогда относитесь к этой памяти как к объектам без вызова конструктора, является слабым С++ (возможно, недействительным - я оставлю это для юристов языка).

Тем не менее, простое изменение malloc на new Pixel[dimensions*dimensions] и свободное до delete [] pixels не имеет большого значения с простой реализацией Pixel, который у вас есть. Здесь результаты на моем ящике (E6600, 64-бит):

UseArray completed in 0.269 seconds
UseVector completed in 1.665 seconds
UseVectorPushBack completed in 7.309 seconds
The whole thing completed in 9.244 seconds

Но с небольшим изменением таблицы поворачиваются:

Pixel.h

struct Pixel
{
    Pixel();
    Pixel(unsigned char r, unsigned char g, unsigned char b);

    unsigned char r, g, b;
};

Pixel.cc

#include "Pixel.h"

Pixel::Pixel() {}
Pixel::Pixel(unsigned char r, unsigned char g, unsigned char b) 
  : r(r), g(g), b(b) {}

main.cc

#include "Pixel.h"
[rest of test harness without class Pixel]
[UseArray now uses new/delete not malloc/free]

Скомпилирован следующим образом:

$ g++ -O3 -c -o Pixel.o Pixel.cc
$ g++ -O3 -c -o main.o main.cc
$ g++ -o main main.o Pixel.o

мы получаем очень разные результаты:

UseArray completed in 2.78 seconds
UseVector completed in 1.651 seconds
UseVectorPushBack completed in 7.826 seconds
The whole thing completed in 12.258 seconds

С не-встроенным конструктором для пикселя std::vector теперь превосходит необработанный массив.

Похоже, что сложность распределения через std::vector и std: allocator слишком оптимизирована так же эффективно, как простой new Pixel[n]. Тем не менее, мы видим, что проблема заключается в простом распределении не векторного доступа, путем настройки нескольких тестовых функций для создания вектора/массива один раз, перемещая его за пределы цикла:

void UseVector()
{
    TestTimer t("UseVector");

    int dimension = 999;
    std::vector<Pixel> pixels;
    pixels.resize(dimension * dimension);

    for(int i = 0; i < 1000; ++i)
    {
        for(int i = 0; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }
    }
}

и

void UseArray()
{
    TestTimer t("UseArray");

    int dimension = 999;
    Pixel * pixels = new Pixel[dimension * dimension];

    for(int i = 0; i < 1000; ++i)
    {
        for(int i = 0 ; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }
    }
    delete [] pixels;
}

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

UseArray completed in 0.254 seconds
UseVector completed in 0.249 seconds
UseVectorPushBack completed in 7.298 seconds
The whole thing completed in 7.802 seconds

Что мы можем узнать из этого, так это то, что std::vector сопоставим с необработанным массивом для доступа, но если вам нужно много раз создавать и удалять вектор/массив, создание сложного объекта будет более трудоемким, что создание простой массив, когда конструктор элемента не встроен. Я не думаю, что это очень удивительно.

Ответ 5

Попробуйте следующее:

void UseVectorCtor()
{
    TestTimer t("UseConstructor");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel> pixels(dimension * dimension, Pixel(255, 0, 0));
    }
}

Я получаю почти то же значение, что и с массивом.

Дело в vector заключается в том, что это гораздо более общий инструмент, чем массив. И это означает, что вам нужно подумать о том, как вы его используете. Его можно использовать по-разному, предоставляя функциональность, которой нет в массиве. И если вы используете его "неправильно" для своей цели, вы несете много накладных расходов, но если вы используете его правильно, это обычно основная структура данных с нулевым накладным расходами. В этом случае проблема заключается в том, что вы отдельно инициализировали вектор (заставляя все элементы ссылаться по умолчанию на ctor), а затем переписывая каждый элемент по отдельности с правильным значением. Для компилятора намного сложнее оптимизировать работу, чем когда вы делаете то же самое с массивом. Именно поэтому вектор предоставляет конструктор, который позволяет сделать именно это: инициализировать N элементы со значением X.

И когда вы используете это, вектор так же быстро, как массив.

Итак, нет, вы не испортили миф производительности. Но вы показали, что это правда, только если вы используете вектор оптимально, что тоже очень хорошо.:)

С яркой стороны это самое простое использование, которое оказывается самым быстрым. Если вы сравните мой фрагмент кода (одну строку) с ответом Джона Кугельмана, содержащий кучи и кучи настроек и оптимизаций, которые до сих пор не совсем устраняют разницу в производительности, довольно ясно, что vector довольно утонченно разработан. Вам не нужно прыгать через обручи, чтобы получить скорость, равную массиву. Напротив, вы должны использовать самое простое решение.

Ответ 6

Это было едва ли справедливое сравнение, когда я впервые посмотрел на ваш код; Я определенно думал, что вы не сравниваете яблоки с яблоками. Поэтому я подумал, пусть вызовутся все конструкторы и деструкторы; и затем сравните.

const size_t dimension = 1000;

void UseArray() {
    TestTimer t("UseArray");
    for(size_t j = 0; j < dimension; ++j) {
        Pixel* pixels = new Pixel[dimension * dimension];
        for(size_t i = 0 ; i < dimension * dimension; ++i) {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = (unsigned char) (i % 255);
        }
        delete[] pixels;
    }
}

void UseVector() {
    TestTimer t("UseVector");
    for(size_t j = 0; j < dimension; ++j) {
        std::vector<Pixel> pixels(dimension * dimension);
        for(size_t i = 0; i < dimension * dimension; ++i) {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = (unsigned char) (i % 255);
        }
    }
}

int main() {
    TestTimer t1("The whole thing");

    UseArray();
    UseVector();

    return 0;
}

Мои мысли заключались в том, что с этой настройкой они должны быть точно такими же. Оказывается, я ошибался.

UseArray completed in 3.06 seconds
UseVector completed in 4.087 seconds
The whole thing completed in 10.14 seconds

Итак, почему эта 30% -ная потеря производительности даже произошла? У STL есть все в заголовках, поэтому компилятор должен был понять все, что требовалось.

Мои мысли заключались в том, что в том, как цикл инициализирует все значения для конструктора по умолчанию. Поэтому я выполнил тест:

class Tester {
public:
    static int count;
    static int count2;
    Tester() { count++; }
    Tester(const Tester&) { count2++; }
};
int Tester::count = 0;
int Tester::count2 = 0;

int main() {
    std::vector<Tester> myvec(300);
    printf("Default Constructed: %i\nCopy Constructed: %i\n", Tester::count, Tester::count2);

    return 0;
}

Результаты были, как я подозревал:

Default Constructed: 1
Copy Constructed: 300

Это, очевидно, источник замедления, тот факт, что вектор использует конструктор копирования для инициализации элементов из построенного по умолчанию объекта.

Это означает, что при построении вектора происходит следующий псевдооперационный порядок:

Pixel pixel;
for (auto i = 0; i < N; ++i) vector[i] = pixel;

Который из-за неявного конструктора копирования, созданного компилятором, расширяется до следующего:

Pixel pixel;
for (auto i = 0; i < N; ++i) {
    vector[i].r = pixel.r;
    vector[i].g = pixel.g;
    vector[i].b = pixel.b;
}

Таким образом, по умолчанию Pixel остается неинициализированным, а остальные инициализируются со значениями Pixel неинициализированных по умолчанию.

По сравнению с альтернативной ситуацией с New[]/Delete[]:

int main() {
    Tester* myvec = new Tester[300];

    printf("Default Constructed: %i\nCopy Constructed:%i\n", Tester::count, Tester::count2);

    delete[] myvec;

    return 0;
}

Default Constructed: 300
Copy Constructed: 0

Все они оставлены без их инициализированных значений и без двойной итерации по последовательности.

Вооружившись этой информацией, как мы можем ее протестировать? Попробуйте переписать конструктор неявных копий.

Pixel(const Pixel&) {}

И результаты?

UseArray completed in 2.617 seconds
UseVector completed in 2.682 seconds
The whole thing completed in 5.301 seconds

Итак, если вы делаете сотни векторов очень часто: передумайте свой алгоритм.

В любом случае реализация STL не медленнее по неизвестной причине, она просто делает именно то, что вы просите; надеясь, что вы знаете лучше.

Ответ 7

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

Ответ 8

GNU STL (и другие), заданный vector<T>(n), по умолчанию создает прототипный объект T() - компилятор оптимизирует пустой конструктор, но затем копия любого мусора оказалась в адресах памяти, которые теперь зарезервированы для объект берется STL __uninitialized_fill_n_aux, который заполняет заполняющие копии этого объекта в качестве значений по умолчанию в векторе. Итак, "мой" STL не является построением цикла, а конструирует затем цикл/копирование. Это противоречит интуитивному, но я должен был помнить, когда я прокомментировал недавний вопрос о потоке stackoverflow по этому поводу: конструкция/копия может быть более эффективной для ссылочных подсчитанных объектов и т.д.

Итак:

vector<T> x(n);

или

vector<T> x;
x.resize(n);

есть - во многих реализациях STL - что-то вроде:

T temp;
for (int i = 0; i < n; ++i)
    x[i] = temp;

Проблема в том, что текущее поколение оптимизаторов компилятора, похоже, не работает из понимания того, что temp - неинициализированный мусор, и не удается оптимизировать вызовы конструктора цикла и по умолчанию. Вы могли бы убедительно утверждать, что компиляторы абсолютно не должны оптимизировать это, поскольку программист, пишущий выше, имеет разумное ожидание, что все объекты будут идентичны после цикла, даже если мусор (обычные оговорки о "идентичном" /operator == vs memcmp/operator = и т.д.). Нельзя ожидать, что компилятор будет иметь дополнительное представление о более широком контексте std::vector < > или о более позднем использовании данных, которые могли бы предложить эту оптимизацию.

Это можно противопоставить более очевидной, прямой реализации:

for (int i = 0; i < n; ++i)
    x[i] = T();

Что мы можем ожидать от компилятора.

Чтобы быть более явным в отношении обоснования для этого аспекта векторного поведения, рассмотрите:

std::vector<big_reference_counted_object> x(10000);

Ясно, что это большая разница, если мы создаем 10000 независимых объектов по сравнению с 10000, ссылаясь на одни и те же данные. Там разумный аргумент, что преимущество защиты случайных пользователей С++ от случайного выполнения чего-то такого дорогого перегружателя - очень небольшая реальная стоимость жесткой оптимизации конструкции копирования.

ОРИГИНАЛЬНЫЙ ОТВЕТ (для справки/понимания комментариев): Без шансов. вектор так же быстро, как массив, по крайней мере, если вы зарезервируете пространство разумно....

Ответ 9

Ответ Martin York беспокоит меня, потому что это похоже на попытку очистить проблему инициализации под ковром. Но он прав, чтобы определить избыточную конструкцию по умолчанию как источник проблем с производительностью.

[EDIT: ответ Мартина больше не предлагает изменить конструктор по умолчанию.]

Для непосредственной проблемы, вы могли бы назвать 2-параметрическую версию vector<Pixel> ctor вместо:

std::vector<Pixel> pixels(dimension * dimension, Pixel(255, 0, 0));

Это работает, если вы хотите инициализировать с постоянным значением, что является обычным случаем. Но более общая проблема: Как вы можете эффективно инициализировать что-то более сложное, чем постоянное значение?

Для этого вы можете использовать back_insert_iterator, который является адаптером итератора. Вот пример с вектором int s, хотя общая идея работает так же хорошо для Pixel s:

#include <iterator>
// Simple functor return a list of squares: 1, 4, 9, 16...
struct squares {
    squares() { i = 0; }
    int operator()() const { ++i; return i * i; }

private:
    int i;
};

...

std::vector<int> v;
v.reserve(someSize);     // To make insertions efficient
std::generate_n(std::back_inserter(v), someSize, squares());

В качестве альтернативы вы можете использовать copy() или transform() вместо generate_n().

Недостатком является то, что логика построения исходных значений должна быть перенесена в отдельный класс, что менее удобно, чем наличие на месте (хотя lambdas в С++ 1x делает это намного приятнее). Также я ожидаю, что это будет не так быстро, как версия, отличная от malloc(), но не ожидающая STL, но я ожидаю, что она будет близка, поскольку для каждого элемента она имеет только одну конструкцию.

Ответ 10

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

Каждый из них запускает почти миллион запусков ctor, которые вы выбираете.

edit: тогда есть внешний контур 1... 1000, поэтому сделайте так, чтобы звонил миллиард ctor!

edit 2: было бы интересно увидеть разбор для случая UseArray. Оптимизатор может оптимизировать все это, поскольку он не имеет никакого эффекта, кроме как сжигания процессора.

Ответ 11

Здесь, как работает метод push_back в векторе:

  • Вектор выделяет X объем пространства, когда он инициализируется.
  • Как указано ниже, он проверяет, существует ли место в текущем базовом массиве для элемента.
  • Он делает копию элемента в вызове push_back.

После вызова push_back X элементов:

  1. Вектор перераспределяет kX объем пространства во 2-й массив.
  2. Он копирует записи первого массива на второй.
  3. Отбрасывает первый массив.
  4. Теперь используется второй массив в качестве хранилища, пока он не достигнет записей kX.

Повторить. Если вы не reserving, то это определенно будет медленнее. Более того, если вам дорого копировать элемент, тогда "push_back", как будто, будет есть вас в живых.

Что касается элемента vector versus array, я должен согласиться с другими людьми. Запустите в выпуске, включите оптимизацию и добавьте еще несколько флагов, чтобы дружелюбные люди в Microsoft не # @% $^ вверх для ya.

Еще одна вещь, если вам не нужно изменять размер, используйте Boost.Array.

Ответ 12

Некоторые данные профилировщика (пиксель выравнивается до 32 бит):

g++ -msse3 -O3 -ftree-vectorize -g test.cpp -DNDEBUG && ./a.out
UseVector completed in 3.123 seconds
UseArray completed in 1.847 seconds
UseVectorPushBack completed in 9.186 seconds
The whole thing completed in 14.159 seconds

Л

[email protected]:~$ opannotate --source libcchem/src/a.out  | grep "Total samples for file" -A3
Overflow stats not available
 * Total samples for file : "/usr/include/c++/4.4/ext/new_allocator.h"
 *
 * 141008 52.5367
 */
--
 * Total samples for file : "/home/andrey/libcchem/src/test.cpp"
 *
 *  61556 22.9345
 */
--
 * Total samples for file : "/usr/include/c++/4.4/bits/stl_vector.h"
 *
 *  41956 15.6320
 */
--
 * Total samples for file : "/usr/include/c++/4.4/bits/stl_uninitialized.h"
 *
 *  20956  7.8078
 */
--
 * Total samples for file : "/usr/include/c++/4.4/bits/stl_construct.h"
 *
 *   2923  1.0891
 */

В allocator:

               :      // _GLIBCXX_RESOLVE_LIB_DEFECTS
               :      // 402. wrong new expression in [some_] allocator::construct
               :      void
               :      construct(pointer __p, const _Tp& __val)
141008 52.5367 :      { ::new((void *)__p) _Tp(__val); }

vector:

               :void UseVector()
               :{ /* UseVector() total:  60121 22.3999 */
...
               :
               :
 10790  4.0201 :        for (int i = 0; i < dimension * dimension; ++i) {
               :
   495  0.1844 :            pixels[i].r = 255;
               :
 12618  4.7012 :            pixels[i].g = 0;
               :
  2253  0.8394 :            pixels[i].b = 0;
               :
               :        }

массив

               :void UseArray()
               :{ /* UseArray() total:  35191 13.1114 */
               :
...
               :
   136  0.0507 :        for (int i = 0; i < dimension * dimension; ++i) {
               :
  9897  3.6874 :            pixels[i].r = 255;
               :
  3511  1.3081 :            pixels[i].g = 0;
               :
 21647  8.0652 :            pixels[i].b = 0;

Большая часть служебных данных находится в конструкторе копирования. Например,

    std::vector < Pixel > pixels;//(dimension * dimension, Pixel());

    pixels.reserve(dimension * dimension);

    for (int i = 0; i < dimension * dimension; ++i) {

        pixels[i].r = 255;

        pixels[i].g = 0;

        pixels[i].b = 0;
    }

Он имеет ту же производительность, что и массив.

Ответ 13

Мой ноутбук Lenova G770 (4 и GB RAM).

ОС - это Windows 7 64-разрядная (одна с ноутбуком)

Компилятор MinGW 4.6.1.

IDE Code::Blocks.

Я тестирую исходные коды первого сообщения.

Результаты

Оптимизация O2

UseArray завершен за 2.841 секунд

UseVector завершен за 2,548 секунды

UseVectorPushBack завершен за 11.95 секунд

Всё завершено за 17.342 секунды

системная пауза

Оптимизация O3

UseArray завершен за 1.452 секунды

UseVector завершен за 2,514 секунды

UseVectorPushBack завершен за 12.967 секунд

Всё завершено за 16.937 секунд

Похоже, что производительность вектора хуже при оптимизации O3.

Если вы измените цикл на

    pixels[i].r = i;
    pixels[i].g = i;
    pixels[i].b = i;

Скорость массива и вектора под O2 и O3 почти одинакова.

Ответ 14

Лучший тест (я думаю...), компилятор из-за оптимизации может изменить код, потому что результаты выделенных векторов/массивов нигде не используются. Результаты:

$ g++ test.cpp -o test -O3 -march=native
$ ./test 
UseArray inner completed in 0.652 seconds
UseArray completed in 0.773 seconds
UseVector inner completed in 0.638 seconds
UseVector completed in 0.757 seconds
UseVectorPushBack inner completed in 6.732 seconds
UseVectorPush completed in 6.856 seconds
The whole thing completed in 8.387 seconds

Компилятор:

gcc version 6.2.0 20161019 (Debian 6.2.0-9)

Процессор:

model name  : Intel(R) Core(TM) i7-3630QM CPU @ 2.40GHz

И код:

#include <cstdlib>
#include <vector>

#include <iostream>
#include <string>

#include <boost/date_time/posix_time/ptime.hpp>
#include <boost/date_time/microsec_time_clock.hpp>

class TestTimer
{
    public:
        TestTimer(const std::string & name) : name(name),
            start(boost::date_time::microsec_clock<boost::posix_time::ptime>::local_time())
        {
        }

        ~TestTimer()
        {
            using namespace std;
            using namespace boost;

            posix_time::ptime now(date_time::microsec_clock<posix_time::ptime>::local_time());
            posix_time::time_duration d = now - start;

            cout << name << " completed in " << d.total_milliseconds() / 1000.0 <<
                " seconds" << endl;
        }

    private:
        std::string name;
        boost::posix_time::ptime start;
};

struct Pixel
{
    Pixel()
    {
    }

    Pixel(unsigned char r, unsigned char g, unsigned char b) : r(r), g(g), b(b)
    {
    }

    unsigned char r, g, b;
};

void UseVector(std::vector<std::vector<Pixel> >& results)
{
    TestTimer t("UseVector inner");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel>& pixels = results.at(i);
        pixels.resize(dimension * dimension);

        for(int i = 0; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }
    }
}

void UseVectorPushBack(std::vector<std::vector<Pixel> >& results)
{
    TestTimer t("UseVectorPushBack inner");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel>& pixels = results.at(i);
            pixels.reserve(dimension * dimension);

        for(int i = 0; i < dimension * dimension; ++i)
            pixels.push_back(Pixel(255, 0, 0));
    }
}

void UseArray(Pixel** results)
{
    TestTimer t("UseArray inner");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        Pixel * pixels = (Pixel *)malloc(sizeof(Pixel) * dimension * dimension);

        results[i] = pixels;

        for(int i = 0 ; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }

        // free(pixels);
    }
}

void UseArray()
{
    TestTimer t("UseArray");
    Pixel** array = (Pixel**)malloc(sizeof(Pixel*)* 1000);
    UseArray(array);
    for(int i=0;i<1000;++i)
        free(array[i]);
    free(array);
}

void UseVector()
{
    TestTimer t("UseVector");
    {
        std::vector<std::vector<Pixel> > vector(1000, std::vector<Pixel>());
        UseVector(vector);
    }
}

void UseVectorPushBack()
{
    TestTimer t("UseVectorPush");
    {
        std::vector<std::vector<Pixel> > vector(1000, std::vector<Pixel>());
        UseVectorPushBack(vector);
    }
}


int main()
{
    TestTimer t1("The whole thing");

    UseArray();
    UseVector();
    UseVectorPushBack();

    return 0;
}

Ответ 15

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

Это мой двойной загрузочный автомат i7-3770, 16GB Ram, x86_64, на Windows 8.1 и Ubuntu 16.04. Дополнительная информация и выводы, замечания ниже. Протестированы как MSVS 2017, так и g++ (как в Windows, так и в Linux).

Программа испытаний

#include <iostream>
#include <chrono>
//#include <algorithm>
#include <array>
#include <locale>
#include <vector>
#include <queue>
#include <deque>

// Note: total size of array must not exceed 0x7fffffff B = 2,147,483,647B
//  which means that largest int array size is 536,870,911
// Also image size cannot be larger than 80,000,000B
constexpr int long g_size = 100000;
int g_A[g_size];


int main()
{
    std::locale loc("");
    std::cout.imbue(loc);
    constexpr int long size = 100000;  // largest array stack size

    // stack allocated c array
    std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
    int A[size];
    for (int i = 0; i < size; i++)
        A[i] = i;

    auto duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "c-style stack array duration=" << duration / 1000.0 << "ms\n";
    std::cout << "c-style stack array size=" << sizeof(A) << "B\n\n";

    // global stack c array
    start = std::chrono::steady_clock::now();
    for (int i = 0; i < g_size; i++)
        g_A[i] = i;

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "global c-style stack array duration=" << duration / 1000.0 << "ms\n";
    std::cout << "global c-style stack array size=" << sizeof(g_A) << "B\n\n";

    // raw c array heap array
    start = std::chrono::steady_clock::now();
    int* AA = new int[size];    // bad_alloc() if it goes higher than 1,000,000,000
    for (int i = 0; i < size; i++)
        AA[i] = i;

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "c-style heap array duration=" << duration / 1000.0 << "ms\n";
    std::cout << "c-style heap array size=" << sizeof(AA) << "B\n\n";
    delete[] AA;

    // std::array<>
    start = std::chrono::steady_clock::now();
    std::array<int, size> AAA;
    for (int i = 0; i < size; i++)
        AAA[i] = i;
    //std::sort(AAA.begin(), AAA.end());

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "std::array duration=" << duration / 1000.0 << "ms\n";
    std::cout << "std::array size=" << sizeof(AAA) << "B\n\n";

    // std::vector<>
    start = std::chrono::steady_clock::now();
    std::vector<int> v;
    for (int i = 0; i < size; i++)
        v.push_back(i);
    //std::sort(v.begin(), v.end());

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "std::vector duration=" << duration / 1000.0 << "ms\n";
    std::cout << "std::vector size=" << v.size() * sizeof(v.back()) << "B\n\n";

    // std::deque<>
    start = std::chrono::steady_clock::now();
    std::deque<int> dq;
    for (int i = 0; i < size; i++)
        dq.push_back(i);
    //std::sort(dq.begin(), dq.end());

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "std::deque duration=" << duration / 1000.0 << "ms\n";
    std::cout << "std::deque size=" << dq.size() * sizeof(dq.back()) << "B\n\n";

    // std::queue<>
    start = std::chrono::steady_clock::now();
    std::queue<int> q;
    for (int i = 0; i < size; i++)
        q.push(i);

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "std::queue duration=" << duration / 1000.0 << "ms\n";
    std::cout << "std::queue size=" << q.size() * sizeof(q.front()) << "B\n\n";
}

Результаты

//////////////////////////////////////////////////////////////////////////////////////////
// with MSVS 2017:
// >> cl /std:c++14 /Wall -O2 array_bench.cpp
//
// c-style stack array duration=0.15ms
// c-style stack array size=400,000B
//
// global c-style stack array duration=0.130ms
// global c-style stack array size=400,000B
//
// c-style heap array duration=0.90ms
// c-style heap array size=4B
//
// std::array duration=0.20ms
// std::array size=400,000B
//
// std::vector duration=0.544ms
// std::vector size=400,000B
//
// std::deque duration=1.375ms
// std::deque size=400,000B
//
// std::queue duration=1.491ms
// std::queue size=400,000B
//
//////////////////////////////////////////////////////////////////////////////////////////
//
// with g++ version:
//      - (tdm64-1) 5.1.0 on Windows
//      - (Ubuntu 5.4.0-6ubuntu1~16.04.10) 5.4.0 20160609 on Ubuntu 16.04
// >> g++ -std=c++14 -Wall -march=native -O2 array_bench.cpp -o array_bench
//
// c-style stack array duration=0ms
// c-style stack array size=400,000B
//
// global c-style stack array duration=0.124ms
// global c-style stack array size=400,000B
//
// c-style heap array duration=0.648ms
// c-style heap array size=8B
//
// std::array duration=1ms
// std::array size=400,000B
//
// std::vector duration=0.402ms
// std::vector size=400,000B
//
// std::deque duration=0.234ms
// std::deque size=400,000B
//
// std::queue duration=0.304ms
// std::queue size=400,000
//
//////////////////////////////////////////////////////////////////////////////////////////

Заметки

  • Собрано в среднем 10 прогонов.
  • Я изначально выполнял тесты с помощью std::sort() тоже (вы можете видеть, что он закомментирован), но удалил их позже, потому что не было существенных относительных различий.

Мои выводы и замечания

  • обратите внимание, как глобальный массив c-style занимает почти столько же времени, сколько массив кучи c-style
  • Из всех тестов я заметил замечательную стабильность в вариациях времени std::array между последовательными прогонами, в то время как другие, особенно std :: data structs, дико варьировались в сравнении
  • Оптимизация O3 не показала каких-либо примечательных временных разниц
  • Удаление оптимизации в Windows cl (no -O2) и на g++ (Win/Linux no -O2, no -march = native) увеличивает время ЗНАЧИТЕЛЬНО. В частности, для std :: data structs. Общее время выше на MSVS, чем g++, но std::array и c-style массивы быстрее в Windows без оптимизации
  • g++ создает более быстрый код, чем компилятор microsoft (видимо, он работает быстрее даже в Windows).

решение суда

Конечно, это код для оптимизированной сборки. И так как вопрос был о std::vector то да, это! Много! медленнее, чем простые массивы (оптимизированные/неоптимизированные). Но когда вы делаете бенчмарк, вы, естественно, хотите создать оптимизированный код.

Звезда шоу для меня, хотя была std::array.

Ответ 16

Ну, потому что vector:: resize() выполняет гораздо большую обработку, чем простое распределение памяти (по malloc).

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

Ответ 17

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

Ответ 18

Кстати, замедление вашего видения в классах с использованием вектора также происходит со стандартными типами, такими как int. Имеет многопоточный код:

#include <iostream>
#include <cstdio>
#include <map>
#include <string>
#include <typeinfo>
#include <vector>
#include <pthread.h>
#include <sstream>
#include <fstream>
using namespace std;

//pthread_mutex_t map_mutex=PTHREAD_MUTEX_INITIALIZER;

long long num=500000000;
int procs=1;

struct iterate
{
    int id;
    int num;
    void * member;
    iterate(int a, int b, void *c) : id(a), num(b), member(c) {}
};

//fill out viterate and piterate
void * viterate(void * input)
{
    printf("am in viterate\n");
    iterate * info=static_cast<iterate *> (input);
    // reproduce member type
    vector<int> test= *static_cast<vector<int>*> (info->member);
    for (int i=info->id; i<test.size(); i+=info->num)
    {
        //printf("am in viterate loop\n");
        test[i];
    }
    pthread_exit(NULL);
}

void * piterate(void * input)
{
    printf("am in piterate\n");
    iterate * info=static_cast<iterate *> (input);;
    int * test=static_cast<int *> (info->member);
    for (int i=info->id; i<num; i+=info->num) {
        //printf("am in piterate loop\n");
        test[i];
    }
    pthread_exit(NULL);
}

int main()
{
    cout<<"producing vector of size "<<num<<endl;
    vector<int> vtest(num);
    cout<<"produced  a vector of size "<<vtest.size()<<endl;
    pthread_t thread[procs];

    iterate** it=new iterate*[procs];
    int ans;
    void *status;

    cout<<"begining to thread through the vector\n";
    for (int i=0; i<procs; i++) {
        it[i]=new iterate(i, procs, (void *) &vtest);
    //  ans=pthread_create(&thread[i],NULL,viterate, (void *) it[i]);
    }
    for (int i=0; i<procs; i++) {
        pthread_join(thread[i], &status);
    }
    cout<<"end of threading through the vector";
    //reuse the iterate structures

    cout<<"producing a pointer with size "<<num<<endl;
    int * pint=new int[num];
    cout<<"produced a pointer with size "<<num<<endl;

    cout<<"begining to thread through the pointer\n";
    for (int i=0; i<procs; i++) {
        it[i]->member=&pint;
        ans=pthread_create(&thread[i], NULL, piterate, (void*) it[i]);
    }
    for (int i=0; i<procs; i++) {
        pthread_join(thread[i], &status);
    }
    cout<<"end of threading through the pointer\n";

    //delete structure array for iterate
    for (int i=0; i<procs; i++) {
        delete it[i];
    }
    delete [] it;

    //delete pointer
    delete [] pint;

    cout<<"end of the program"<<endl;
    return 0;
}

Поведение из кода показывает, что создание вектора является самой длинной частью кода. Как только вы пройдете через эту горло бутылки. Остальная часть кода работает очень быстро. Это верно независимо от того, сколько потоков вы используете.

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

Ответ 19

Я просто хочу упомянуть, что vector (и smart_ptr) - это всего лишь тонкий слой, добавляемый поверх необработанных массивов (и необработанных указателей). И на самом деле время доступа вектора в непрерывной памяти быстрее, чем массив. Следующий код показывает результат инициализации и доступа к вектору и массиву.

#include <boost/date_time/posix_time/posix_time.hpp>
#include <iostream>
#include <vector>
#define SIZE 20000
int main() {
    srand (time(NULL));
    vector<vector<int>> vector2d;
    vector2d.reserve(SIZE);
    int index(0);
    boost::posix_time::ptime start_total = boost::posix_time::microsec_clock::local_time();
    //  timer start - build + access
    for (int i = 0; i < SIZE; i++) {
        vector2d.push_back(vector<int>(SIZE));
    }
    boost::posix_time::ptime start_access = boost::posix_time::microsec_clock::local_time();
    //  timer start - access
    for (int i = 0; i < SIZE; i++) {
        index = rand()%SIZE;
        for (int j = 0; j < SIZE; j++) {

            vector2d[index][index]++;
        }
    }
    boost::posix_time::ptime end = boost::posix_time::microsec_clock::local_time();
    boost::posix_time::time_duration msdiff = end - start_total;
    cout << "Vector total time: " << msdiff.total_milliseconds() << "milliseconds.\n";
    msdiff = end - start_acess;
    cout << "Vector access time: " << msdiff.total_milliseconds() << "milliseconds.\n"; 


    int index(0);
    int** raw2d = nullptr;
    raw2d = new int*[SIZE];
    start_total = boost::posix_time::microsec_clock::local_time();
    //  timer start - build + access
    for (int i = 0; i < SIZE; i++) {
        raw2d[i] = new int[SIZE];
    }
    start_access = boost::posix_time::microsec_clock::local_time();
    //  timer start - access
    for (int i = 0; i < SIZE; i++) {
        index = rand()%SIZE;
        for (int j = 0; j < SIZE; j++) {

            raw2d[index][index]++;
        }
    }
    end = boost::posix_time::microsec_clock::local_time();
    msdiff = end - start_total;
    cout << "Array total time: " << msdiff.total_milliseconds() << "milliseconds.\n";
    msdiff = end - start_acess;
    cout << "Array access time: " << msdiff.total_milliseconds() << "milliseconds.\n"; 
    for (int i = 0; i < SIZE; i++) {
        delete [] raw2d[i];
    }
    return 0;
}

Вывод:

    Vector total time: 925milliseconds.
    Vector access time: 4milliseconds.
    Array total time: 30milliseconds.
    Array access time: 21milliseconds.

Таким образом, скорость будет почти такой же, если вы правильно ее используете. (как другие упоминаются с использованием reserve() или resize()).

Ответ 20

Я должен сказать, что я не эксперт на С++. Но чтобы добавить некоторые результаты экспериментов:

компиляции:   gcc-6.2.0/bin/g++ -O3 -std = С++ 14 vector.cpp

машина:

Intel(R) Xeon(R) CPU E5-2690 v2 @ 3.00GHz 

ОС:

2.6.32-642.13.1.el6.x86_64

Выход:

UseArray completed in 0.167821 seconds
UseVector completed in 0.134402 seconds
UseConstructor completed in 0.134806 seconds
UseFillConstructor completed in 1.00279 seconds
UseVectorPushBack completed in 6.6887 seconds
The whole thing completed in 8.12888 seconds

Здесь единственное, что мне кажется странным, это производительность "UseFillConstructor" по сравнению с "UseConstructor".

Код:

void UseConstructor()
{
    TestTimer t("UseConstructor");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel> pixels(dimension*dimension);
        for(int i = 0; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }
    }
}


void UseFillConstructor()
{
    TestTimer t("UseFillConstructor");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel> pixels(dimension*dimension, Pixel(255,0,0));
    }
}

Таким образом, дополнительное "значение" обеспечивает очень низкую производительность, что, по-моему, связано с множественным вызовом для копирования конструктора. Но...

Compile:

gcc-6.2.0/bin/g++ -std=c++14 -O vector.cpp

Вывод:

UseArray completed in 1.02464 seconds
UseVector completed in 1.31056 seconds
UseConstructor completed in 1.47413 seconds
UseFillConstructor completed in 1.01555 seconds
UseVectorPushBack completed in 6.9597 seconds
The whole thing completed in 11.7851 seconds

Итак, в этом случае gcc-оптимизация очень важна, но она не может помочь вам, когда значение предоставляется по умолчанию. Это, на самом деле, против моего обучения. Надеюсь, это поможет новому программисту при выборе формата векторной инициализации.

Ответ 21

std::vector если вы вставляете данные внутри него. Он перераспределит материал с большей памятью. Процесс распределения и освобождения занимает больше времени.

С другой стороны, простые массивы просто хранят данные внутри них и не освобождают или не перераспределяют себя.

ПРИМЕЧАНИЕ. Информация может быть неправильной. Я получил эту информацию где-нибудь в Кворе.

Ответ 22

Кажется, это зависит от флагов компилятора. Вот эталонный код:

#include <chrono>
#include <cmath>
#include <ctime>
#include <iostream>
#include <vector>


int main(){

    int size = 1000000; // reduce this number in case your program crashes
    int L = 10;

    std::cout << "size=" << size << " L=" << L << std::endl;
    {
        srand( time(0) );
        double * data = new double[size];
        double result = 0.;
        std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
        for( int l = 0; l < L; l++ ) {
            for( int i = 0; i < size; i++ ) data[i] = rand() % 100;
            for( int i = 0; i < size; i++ ) result += data[i] * data[i];
        }
        std::chrono::steady_clock::time_point end   = std::chrono::steady_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
        std::cout << "Calculation result is " << sqrt(result) << "\n";
        std::cout << "Duration of C style heap array:    " << duration << "ms\n";
        delete data;
    }

    {
        srand( 1 + time(0) );
        double data[size]; // technically, non-compliant with C++ standard.
        double result = 0.;
        std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
        for( int l = 0; l < L; l++ ) {
            for( int i = 0; i < size; i++ ) data[i] = rand() % 100;
            for( int i = 0; i < size; i++ ) result += data[i] * data[i];
        }
        std::chrono::steady_clock::time_point end   = std::chrono::steady_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
        std::cout << "Calculation result is " << sqrt(result) << "\n";
        std::cout << "Duration of C99 style stack array: " << duration << "ms\n";
    }

    {
        srand( 2 + time(0) );
        std::vector<double> data( size );
        double result = 0.;
        std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
        for( int l = 0; l < L; l++ ) {
            for( int i = 0; i < size; i++ ) data[i] = rand() % 100;
            for( int i = 0; i < size; i++ ) result += data[i] * data[i];
        }
        std::chrono::steady_clock::time_point end   = std::chrono::steady_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
        std::cout << "Calculation result is " << sqrt(result) << "\n";
        std::cout << "Duration of std::vector array:     " << duration << "ms\n";
    }

    return 0;
}

Различные флаги оптимизации дают разные ответы:

$ g++ -O0 benchmark.cpp 
$ ./a.out 
size=1000000 L=10
Calculation result is 181182
Duration of C style heap array:    118441ms
Calculation result is 181240
Duration of C99 style stack array: 104920ms
Calculation result is 181210
Duration of std::vector array:     124477ms
$g++ -O3 benchmark.cpp
$ ./a.out 
size=1000000 L=10
Calculation result is 181213
Duration of C style heap array:    107803ms
Calculation result is 181198
Duration of C99 style stack array: 87247ms
Calculation result is 181204
Duration of std::vector array:     89083ms
$ g++ -Ofast benchmark.cpp 
$ ./a.out 
size=1000000 L=10
Calculation result is 181164
Duration of C style heap array:    93530ms
Calculation result is 181179
Duration of C99 style stack array: 80620ms
Calculation result is 181191
Duration of std::vector array:     78830ms

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