ostream_iterator与for each循环的效率比较

11

我昨天看到这位用户的帖子。我觉得那是一个很酷的输出向量的方法。于是我写了一个例子,并问自己这与for each循环相比如何?

template <typename T>
void printVectorO(std::vector<T> &v)
{
    std::cout << "Ostream_iterator contents: " << std::endl;
    auto start = std::chrono::high_resolution_clock::now();
    std::ostream_iterator<T> ost(std::cout, " ");
    std::copy(begin(v), end(v), ost);
    std::cout << std::endl;

    auto end = std::chrono::high_resolution_clock::now();
    auto time = end - start;
    auto nano = std::chrono::duration_cast<std::chrono::nanoseconds>(time);
    std::cout << "Ostream_iterator computation took: " << nano.count() << " nano seconds"<< std::endl;
    std::cout << std::endl;
}

template <typename T>
void printVectorC(std::vector<T> &v)
{
    std::cout << "For Each Loop contents: " << std::endl;
    auto start = std::chrono::high_resolution_clock::now();
    for (auto && e : v) std::cout << e << " ";
    std::cout << std::endl;

    auto end = std::chrono::high_resolution_clock::now();
    auto time = end - start;
    auto nano = std::chrono::duration_cast<std::chrono::nanoseconds>(time);
    std::cout << "For Each Loop took: " << nano.count() << " nano seconds" << std::endl;
    std::cout << std::endl;
}

我使用了3个向量来进行测试:

std::vector<double> doubles = { 3.15, 2.17, 2.555, 2.014 };
std::vector<std::string> strings = { "Hi", "how", "are", "you" };
std::vector<int> ints = { 3, 2 , 2 , 2 };

我得到了各种结果。当我输出双精度浮点数时(例如41856与11207和55198与10308纳秒),for each循环总是比ostream_iterator更快。有时,字符串ostream_iterator胜过for each循环,而在处理整数时,for each循环和ostream_iterator几乎保持不分伯仲。

这是为什么?ostream_iterator的后台发生了什么?在效率和速度方面,何时应该使用ostream_iterator而不是for each循环?


2
建议添加编译器和优化级别,以帮助建立参考框架。 - user4581301
2
也许在调用 copy 的代码周围添加一个循环,并在 for 周围添加一个循环,重复感兴趣的代码一百万次左右,以消除可能影响您计时的其他因素。 - jxh
2
除了jxh的评论之外,或许可以添加一个循环来生成几十万个值放入向量中,这样差异就会更加明显(即检查差异是否总是几纳秒,还是它们成比例增长)。 - scohe001
4
我同意,仅使用三个值时任何指标都是无意义的。尝试使用几千个值。另外,尝试将输出发送到std::ostringstream而不是std::cout。将输出发送到std::cout可能会反映有关输出缓冲区刷新的一些伪影。 - Sam Varshavchik
1
在控制台上显示字符(和滚动控制台)的成本将使两种迭代策略之间的可忽略性能差异相形见绌。 唯一有意义的基准测试方法是流输出到具有足够容量以防止重新分配的std::ostream的底层字符串。 另一种选择是创建自己的streambuf派生流缓冲区并将其分配给ostream。 - Richard Hodges
显示剩余7条评论
1个回答

3

小心微基准测试。

我对这段代码有几点普遍的评论:

  1. 将只读变量作为const引用而不是常规引用传递。这不会影响性能,但可以提高代码可读性。
  2. 不要使用std::endl,因为它调用flush(),在这样的微基准测试中最耗时。例如,使用std::endl打印双精度浮点数需要37010纳秒,而使用'\n'只需要4456纳秒。
  3. 单个测量是不准确的。为了平滑任何测量噪声,您必须在循环中运行它多次。这仍然是不完美的,因为最好的方法是交替运行测试(使可能减慢代码的随机事件以相同的方式影响两种实现)。
  4. 最好将其重定向到文件,否则终端速度将主导结果。

以下是已更正的基准测试:

constexpr unsigned ITERATIONS = 1000000;
template <typename T>
void printVectorO(const std::vector<T> &v)
{
    std::cout << "Ostream_iterator contents\n";
    auto start = std::chrono::high_resolution_clock::now();
    for (unsigned i=0 ; i < ITERATIONS; ++i) {
        std::ostream_iterator<T> ost(std::cout, " ");
        std::copy(begin(v), end(v), ost);
        std::cout << '\n';
    }

    auto end = std::chrono::high_resolution_clock::now();
    auto time = end - start;
    auto nano = std::chrono::duration_cast<std::chrono::nanoseconds>(time);
    std::cout << "Ostream_iterator computation took: "
              << nano.count() / ITERATIONS << " nano seconds\n\n";
}

template <typename T>
void printVectorC(const std::vector<T> &v)
{
    std::cout << "For Each Loop contents\n";
    auto start = std::chrono::high_resolution_clock::now();
    for (unsigned i=0 ; i < ITERATIONS ; ++i) {
        for (auto && e : v) std::cout << e << " ";
        std::cout << '\n';
    }

    auto end = std::chrono::high_resolution_clock::now();
    auto time = end - start;
    auto nano = std::chrono::duration_cast<std::chrono::nanoseconds>(time);
    std::cout << "For Each Loop took: "
              << nano.count() / ITERATIONS << " nano seconds\n\n";
}

并使用以下方式调用:

template <class Container>
void test(const Container & ctr)
{
    printVectorC2(ctr);
    printVectorO2(ctr);
}


int main()
{
    std::vector<double> doubles = { 3.15, 2.17, 2.555, 2.014 };
    test(doubles);
    std::vector<std::string> strings = { "Hi", "how", "are", "you" };
    test(strings);
    std::vector<int> ints = { 3, 2 , 2 , 2 };
    test(ints);
}

现在,在使用grep命令搜索nano后,我们得到了以下结果:

For Each Loop took: 2045 nano seconds
Ostream_iterator computation took: 2033 nano seconds
For Each Loop took: 487 nano seconds
Ostream_iterator computation took: 485 nano seconds
For Each Loop took: 503 nano seconds
Ostream_iterator computation took: 499 nano seconds

几乎没有什么区别。实际上,使用这个特定的运行方式,似乎ostream版本更快。但再次运行会得到稍微不同的结果。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接