今天,我想分享一些在我尝试实现这个简单操作时令我震惊的事情:
我发现执行相同操作的不同方法:
- 通过使用
std::inner_product
.
- 实现谓词并使用
std::accumulate
功能。
- 使用 C 风格的循环。
我想通过使用 Quick Bench 并启用所有优化来执行一些基准测试。
首先,我比较了两种具有浮点值的 C++ 替代方案。这是使用使用的代码std::accumulate
:
const auto predicate = [](const double previous, const double current) {
return previous + current * current;
};
const auto result = std::accumulate(input.cbegin(), input.cend(), 0, predicate);
与此代码相比,使用std::inner_product
功能:
const auto result = std::inner_product(input.cbegin(), input.cend(), input.cbegin(), 1);
在启用所有优化的情况下运行基准测试后,我得到了以下结果:
两种算法似乎达到了相同的性能。我确实想进一步尝试 C 实现:
double result = 0;
for (auto i = 0; i < input.size(); ++i) {
result += input[i] * input[i];
}
令人惊讶的是,我发现:
我没想到这个结果。我确信有问题,所以我检查了 GCC 的实现:
template<typename _InputIterator1, typename _InputIterator2, typename _Tp>
inline _Tp
inner_product(_InputIterator1 __first1, _InputIterator1 __last1,
_InputIterator2 __first2, _Tp __init)
{
// concept requirements
__glibcxx_function_requires(_InputIteratorConcept<_InputIterator1>)
__glibcxx_function_requires(_InputIteratorConcept<_InputIterator2>)
__glibcxx_requires_valid_range(__first1, __last1);
for (; __first1 != __last1; ++__first1, (void)++__first2)
__init = __init + (*__first1 * *__first2);
return __init;
}
我发现它的做法与 C 实现相同。在审查了实现之后,我发现了一些奇怪的事情(或者至少我没想到会产生那么重大的影响):在所有内部累积中,它正在从迭代器 value_type 到初始值的类型进行转换。
就我而言,我将初始值初始化为 0 或 1,这些值被视为整数,并且在每次累加中,编译器都会进行转换。在不同的测试用例中,我的输入数组存储截断的浮点数,因此结果没有改变。
将初始值更新为 double 类型后:
const auto result = std::accumulate(input.cbegin(), input.cend(), 0.0, predicate);
And:
const auto result = std::inner_product(input.cbegin(), input.cend(), input.cbegin(), 0.0);
我得到了预期的结果:
现在,我明白,将初始值保留为独立于迭代器基础类型的类型可能会使函数更加灵活并允许执行更多操作。但,
如果我正在累积数组的元素,我希望得到相同类型的结果。内积也是如此。
它应该是默认行为吗?
为什么标准决定以这种方式执行?