Влияние на производительность при изменении размера вектора в пределах емкости - PullRequest
0 голосов
/ 13 февраля 2020

У меня есть следующий синтезированный пример моего кода:

#include <vector>
#include <array>
#include <cstdlib>

#define CAPACITY 10000

int main() {
    std::vector<std::vector<int>> a;
    std::vector<std::array<int, 2>> b;

    a.resize(CAPACITY, std::vector<int> {0, 0})
    b.resize(CAPACITY, std::array<int, 2> {0, 0})

    for (;;) {
        size_t new_rand_size = (std::rand() % CAPACITY);

        a.resize(new_rand_size);
        b.resize(new_rand_size);

        for (size_t i = 0; i < new_rand_size; ++i) {
            a[i][0] = std::rand();
            a[i][1] = std::rand();
            b[i][0] = std::rand();
            b[i][1] = std::rand();
        }

        process(a); // respectively process(b)
    }
}

, поэтому очевидно, что версия массива лучше, потому что она требует меньшего выделения, так как массив фиксирован по размеру и непрерывен в памяти (правильно ?). Он просто переинициализируется при повторном повышении размера в пределах емкости.

Поскольку я все равно собираюсь перезаписать, мне было интересно, есть ли способ пропустить инициализацию (например, перезаписать распределитель или подобное) для оптимизации кода еще дальше.

Ответы [ 2 ]

1 голос
/ 13 февраля 2020

так очевидно,

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

версия массива лучше, потому что она требует меньшего выделения, так как массив фиксирован по размеру и непрерывен в памяти (правильно?).

Правда этого зависит от реализации, но есть некоторая действительность здесь. Я бы go использовал менее микроуправляемый подход и сказал бы, что версия массива предпочтительна , потому что окончательный размер фиксирован. Использование инструмента, предназначенного для вашей специализированной ситуации (массив фиксированного размера), приводит к меньшим накладным расходам, чем использование инструмента для более общей ситуации. Однако не всегда меньше.

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

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

Заключительное примечание: «непрерывный» немного более точный / описательный, чем «непрерывный».

Он просто переинициализируется при повторном повышении размера в пределах емкости.

Это фактор, который влияет на оба подхода. Фактически это приводит к тому, что ваш код демонстрирует неопределенное поведение. Например, предположим, что ваша первая итерация изменяет размер внешнего вектора до 1, а вторая - до 5. Сравните то, что делает ваш код, со следующим:

std::vector<std::vector<int>> a;
a.resize(CAPACITY, std::vector<int> {0, 0});
a.resize(1);
a.resize(5);
std::cout << "Size " << a[1].size() <<".\n";

Вывод показывает, что размер равен нулю на этом этапе ваш код назначит значение a[1][0]. Если вы хотите, чтобы каждый элемент a по умолчанию представлял собой вектор из 2 элементов, вам нужно указывать это значение по умолчанию каждый раз, когда вы изменяете размер a, а не только изначально.

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

Да, вы можете пропустить инициализацию. На самом деле это целесообразно сделать. Используйте инструмент, разработанный для поставленной задачи. Ваша инициализация служит для увеличения емкости ваших векторов. Поэтому используйте метод, единственной целью которого является увеличение емкости вектора: vector::reserve.

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

1 голос
/ 13 февраля 2020

Поскольку я все равно собираюсь перезаписать, мне было интересно, есть ли способ пропустить инициализацию

Да: не изменять размер. Вместо этого зарезервируйте емкость и вставьте новые элементы * * * * * * *

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...