【问题标题】:Why is this rangev3 implementation of vectors summation slower than the STD equivalent?为什么向量求和的 rangev3 实现比 STD 等效项慢?
【发布时间】:2019-11-17 11:44:58
【问题描述】:

我正在考虑在我的库中使用 rangev3。我喜欢 rangev3 的语法,但优先级是性能。该库运行大量向量乘法和加法,其中大部分为 128 个样本。例如,我使用 Google benchmark 来评估两个向量的相加。范围版本比 STD 版本慢得多(对于短向量长度几乎慢 10 倍)。这有点令人惊讶,因为 rangev3(以及 C++20 中未来的 std::ranges)通常声称具有良好的性能。

我在这里使用 rangev3 的方式有问题吗?还是与编译器无法很好地展开 rangev3 代码有关?还是 rangev3 的性能提升仅显示在许多菊花链操作中?

注意:output = rng1; 赋值不应该分配内存,因为向量长度是相同的(我尝试使用 range::copy,但它变得慢了 100 倍)。我尝试对向量 A 和 B 进行预初始化和随机化,但没有发现任何区别。我确实注意到,如果我在管道中有更多操作,STL 和 ragesv3 之间的差距会缩小,但仅限于长向量(连续 5 次操作超过 32000)。

下面是一个包含性能指标的独立示例。我在带有 -O3 标志的 4 核 i7 MacBook Pro 上运行 C++17 LLVM libc++。

#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

【问题讨论】:

  • 您是否在启用优化的情况下进行编译?请发布您的编译行
  • 使用 -O3 运行。我正在使用 Xcode,所以我并没有真正的编译行,但如果你问具体的选项,我可以给你。谢谢!
  • 我的猜测是范围,还不是标准库的一部分,不会从标准算法所做的一些经过良好测试的性能调整中受益。我能想到的一件事是views 是懒惰的,因此无法从矢量化处理器指令中受益,但我可能是错的。编辑:@alfC - 非常好的电话。我建议你运行一些测试并可能发布一个答案,如果它变成了瓶颈
  • @Enzo:我认为将此测试用作范围视图的某种基准是没有帮助的。范围视图不适用于琐碎的情况。您正在为每个值调用一个简单的算法并将结果存储在一个容器中。视图用于组合多个独立操作来构建复杂操作 不必将中间结果存储在容器中。基本上,没有人不应该编写该代码的视图版本,因此没有必要针对常规算法版本对其进行测试。
  • @Enzo:如果您没有将视图版本与 必须在容器中存储中间值的非视图版本进行比较,则为下一个操作处理这些值,那么您就没有将视图用于其预期目的。如果您谈论的是比int 更复杂的对象(例如字符串或其他分配内存的东西),那就更是如此。

标签: c++ vector range-v3


【解决方案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&lt;std::vector&lt;double&gt;&gt;()?

【讨论】:

  • 感谢您对此进行调查。你是对的——我的代码似乎不再编译了。它在我发布时使用,与我当时使用的编译器一起使用。我用新行 output = ranges::to&lt;std::vector&lt;double&gt;&gt;(rng1); 编辑了我的问题,遗憾的是,range-v3 仍然比今天的非范围版本慢得多。
猜你喜欢
  • 2017-08-22
  • 2022-11-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-22
相关资源
最近更新 更多