打印性能优化[缓冲模式角度]

Posted BHY_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了打印性能优化[缓冲模式角度]相关的知识,希望对你有一定的参考价值。

问题背景

在某些场景下,N个进程调用XX接口进行大量字段信息打印,打印信息频繁,极大耗费系统资源。

问题代码

void functest(std::ostream &os)

{

         os << "xxxxx" << std::endl;

         while(flag) {

                   os << "[";

                   os << "xxx" << "yyy" << std::endl;

                   os << "zzz";

                   os << "]" << std::endl;

         }

         os << "xxxxx" << std::endl;

}

问题分析角度

XX接口中使用os有多次输出,其中还有while循环中的os输出(while循环的次数与字段数量正相关)。当os为stdout对象时,其缓冲模式的默认值为_IOLBF,即行缓冲模式,遇到换行符时会将缓冲区内容输出到终端,并清空缓冲区;当os为std::ofstream时(重定向),其缓冲模式默认值通常为_IOFBF,即全缓冲模式,遇到换行不输出。两种情况下区别在于遇到换行是否清理缓冲区。

缓冲模式类型

  1. _IOFBF(Fully Buffered):缓冲区已满或显式请求刷新时刷新,典型的缓冲区大小是 4KB。
  2. _IOLBF(Line Buffered):找到换行符、缓冲区已满或请求刷新时,将刷新缓冲区,典型的缓冲区大小是 1KB。
  3. _IONBF(Unbuffered):流是无缓冲的,也就是说,一旦可用,就立即发送输出。

cout会对输出的内容进行缓冲,所以输出的内容并不会立即输出到目标设备,而是被存储在缓冲区中,直到缓冲区填满才输出,在缓冲区没有满的时候,手动刷新缓冲区也可以实现输出。手动刷新缓冲区有两种方式,一种是向cout输出一个flush操作符,一种是向cout输出一个endl操作符,flush只刷新缓冲区,endl不仅刷新缓冲区,还加入了一个换行符。

以下情况会引发缓冲区刷新

  1. 缓冲区满时;
  2. 程序结束时;
  3. 关闭文件,即执行close语句;
  4. 执行cin语句,cin是和cout绑定的,可以用函数cin.tie(0)来解绑定
  5. 执行flush语句;
  6. 执行endl/ends语句

所以在以上代码中,多次std::endl,即有多次的清理缓冲区行为,清理缓冲区对性能的影响测试如下:

打印代码

输出到终端

输出到文件

for (int i = 0; i < 100000; i++) {
        fout << "1";
        //cout << "1";
    }

6468

4705

for (int i = 0; i < 100000; i++) {
        fout << "1\\n";
        //cout << "1\\n";
    }

175658

4783

for (int i = 0; i < 100000; i++) {
        fout << "1" << endl;
        //cout << "1" << endl;
    }

183146

152005

for (int i = 0; i < 100000; i++) {
        fout << "1" << flush;
        //cout << "1" << flush;
    }

160890

147487

for (int i = 0; i < 50000; i++) {
        fout << "1" << ends << "1" << endl;
        //cout << "1" << ends << "1" << endl;
    }

100196

78584

for (int i = 0; i < 50000; i++) {
        cout << "1\\n" << "1" << "\\n";
    }

177131

6772

输出到终端/文件的结果为3次测试的tick差值的平均值。可以看出,当使用flush或者endl/ends刷缓冲区时,输出到终端和文件性能差异并不大,但使用‘\\n’时,仅有换行,不刷缓冲区,输出到文件性能上提升了30多倍。

优化代码

void functest(std::ostream &os)

{

         os << "xxxxx" << "\\n";

         while(flag) {

                   os << "[";

                   os << "xxx" << "yyy" << "\\n";

                   os << "zzz";

                   os << "]" << "\\n";

         }

         os << "xxxxx" << endl; // 最后确保函数走完输出到文件或者由调用者flush或者触发自动刷缓冲区条件

}

Trace分析

打印代码

    while(1) {

        fout << "1" << endl;

    }

13:46:27.638892 write(3, "1\\n", 2)      = 2 <0.000012>

13:46:27.638933 write(3, "1\\n", 2)      = 2 <0.000012>

13:46:27.638974 write(3, "1\\n", 2)      = 2 <0.000013>

13:46:27.639015 write(3, "1\\n", 2)      = 2 <0.000013>

    while(1) {

        fout << "1\\n";

          }

13:50:25.713263 writev(3, [{"1\\n1\\n1\\n1\\n1\\n1\\n1\\n1\\n1\\n1\\n1\\n1\\n1\\n1\\n1\\n1\\n"..., 8190}, {"1\\n", 2}], 2) = 8192 <0.000019>

13:50:25.713499 writev(3, [{"1\\n1\\n1\\n1\\n1\\n1\\n1\\n1\\n1\\n1\\n1\\n1\\n1\\n1\\n1\\n1\\n"..., 8190}, {"1\\n", 2}], 2) = 8192 <0.000019>

13:50:25.713735 writev(3, [{"1\\n1\\n1\\n1\\n1\\n1\\n1\\n1\\n1\\n1\\n1\\n1\\n1\\n1\\n1\\n1\\n"..., 8190}, {"1\\n", 2}], 2) = 8192 <0.000019>

13:50:25.713971 writev(3, [{"1\\n1\\n1\\n1\\n1\\n1\\n1\\n1\\n1\\n1\\n1\\n1\\n1\\n1\\n1\\n1\\n"..., 8190}, {"1\\n", 2}], 2) = 8192 <0.000018>

优化效果分析

针对XX情况,一共有YY个字段信息,即最少YY个endl,通过该方式优化后,极大减少了缓冲区刷新次数,性能较大提升。

以上是关于打印性能优化[缓冲模式角度]的主要内容,如果未能解决你的问题,请参考以下文章

打印性能优化[缓冲模式角度]

打印性能优化[缓冲模式角度]

打印性能优化[缓冲区大小角度]

打印性能优化[缓冲区大小角度]

打印性能优化[缓冲区大小角度]

打印性能优化[缓冲区大小角度]