美文网首页
C++中endl和\n的差异引发的思考

C++中endl和\n的差异引发的思考

作者: Domibaba | 来源:发表于2022-02-16 03:03 被阅读0次

      今天在处理输出时候遇到一个问题,当输出量级比较小时,使用std::cout << std::endl正常工作,而当输出的量级达到百万左右的时候,程序一直工作异常,原始的程序类似下面的代码片段,每一行输出一个字符:

    // 使用std::endl的版本
    vector<char> buff;
    // 其他给buff赋值的代码
    for (const auto &ch : buff) {
        std::cout << ch << std::endl;
    }
    

      而使用\n替代std::endl后,程序可以正常工作,也就是按照如下修改后工作正常:

    // 使用\n的版本
    vector<char> buff;
    // 其他给buff赋值的代码
    for (const auto &ch : buff) {
        std::cout << ch << '\n';
    }
    

      那么std::endl\n的区别是啥呢?简而言之:

    std::endl会首先在输出流的缓冲区插入一个换行符\n,然后强制刷新输出流的缓冲区;而\n只是简单的在输出流的缓冲区中插入换行符。

      正是因为多了强制刷新输出流缓冲区这个动作,导致使用std::endl的版本明显要慢于使用\n的版本。

      那么为什么会造成这种结果呢?想了解这个问题,首先要弄清楚两个概念: 缓冲区

    :流C++标准库为处理 输入输出 (IO)而定义的一种类型,它支持向设备写入数据,以及从设备读取数据的操作。这里的设备可以是文件,可以是控制台,可以是string类型的字符串等等。能读取数据的流叫做 输入流 ,能往设备写入数据的流叫做 输出流 ,例如C++标准库中定义的cin即为输入流,从标准输入读取数据;cout即为输出流,往标准输出写入数据。

    缓冲区:这里特指流的缓冲区,也就是保存流读写数据的地方。流的写操作并不马上作用于设备(控制台、文件等),而是保存在缓冲区中,等待合适的时机才会刷新(也就是输出到实际的设备)。

      举个例子:std::cout << "Hello world!;这个输出并不会立马被打印到标准输出,而是存到缓冲区中,等到合适的时机再打印到标准输出,换成写文件就好理解了,每输出一个字符就向文件做一次写操作;和将输出的字符存到缓冲区,等到合适的时候再把缓冲区存的多个字符一次写入文件;这两者相比显然是后者效率要高,能带来性能的提升,因为写文件是耗时操作。

      上面提到的 合适的时机 也叫做 缓冲区刷新 ,也就是把缓冲区输出到设备(文件、控制台等),缓冲区刷新可能由以下几个方面触发:

    • 程序正常结束(注意如果程序异常结束可能不会触发缓冲区刷新)
    • 缓冲区满(不刷新后面的数据就无法写入缓冲区了)
    • 使用endlflush等操作强制刷新
    • 和其他流关联的输出流,在关联的流被读写时,会刷新输出流的缓冲区。例如默认情况下cincerr都关联到cout,因此cincerr调用的时候,cout的缓冲区会被刷新
    • 使用unitbuf设置输出流,例如执行cout << unitbuf;,那么接下来每次的cout都会立马触发缓冲区的刷新

      回到最开始的问题,那么答案就是:每次输出都触发缓冲区的刷新会比让系统决定在合适的时候刷新缓冲区效率低,因此除非有明确的手动刷新缓冲区诉求,否则建议在频繁操作中使用\n代替std::endl;也许单次的std::cout << "Hello world!" << std::endlstd::cout << "Hello world!\n"差别并不大,但是频繁的操作前者的性能显然比后者低。我们使用2个程序来做对比验证,输出一百万次,程序1每次输出都刷新,程序2使用系统默认的缓冲区刷新策略:

    • 程序1:使用std::endl
    // ./test_flush_immediately
    #include <iostream>
    int main(int argc, char **argv)
    {
        for (int i = 0; i < 1000000; i++) {
            std::cout << i << std::endl;
        }
    }
    
    • 程序2:使用\n
    // ./test_flush_later
    #include <iostream>
    int main(int argc, char **argv)
    {
        for (int i = 0; i < 1000000; i++) {
            std::cout << i << '\n';
        }
    }
    

      使用g++编译,测试结果如下:

    • 输出到控制台
    time ./test_flush_immediately
    ./test_flush_immediately  `1.93`s user 1.20s system 57% cpu 5.485 total
     
    time ./test_flush_later
    ./test_flush_later  1.81s user 1.22s system 55% cpu 5.486 total
    
    • 输出到/dev/null
    time ./test_flush_immediately >/dev/null
    ./test_flush_immediately >/dev/null  1.56s user 0.53s system 99% cpu 2.111 total
    
    time ./test_flush_later >/dev/null
    ./test_flush_later >/dev/null  0.41s user 0.01s system 97% cpu 0.423 total
    
    • 输出到文件
    time ./test_flush_immediately >file
    ./test_flush_immediately > file  `1.76`s user 6.04s system 87% cpu 8.933 total
     
    time ./test_flush_later >file
    ./test_flush_later > file  0.41s user 0.02s system 98% cpu 0.440 total
    
      结论:std::endl比使用\n耗时,且在不同的输出设备上耗时也有差异(输出到文件时尤其明显,使用std::endl的系统调用需要6.04s,而使用\n只需要0.02s),因此除非明确需要手动刷新缓冲区,否则建议使用\n

    相关文章

      网友评论

          本文标题:C++中endl和\n的差异引发的思考

          本文链接:https://www.haomeiwen.com/subject/rysrlrtx.html