std :: accumulate версия C ++ 20 - PullRequest
       110

std :: accumulate версия C ++ 20

3 голосов
/ 16 июня 2020

Я пытаюсь понять этот код, но не могу понять, почему эта версия

for (; first != last; ++first) 
    init = std::move(init) + *first;

быстрее, чем эта

for (; first != last; ++first)
    init += *first;

Я взял их из std: : накапливать. Ассемблерный код первой версии длиннее второй. Даже если первая версия создает rvalue ref для init, она всегда создает временное значение, сначала добавляя *, а затем присваивая его init, это тот же процесс во втором случае, когда он создает временное значение, а затем назначает его init. Итак, почему использование std :: move лучше, чем «добавить значение» с оператором + =?

EDIT

Я смотрел код версии C ++ 20 для накопления, и они говорят, что до накопления C ++ 20 было это

template<class InputIt, class T>
T accumulate(InputIt first, InputIt last, T init)
{
    for (; first != last; ++first) {
        init = init + *first;
    }
    return init;
}

и после C ++ 20 он становится

template<class InputIt, class T>
constexpr // since C++20
T accumulate(InputIt first, InputIt last, T init)
{
    for (; first != last; ++first) {
        init = std::move(init) + *first; // std::move since C++20
    }
    return init;
}

Я просто хотел знать, было ли при использовании std :: move какое-либо реальное улучшение или нет.

EDIT2

Хорошо, вот мой пример кода:

#include <utility>
#include <chrono>
#include <iostream>

using ck = std::chrono::high_resolution_clock;

std::string
test_no_move(std::string str) {

    std::string b = "t";
    int count = 0;

    while (++count < 100000)
        str = std::move(str) + b;   // Without std::move

    return str;
}

std::string
test_with_move(std::string str) {

    std::string b = "t";
    int count = 0;

    while (++count < 100000)        // With std::move
        str = str + b;

    return str;

}

int main()
{
    std::string result;
    auto start = ck::now();
    result = test_no_move("test");
    auto finish = ck::now();

    std::cout << "Test without std::move " << std::chrono::duration_cast<std::chrono::microseconds>(finish - start).count() << std::endl;

    start = ck::now();
    result = test_with_move("test");
    finish = ck::now();

    std::cout << "Test with std::move " << std::chrono::duration_cast<std::chrono::microseconds>(finish - start).count() << std::endl;

    return 0;
}

Если вы запустите его, вы заметите, что версия std :: move действительно быстрее, чем другая, но если вы попробуете использовать встроенные типы вы получаете версию std :: move медленнее, чем другую.

Итак, мой вопрос заключался в том, что, поскольку эта ситуация, вероятно, такая же, как и для std :: accumulate, почему они говорят, что версия накопителя C ++ 20 с std :: move быстрее, чем версия без него? Почему, используя std :: move с чем-то вроде строк, я получаю такое улучшение, но не использую что-то вроде int? Зачем все это, если в обоих случаях программа создает временную строку str + b (или std :: move (str) + b), а затем переходит на str? То есть это та же операция. Почему второй быстрее?

Спасибо за терпение. Надеюсь, на этот раз я ясно выразился.

1 Ответ

4 голосов
/ 16 июня 2020

Это потенциально быстрее для типов с нетривиальной семантикой перемещения. Рассмотрим накопление std::vector<std::string> достаточно длинных строк:

std::vector<std::string> strings(100, std::string(100, ' '));

std::string init;
init.reserve(10000);
auto r = accumulate(strings.begin(), strings.end(), std::move(init));

Для accumulate без std::move будет использоваться

std::string operator+(const std::string&, const std::string&);

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

Для accumulate с std::move будет использоваться

std::string operator+(std::string&&, const std::string&);

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

Простая демонстрация

without std::move
n_allocs = 199

with std::move
n_allocs = 0

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

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