1

Тема: Швидкість різноманітних операцій ініціалізації вектора і С-масива

Провів тест, ініціалізуючи вектор кількома способами і С-масив. Результати дивні. Наводжу усі необхідні сирцеві коди і результати.

Measure.h

#pragma once

#include <chrono>

template<typename TimeT = std::chrono::milliseconds>
struct Measure
{
    template<typename F, typename ...Args>
    static typename TimeT::rep execution(F func, Args&&... args)
    {
        auto start = std::chrono::system_clock::now();
        func(std::forward<Args>(args)...);
        auto duration = std::chrono::duration_cast< TimeT>
            (std::chrono::system_clock::now() - start);
        return duration.count();
    }
};

PerformanceTest.h

#pragma once

class PerformanceTest
{
private:
    int _setsCount = 1000;
    int _elemsCount = 1000;
public:
    PerformanceTest() = default;
    PerformanceTest(int setsCount, int elemsCount) : _setsCount(setsCount), _elemsCount(elemsCount) {}

    void Test();
private:
    void TestVectorPushBack();
    void TestVectorEmplaceBack();
    void TestVector();
    void TestArray();
};

PerformanceTest.cpp

#include "stdafx.h"

#include "PerformanceTest.h"

#include "Utils\Measure.h"

#include <iostream>
#include <vector>
#include <memory>
using namespace std;

namespace
{
    struct TestClass
    {
        TestClass(int i, int j, int k);
        TestClass(const TestClass& other);
        int _i;
        int _j;
        int _k;
    };
    __declspec(noinline) TestClass::TestClass(int i, int j, int k) : _i(i), _j(j), _k(k) {}
    __declspec(noinline) TestClass::TestClass(const TestClass& other) { _i = other._i; _j = other._j; _k = other._k; }
}


void PerformanceTest::Test()
{
    cout << Measure<>::execution([](PerformanceTest *c) { c->TestVectorPushBack(); }, this) << endl;
    cout << Measure<>::execution([](PerformanceTest *c) { c->TestVectorEmplaceBack(); }, this) << endl;
    cout << Measure<>::execution([](PerformanceTest *c) { c->TestVector(); }, this) << endl;
    cout << Measure<>::execution([](PerformanceTest *c) { c->TestArray(); }, this) << endl;
}

void PerformanceTest::TestVectorPushBack()
{
    for (int i = 0; i < _setsCount; i++)
    {
        vector<TestClass> v;
        v.reserve(_elemsCount);
        for (int i = 0; i < _elemsCount; i++)
        {
            v.push_back(TestClass(1, 2, 3));
        }
    }
}

void PerformanceTest::TestVectorEmplaceBack()
{
    for (int i = 0; i < _setsCount; i++)
    {
        vector<TestClass> v;
        v.reserve(_elemsCount);
        for (int i = 0; i < _elemsCount; i++)
        {
            v.emplace_back(1, 2, 3);
        }
    }
}

void PerformanceTest::TestVector()
{
    for (int i = 0; i < _setsCount; i++)
    {
        vector<TestClass> v(_elemsCount, TestClass(1, 2, 3));
    }
}

void PerformanceTest::TestArray()
{
    for (int i = 0; i < _setsCount; i++)
    {
        void* raw_testClass = operator new[](_elemsCount * sizeof(TestClass));
        TestClass *tc = static_cast<TestClass*>(raw_testClass);
        for (int i = 0; i < _elemsCount; i++)
        {
            tc[i] = TestClass(1, 2, 3);
        }

        operator delete[](raw_testClass);
    }
}
    PerformanceTest test;
    test.Test();

Результати написав:

PushBack - 576
EmplaceBack - 672
Vector - 254
C-array - 36

Така жахлива перевага у C-масива. Але те, що emplace гірше ніж push - дуже дивно, адже emplace уникає копіювання. Вектор від С-масива відрізняється лише тим, що вектор копіює наданий йому елемент, а масив постійно створює його. Хтось може пояснити?

2 Востаннє редагувалося 0x9111A (02.07.2015 13:08:09)

Re: Швидкість різноманітних операцій ініціалізації вектора і С-масива

А оптимізація включена?

Debug:
828
994
453
35

Release:
10
7
5
9

Певно краще запустити тест багато разів і порахувати середній час для більшої достовірності, бо в мене, наприклад, результати помітно різняться при кожному запуску

Подякували: Yola1

3

Re: Швидкість різноманітних операцій ініціалізації вектора і С-масива

Я використовую VC++. Спробував вимкнути перевірки для STL.

#ifdef _SECURE_SCL
#undef _SECURE_SCL
#endif
#define _SECURE_SCL 0

І з ними має такі результати. Схоже, що не подіяло.

І дійсно, з Full Optimization (Conf. Properties -> C/C++ -> Optimization) маю ваші результати, тут вже С-масив лагає.

4

Re: Швидкість різноманітних операцій ініціалізації вектора і С-масива

Масив лагає через виклики "operator new[]" тоді як для вектора в TestVector() пам’ять виділяється на стеці

5

Re: Швидкість різноманітних операцій ініціалізації вектора і С-масива

Розглянемо такий код:

void f()
{
    int i0;
    array<int, 100> a;
    int i1;
    vector<int> v(100);
    int i2;
}

Перед виходом з функції подивимось на адреси наших локальних змінних:
+        &i0    0x00c8e820 {-858993460}    int *
+        &a    0x00c8e688 { size=100 }    std::array<int,100> *
+        &i1    0x00c8e67c {-858993460}    int *
+        &v    0x00c8e664 { size=100 }    std::vector<int,std::allocator<int> > *
+        &i2    0x00c8e658 {-858993460}    int *

З різниць адрес видно, що array на стеці, а vector у купі. Адже vector - це динамічний масив.

Щодо new. Я виділяю пам'ять лише один раз. Потім я використовую placement new, який не виділяє пам'ять, а лише конструює об'єкт у вказаному місці.

6 Востаннє редагувалося 0x9111A (02.07.2015 18:50:09)

Re: Швидкість різноманітних операцій ініціалізації вектора і С-масива

Якщо подивитись на адресу вектора і його елемента в циклі то видно що пам’ять під нього не виділяється кожен раз.

void PerformanceTest::TestVector()
{
    int a;
    for (int i = 0; i < _setsCount; i++)
    {
        vector<TestClass> v(_elemsCount, TestClass(1, 2, 3));
        printf("%x %x %x\n", &v, &v[0], new int(1));
    }
}

4efc20 5bffd8 5c51a8
4efc20 5bffd8 5c51b8
4efc20 5bffd8 5c51c8
4efc20 5bffd8 5c51d8
4efc20 5bffd8 5c51e8
...

Щодо placement new то і правда працює, незвичний синтаксис

7

Re: Швидкість різноманітних операцій ініціалізації вектора і С-масива

Yola написав:

З різниць адрес видно, що array на стеці, а vector у купі. Адже vector - це динамічний масив.

Вам видно те, що Вам хотілось би побачити. З std::array<> не доводилось мати справу ніколи, але вектор - то відома річ. Пам'ять під вектор - у стеці. Там, де Ви його розташували. Я маю на увазі 4 слова, які є Ваш вектор (якщо сумнів - подивіться, чому дорівнює sizeof(std::vector<int>)). А вже пам'ять для елементів вектора буде, звичайно, виділена в купі (вона і не може знаходитись у стеці).

8

Re: Швидкість різноманітних операцій ініціалізації вектора і С-масива

0x9111A написав:

Якщо подивитись на адресу вектора і його елемента в циклі то видно що пам’ять під нього не виділяється кожен раз.

Звичайно! Він же в стеці!

9 Востаннє редагувалося 0x9111A (02.07.2015 19:02:56)

Re: Швидкість різноманітних операцій ініціалізації вектора і С-масива

mich_retten написав:

Звичайно! Він же в стеці!

Я про то пану Yoli й кажу

10 Востаннє редагувалося Yola (06.07.2015 18:58:18)

Re: Швидкість різноманітних операцій ініціалізації вектора і С-масива

Ну ви даєте, невже не зрозуміло, що "array на стеці, а vector у купі" має на увазі де їх елементи розташовані. Як діти малі, їй-бо!

0x9111A написав:
mich_retten написав:

Звичайно! Він же в стеці!

Я про то пану Yoli й кажу

Тю, а це я взагалі не добрав. Його елементи в купі, для нього теж викликається оператор new. І, за потреби, багато разів.

EDIT:
Пора на мене вже адміністратора викликати.