【问题标题】:Cumulative product of an array: std::accumulate vs loop数组的累积乘积:std::accumulate vs loop
【发布时间】:2021-11-10 06:09:24
【问题描述】:

假设我想计算数组的第一个 k 元素的累积乘积。

int arr[8] = {3, 5, 7, 4, 8, 9, 1, 2};
int k = 3;

就性能而言,哪一个是最佳选择?

选项1.普通for循环

int result = 1;
for (size_t i = 0; i < k; ++i) {
    result *= arr[i];
}

选项 2. 累积

result = std::accumulate(std::begin(arr),
                         std::begin(arr) + k,
                         1,
                         [](const int& x, const int &y) { return x*y; });

我对@9​​87654326@ 很小的情况特别感兴趣,例如k = 3k = 4。在这种情况下,花哨的 C++11 做事方式真的值得吗?

【问题讨论】:

  • “最佳”指的是什么?使用算法而不是循环的一个主要原因是可读性和表现力,这主要是基于意见
  • 顺便说一句,用 std::multiplies 累积不是“花哨的 C++11 方式”,这在 C++11 之前很久就可能实现,而且在 C++11 之后它比手写的 lambda 更可取跨度>
  • 在性能方面。最初的帖子已编辑。
  • 为了可读性,毫无疑问for 循环在做什么......两个都值得理解,“style-points v. readabilty, style-points v. readability, ...”结构有一个平衡。
  • @DavidC.Rankin 毫无疑问 std::accumulate 正在做什么。在我看来,两者都同样具有可读性。这是样式点和样式点之间的权衡。

标签: c++ accumulate


【解决方案1】:

简短的回答:使用算法而不是手写循环与性能无关!你可以看到一个可能的实现here。您会注意到它只是一个循环,您也可以自己编写。


更好的比较是手写循环与:

auto result = std::accumulate(std::begin(arr),
                              std::begin(arr) + k,
                              1,
                              std::multiplies<>{});

这相当于手写循环,不需要 C++11(仅适用于autostd::beginstd::end,而std::multiplies&lt;&gt; 需要 C++14,但在您使用 @ 987654327@).

该算法的优点是表现力和可读性。您不能为循环命名,而算法名称则说明了它的作用。此外,二元运算符和初始值更容易替换。初始值不一定与元素的类型相同,由此推导出accumulate的返回类型。如果您想将更多值相乘并避免溢出,您只需将1 替换为1L。如果您后来决定不想相乘,而是相加,您只需将std::multiplies 替换为std::plus。对于手写循环,这种重构需要深入细节并考虑整个循环,而不是只更改您想要更改的一个细节。除此之外,哪个“更好”是基于意见的。如果有的话,性能方面不会有太大差异。


请注意,C++11 和 C++14 带来的变化不仅仅是“花哨”。在 C++11 之前,调用看起来像这样:

int result = std::accumulate(arr,                        // <- int
                             arr + k,
                             1,                          // <- int
                             std::multiplies<int>{});    // <- int 

这里用于结果的类型出现了 3 次,这使得上面关于轻松替换初始值的类型的观点没有意义。 auto 和模板化 operator() 的函子带来的变化与花哨无关,但它们显着减少了犯错的机会。


TL;DR

对于像你这样简单的循环,我可能会使用手写循环。它清晰易读,不太可能有任何花哨的算法可以使其性能更高。

【讨论】:

  • 只是对语言级别的挑剔:独立的std::begin 在 C++11 之前也不可用。
【解决方案2】:

这两种方法都很好;两者都不是坏事。他们之间的选择是见仁见智的问题。

后者几乎不是“花哨的 C++11”,因为使用的唯一有意义的 C++11 功能是 lambda,它可以替换为 std::multiplies 或在一般情况下用“花哨的 C++ 中的函数对象”替换98"。 (是的,C++11 中也添加了 std::begin,但您可以简单地删除它,因为它不是必需的)。


我想建议一个范围替代方案。不幸的是,C++20 没有包含std::ranges::accumulate,所以我不得不建议使用非标准算法:

std::ranges::subrange sub1(arr, arr + k);
auto sub2 = std::views::counted(arr, k); // alternative
std::span sub3(arr, k);                  // less general alternative

// no standard ranges alternative for this yet
auto result = ranges::accumulate(sub1, 1, std::multiplies<>{});

就性能而言,哪一个是最佳选择?

两者/都不是。任何体面的优化器都应该产生相同的程序集。

【讨论】:

    猜你喜欢
    • 2023-03-09
    • 2019-02-09
    • 2020-11-12
    • 2018-12-29
    • 2021-09-12
    • 2015-11-27
    • 2020-03-16
    • 2020-08-07
    • 1970-01-01
    相关资源
    最近更新 更多