告诉 `endl` 不要刷新缓冲区

3
我的程序向cout打印大量的短行。
作为一个有点牵强的例子,我的行看起来有点像这样:
cout<<"The variable's value is: "<<variable<<endl;

我希望程序运行快速,我确信endl正在拖慢我的程序,因为每次使用它都会在cout上启动缓冲区刷新。
现在,互联网上的一些人说,我可以这样做:
cout<<"The variable's value is: "<<variable<<"\n";

但这似乎不是一个好的解决方案,因为 endl 抽象了特定系统特定指定行尾的方式,而 \n 则没有。这也似乎是一个糟糕的解决方案,因为如果我将来需要缓冲,那么我将不得不修改整个代码库。
因此,我想问,有没有一种方法可以禁用 endl 的缓冲刷新功能?
编辑
进一步挖掘似乎表明,endl\n 都会遵循操作系统选择结束行的各种方式。 它还表明 输出流会检测是否处于潜在的交互式情况,并相应地进行缓冲和刷新。因此:如果我能够弄清楚如何手动告诉输出流执行积极的缓冲,那么问题可能就解决了。

6
我看不出这个问题与你链接的那个问题有什么重复之处。 - undefined
1
我可能对此有所错误,但我怀疑 cout 通过将内容写入 stdout ,会隐式地以文本模式打开,并因此执行字符转换来将 \n 转换为正确的行结束符。尽管我找不到任何证实这一点的内容,但我认为编写 \n 是完全可以的,而且具有可移植性。 - undefined
只是好奇...如果你快速写那么多行代码...用户怎么能读得过来呢?这有什么意义呢?stdout是行缓冲的,因为它被认为是“交互式”的。如果不是交互式的,为什么不直接写到其他地方呢? - undefined
2
请查看此答案:https://dev59.com/rnVC5IYBdhLWcg3wsTRi#213977 - undefined
1
我相信endl正在折磨我,但你应该通过测量来得出结论,也许会有一些意外的发现。 - undefined
显示剩余8条评论
5个回答

10

std::endl 把特定于系统的换行符抽象出来了,而 \n 没有。

std::endl 被定义为输出 '\n' 并刷新。正确的抽象是使用系统特定的换行符 '\n'

如果不想刷新,就不要使用 std::endl。此外,如果标准输出是或可能连接到交互设备,则可以将其设置为行缓冲。在这种情况下,换行符会刷新流。如果这是个问题,请使用一个连接到命名文件的 ofstream。我认为在类Unix系统中,只有标准输出是终端时才会发生行缓冲。


4
然而,这并不是问题的关键。他想要避免刷新。许多平台在看到换行符时也会进行刷新。 - undefined
2
@AlanStokes:不能保证换行符不会导致刷新。一开始我确实没有理解他的意思。我找不到任何绝对可靠的方法来解决这个问题。也许可以写入cerr?虽然有点取巧,但是…… - undefined
1
@Ed 并没有任何理由会这样。但是不是问题所在。而且恰好 cerr 在每次写入后都会刷新。 - undefined
1
@AlanStokes: 当然是可以的。"由于输出流的频繁刷新,性能很差,我该如何解决这个问题?" 我认为可以合理地假设他并不想要一个可能在用户环境中起作用或者不起作用的"解决方案"。 - undefined
@EdS.: 对于所要求的内容,你提出了一个合理的观点 - 但是值得一提的是,大多数程序在没有显式刷新的情况下会将输出打印到标准输出(stdout),如果终端每行都会自动刷新,那么通常不会有问题,因为特别长的输出通常会被重定向或者管道传输,例如使用less、tail、文件、/dev/null等。 - undefined
显示剩余3条评论

7

endl刷新缓冲区。如果您不希望有这种行为,请不要使用endl。如果您想轻松更改代码,请使用自己的操作符:

inline std::ostream& myendl( std::ostream& os ){
    os.put(os.widen('\n'));
    return os;
}

这样您就可以在一个地方轻松更改myendl的行为。


2
os.widen()不需要,因为字符类型已知。 - undefined

3
根据http://en.cppreference.com/w/cpp/io/manip/endl,endl:: 将一个换行符插入到输出序列 os 中,并刷新它,就像通过调用 os.put(os.widen('\n')) 然后是 os.flush() 一样。
所以从这个定义来看,你只需要编写 os.put(os.widen('\n')),这应该是安全、可移植和正确的,同时也满足你的主要需求。

1

有一个名为std::nounitbuf的选项,据文档称在此事上有一定影响。然而,我并没有注意到任何区别。为了绕过所有ostream关于何时刷新或不刷新的想法,我尝试了这个方法:

std::ostringstream oss;
//  std::cout << std::nounitbuf;
for( int i = 0; i < 1000000; i++ ){
//  std::cout <<  "Test " << "file" << '\n';
    oss <<  "Test " << "file" << '\n';
}
std::cout << oss.str();

这样改善后,执行时间从约33秒缩短至约25秒。
如果你的输出是在xterm中显示的,那么xterm完成滚动等工作会严重限制你的执行速度。如果你使用管道来过滤掉不必要的行,你会看到速度有显著提升,例如:
./program | grep -v "The variable"

1
如果刷新是问题,您可以实现一个流缓冲区,覆盖sync()成员函数,仅在指定时刷新到外部设备。这还需要创建自己的操纵符flush_on_endlnoflush_on_endl,如果您打算在整个程序中更改这些首选项,则必须这样做。
#include <iostream>

static int disable() {
    static int index(std::ios_base::xalloc());
    return index;
}

class save_buffer
{
public:
    save_buffer(std::ios& other)
        : str(other), buf(other.rdbuf())
    { }

    ~save_buffer() { str.rdbuf(buf); }
private:
    std::ios& str;
    std::streambuf* buf;
};

class syncing_buffer_optional : public std::streambuf, private save_buffer
{
public:
    syncing_buffer_optional(std::ostream& other)
        : save_buffer(other),
          buf(other.rdbuf()),
          disable_sync(other.iword(disable()))
    { }

    std::streambuf::int_type overflow(std::streambuf::int_type c)
    {
        buf->sputc(c);
        return 0;
    }

    int sync()
    {
        return disable_sync? 0: buf->pubsync();
    }
private:
    std::streambuf* buf;
    bool disable_sync;
};

std::ostream& flush_on_endl(std::ostream& os)
{
    os.iword(disable()) = false;
    return os;
}

std::ostream& noflush_on_endl(std::ostream& os)
{
    os.iword(disable()) = true;
    return os;
}


std::ostream& endl(std::ostream& os)
{
    syncing_buffer_optional eb(os);
    os.rdbuf(&eb);

    return os << std::endl;
}

int main()
{
    std::cout << noflush_on_endl << endl;
}

注意:由于缓冲区和流之间存在生命周期依赖关系,我需要创建一个endl函数。此外,即使缓冲区是静态的,我们如何区分std::endl调用和任意的flush()调用呢? - undefined

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