Почему эта реализация суммирования векторов в rangev3 медленнее, чем эквивалент STD?

Я рассматриваю возможность использования rangev3 в своей библиотеке. Мне нравится синтаксис rangev3, но приоритетом является производительность. Библиотека выполняет множество операций умножения и сложения векторов, в основном длиной 128 отсчетов. Я использовал тест Google для оценки, например, сложения двух векторов. Версия диапазонов намного медленнее, чем версия STD (почти в 10 раз медленнее для коротких векторов). Это несколько удивительно, поскольку часто утверждается, что rangev3 (и будущие std :: range в C ++ 20) имеют хорошую производительность.

Есть ли проблема с тем, как я использую здесь rangev3? Или это как-то связано с тем, что компилятор не может хорошо развернуть код rangev3? Или прирост производительности rangev3 проявляется только для многих операций с последовательным подключением?

Примечания: назначение output = rng1; не должно выделять память, поскольку длина вектора такая же (я пробовал использовать range :: copy, но это стало в 100 раз медленнее). Я попытался предварительно инициализировать и рандомизировать векторы A и B, но не увидел разницы. Я заметил, что если бы у меня было больше операций в конвейере, разрыв между STL и ragesv3 сужался, но только для длинных векторов (более 32000 для 5 последовательных операций).

Ниже приведен автономный пример с показателями производительности. Я использую C ++ 17 LLVM libc ++ на 4-ядерном MacBook Pro i7 с флагом -O3.

#include <range/v3/all.hpp>
#include <benchmark/benchmark.h>

static void AddBenchmark(benchmark::State& state) {
  const size_t length = state.range(0);

  std::vector<double> B(length);
  std::vector<double> A(length);
  std::vector<double> output(length);
  
  while (state.KeepRunning()) {
    std::transform(A.begin( ), A.end( ), B.begin( ), output.begin(), std::plus<>( ));
    benchmark::ClobberMemory(); // Force output to be written to memory.
  }
}
BENCHMARK(AddBenchmark)->RangeMultiplier(8)->Range(1<<7, 1<<20);


static void AddRangesBenchmark(benchmark::State& state) {
  const size_t length = state.range(0);

  std::vector<double> B(length);
  std::vector<double> A(length);
  std::vector<double> output(length);
  
  while (state.KeepRunning()) {
    auto rng1 = ranges::view::transform(A, B, std::plus<>( ));
    output = ranges::to<std::vector<double>>(rng1);
    benchmark::ClobberMemory(); // Force output to be written to memory.
  }
}
BENCHMARK(AddRangesBenchmark)->RangeMultiplier(8)->Range(1<<7, 1<<20);

BENCHMARK_MAIN();

который выводит

AddBenchmark/128                 30.3 ns         30.2 ns     23194091
AddBenchmark/512                  121 ns          121 ns      5758094
AddBenchmark/4096                1917 ns         1906 ns       417300
AddBenchmark/32768              25054 ns        24795 ns        28182
AddBenchmark/262144            385913 ns       382803 ns         1718
AddBenchmark/1048576          2100095 ns      2096442 ns          328
AddRangesBenchmark/128            218 ns          218 ns      3131249
AddRangesBenchmark/512            579 ns          579 ns      1169688
AddRangesBenchmark/4096          5071 ns         5069 ns       123231
AddRangesBenchmark/32768        50702 ns        50649 ns        14382
AddRangesBenchmark/262144      482216 ns       481333 ns         1288
AddRangesBenchmark/1048576    3349331 ns      3347475 ns          200

person Enzo    schedule 07.07.2019    source источник
comment
Вы компилируете с включенной оптимизацией? Пожалуйста, опубликуйте свою строку компиляции   -  person Vittorio Romeo    schedule 08.07.2019
comment
Работает с -O3. Я использую Xcode, поэтому на самом деле у меня нет строки компиляции, но если вы спросите конкретные параметры, я могу их вам предоставить. Спасибо!   -  person Enzo    schedule 08.07.2019
comment
Я предполагаю, что диапазоны, еще не входящие в стандартную библиотеку, не получают преимуществ от некоторых хорошо протестированных настроек производительности, которые делают стандартные алгоритмы. Одна вещь, о которой я могу думать, это то, что views ленивы и поэтому не могут извлечь выгоду из векторизованных инструкций процессора, но я могу ошибаться. РЕДАКТИРОВАТЬ: @alfC - очень хороший звонок. Я предлагаю вам провести несколько тестов и, возможно, опубликовать ответ, если окажется, что это узкое место   -  person Fureeish    schedule 08.07.2019
comment
@alfC Whops! Спасибо за это. Повторите попытку с дублями и не заметили большой разницы. Редактируем вопрос сейчас.   -  person Enzo    schedule 08.07.2019
comment
@Fureeish, спасибо за это. Это тоже моя гипотеза. Однако тот факт, что операция является ленивой, не обязательно означает, что вы не можете использовать векторизованные операции. Это просто означает, что оптимизация должна происходить в строке, где фактически вызывается операция (в данном случае, в строке присваивания).   -  person Enzo    schedule 08.07.2019
comment
Я считаю, что это действительно означает, что векторизация не может использоваться тривиально. view являются ленивыми и генерируют одно значение за один вызов *, что означает, что значения не могут получить выгоду от векторизованных операций, поскольку одновременно присутствует не более одного значения. С другой стороны, я считаю, что если диапазоны принимаются, компиляторы могут (и должны) быть изменены, чтобы генерировать для них эффективный код. На данный момент я думаю, что они просто недостаточно умны, учитывая тот факт, что концентрат является новым для C++, что на самом деле довольно досадно. Кстати, какое задание? Я думаю, вы могли неправильно понять некоторые вещи.   -  person Fureeish    schedule 08.07.2019
comment
@Enzo: Я думаю, что использование этого теста в качестве эталона для просмотра диапазона просто бесполезно. Представления диапазона не подходят для тривиальных случаев. Вы вызываете простой алгоритм для каждого значения и сохраняете результаты в контейнере. Представления предназначены для составления нескольких независимых операций для построения сложных операций без необходимости хранить промежуточные результаты в контейнере. По сути, никто не должен писать версию этого кода для просмотра, поэтому нет смысла тестировать ее по сравнению с обычной версией алгоритма.   -  person Nicol Bolas    schedule 08.07.2019
comment
Что benchmark.h?   -  person einpoklum    schedule 08.07.2019
comment
@NicolBolas Спасибо за комментарий и за обновление тегов. У меня также был пример с несколькими конвейерными операциями (5 сложений и умножений), и разрыв сузился, но диапазон-v3 все еще был намного медленнее (в 2 раза) для малых размеров векторов. Я подумал о представлении здесь автономного примера только для суммирования для простоты.   -  person Enzo    schedule 08.07.2019
comment
@einpoklum Привет. Это тесты Google.   -  person Enzo    schedule 08.07.2019
comment
@Enzo: если вы не сравниваете версию представления с версией без представления, которая должна хранить промежуточные значения в контейнерах, а затем обрабатывать эти значения для следующей операции, тогда вы не используете представления по прямому назначению. Это вдвойне верно, если вы говорите об объектах с большей сложностью, чем int (например, строках или других объектах, которые выделяют память).   -  person Nicol Bolas    schedule 08.07.2019
comment
@NicolBolas В версии с несколькими операциями я действительно сравниваю версию представления с версией без представления, в которой промежуточные значения хранятся в контейнерах. Я протестировал 5 операций (3 суммы и 2 мульт), и производительность еще хуже для версии rangev3 (до длины вектора 32К).   -  person Enzo    schedule 08.07.2019
comment
@Enzo, ты обновлял тайминги? Я не понимаю, как их читать. Я не думаю, что виноват был ленивый просмотр и нетерпеливое ожидание.   -  person alfC    schedule 08.07.2019
comment
@alfC Да, обновил!   -  person Enzo    schedule 10.07.2019
comment
@Enzo, ладно, в конце концов, у них одинаковая скорость. Если да, пожалуйста, добавьте примечание в начале вашего вопроса, иначе это сбивает с толку.   -  person alfC    schedule 10.07.2019
comment
@alfC Я не уверен, что понимаю. Я имел в виду небольшую разницу, когда исправил указанную вами проблему int. Как видно из результата, разница между range-v3 и STD сохраняется.   -  person Enzo    schedule 10.07.2019
comment
@Enzo например 121 против 579 нс?   -  person alfC    schedule 10.07.2019
comment
Ага, верно. Это в 5 раз медленнее. На 128 сэмплах это в 7 раз медленнее. Вы должны представить себе, что люди, занимающиеся обработкой сигналов, непрерывно выполняют этот тип вычислений. Чтобы дать вам представление, для увеличения времени вычислений на 10% требуются дни программирования SSE / AVX и т. Д. Если переход на range-v3 означает замедление на 700%, это определенно не хорошо.   -  person Enzo    schedule 10.07.2019
comment
@Enzo: Пожалуйста, обратитесь к моему ответу / комментарию.   -  person einpoklum    schedule 24.12.2020
comment
ranges::to<std::vector<double>>(rng1) создать временный вектор. Из godbolt.org/z/1dE8oxrME при использовании функции to выделяется ненужная память. Пожалуйста, просто используйте некоторые вещи, например std::copy или ranges::copy, чтобы снова проверить производительность. Я не устанавливаю тест Google в руки. Согласно godbolt.org/z/1dE8oxrME, rangev3 может генерировать хорошие (но не самые оптимальные) инструкции.   -  person HarryLeong    schedule 14.07.2021


Ответы (1)


(Слишком долго для комментария)

Когда я пытаюсь скомпилировать этот код, я получаю:

<source>:1468:14: error: no match for 'operator=' (operand types are 'std::vector<double>' and 'ranges::transform2_view<ranges::ref_view<std::vector<double> >, ranges::ref_view<std::vector<double> >, std::plus<void> >')
 1468 |     output = rng1;

и я думаю, что это законная ошибка. Так, возможно, вы неправильно скопировали? Или вы хотите использовать там ::to<std::vector<double>>()?

person einpoklum    schedule 11.11.2020
comment
Спасибо, что изучили это. Вы правы - похоже, мой код больше не компилируется. Он использовался в то время, когда я писал, с компилятором, который я использовал в то время. Я отредактировал свой вопрос, добавив новую строку output = ranges::to<std::vector<double>>(rng1); К сожалению, сегодня range-v3 остается значительно медленнее, чем версия без диапазона. - person Enzo; 01.03.2021