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

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

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

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

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

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

РЕДАКТИРОВАТЬ

Я просматривал код 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;
}

и после С++ 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 какое-либо реальное улучшение или нет.

РЕДАКТИРОВАТЬ2

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

#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), а затем перемещается в строку? То есть это одна и та же операция. Почему второй быстрее?

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


person Sam    schedule 16.06.2020    source источник
comment
это зависит от типа init, если этот тип не реализует какую-либо перегрузку с помощью ссылки rvalue, этот случай будет таким же, поэтому, пожалуйста, предоставьте полный пример   -  person Alberto Sinigaglia    schedule 16.06.2020
comment
Вторая версия никогда не была в стандарте C++. std::accumulate всегда работает либо с использованием operator+(), либо с использованием параметра шаблона BinaryOperation.   -  person Ruslan    schedule 16.06.2020
comment
std::accumulate — это шаблон, поэтому необходимо выполнить несколько шагов, прежде чем вы сможете взглянуть на сборку. Можете ли вы включить минимально воспроизводимый пример?   -  person 463035818_is_not_a_number    schedule 16.06.2020
comment
@idclev463035818 нет, я оговорился, я не имел в виду, что пытался увидеть сборку шаблона, извините, я написал что-то вроде 2 переменных int и увидел разницу в сборке, выполнив a = a + b и a = std:: ход (а) + б.   -  person Sam    schedule 16.06.2020
comment
Я не эксперт по C++, пожалуйста, наберитесь терпения.   -  person Sam    schedule 16.06.2020
comment
Опубликуйте свой полный код теста и то, как вы его компилируете и запускаете.   -  person Maxim Egorushkin    schedule 16.06.2020
comment
затем включите минимально воспроизводимый пример. Как вы называете методы? Какой компилятор и какие параметры компилятора вы использовали?   -  person 463035818_is_not_a_number    schedule 16.06.2020
comment
Для встроенных типов не должно быть никакой разницы в сгенерированной сборке.   -  person Maxim Egorushkin    schedule 16.06.2020
comment
quick-bench.com подходит для сравнения производительности.   -  person Jarod42    schedule 16.06.2020


Ответы (1)


Это потенциально быстрее для типов с нетривиальной семантикой перемещения. Рассмотрим накопление 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, перемещение — это просто копирование — перемещать нечего. Для оптимизированной сборки, скорее всего, вы получите точно такой же код сборки. Если ваш бенчмаркинг показывает какое-либо улучшение/ухудшение скорости, скорее всего, вы делаете это неправильно (нет оптимизации, шума, оптимизации кода и т. д.).

person Evg    schedule 16.06.2020