Почему такая реализация суммирования векторов в rangev3 медленнее, чем в STD-эквиваленте? - PullRequest
6 голосов
/ 08 июля 2019

Я рассматриваю возможность использования 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-ядерном i7 MacBook Pro с флагом -O3.

#include <range/v3/all.hpp>
#include "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 = 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
...