我公司的产品运行在许多合格的Linux硬件/软件配置上。从历史上看,使用的编译器是GNU C ++。出于本文的目的,让我们将版本3.2.3视为基线,因为我们的软件通过该版本“按预期工作”。
随着新的合格平台的推出,使用GNU C ++版本3.4.4,我们开始观察到一些我们以前从未见过的性能问题。经过一番挖掘,我们的一位工程师想出了这个测试程序:
#include <fstream>
#include <iostream>
using namespace std;
class my_filebuf : public filebuf
{
public:
my_filebuf() : filebuf(), d_underflows(0) {};
virtual ~my_filebuf() {};
virtual pos_type seekoff(off_type, ios_base::seekdir,
ios_base::openmode mode = ios_base::in | ios_base::out);
virtual int_type underflow();
public:
unsigned int d_underflows;
};
filebuf::pos_type my_filebuf::seekoff(
off_type off,
ios_base::seekdir way,
ios_base::openmode mode
)
{
return filebuf::seekoff(off, way, mode);
}
filebuf::int_type my_filebuf::underflow()
{
d_underflows++;
return filebuf::underflow();
}
int main()
{
my_filebuf fb;
fb.open("log", ios_base::in);
if (!fb.is_open())
{
cerr << "need log file" << endl;
return 1;
}
int count = 0;
streampos pos = EOF;
while (fb.sbumpc() != EOF)
{
count++;
// calling pubseekoff(0, ios::cur) *forces* underflow
pos = fb.pubseekoff(0, ios::cur);
}
cerr << "pos=" << pos << endl;
cerr << "read chars=" << count << endl;
cerr << "underflows=" << fb.d_underflows << endl;
return 0;
}
我们针对大约751KB字符的日志文件运行它。在之前的配置中,我们得到了结果:
$ buftest
pos=768058
read chars=768058
underflows=0
在较新版本中,结果为:
$ buftest
pos=768058
read chars=768058
underflows=768059
注释 pubseekoff(0,ios :: cur)调用,过多的 underflow()调用消失。很明显,在较新版本的g ++中,调用 pubseekoff()会使缓冲区无效,从而强制调用 underflow()。
我已经阅读了标准文档, pubseekoff()上的措词肯定是模棱两可的。例如,底层文件指针位置与 gptr()的关系是什么?在 underflow()之前之后之后?无论如何,我发现g ++'在中游改变了马'令人恼火,可以这么说。而且,即使通用 seekoff()需要使缓冲区指针无效,为什么要等效于 ftell()?
有人能指出实施者之间的讨论线程导致这种行为改变吗?您是否对所涉及的选择和权衡有简洁的描述?
int sc(0);
filebuf::pos_type my_filebuf::seekoff(
off_type off,
ios_base::seekdir way,
ios_base::openmode mode
)
{
if ((off == 0) && (way == ios::cur))
{
FILE *file =_M_file.file();
pos_type pos = pos_type(ftell(file));
sc++;
if ((sc % 100) == 0) {
cerr << "POS IS " << pos << endl;
}
return pos;
}
return filebuf::seekoff(off, way, mode);
}
但是,每百次搜索尝试打印位置的诊断每次都会产生8192。咦?由于这是 filebuf 本身的 FILE *
成员,我希望它的文件位置指针与任何下溢()同步 filebuf 发出的调用。为什么我错了?
首先,我要强调一点,我理解这篇文章的后半部分都是关于非便携式黑客的。尽管如此,还是不了解这里的细节。我试着打电话
pos_type pos = _M_file.seekoff(0,ios::cur);
相反,这个愉快地在示例文件中前进,而不是卡在8192。
在我公司的内部,我们制定了一些解决方案,可以降低我们可以忍受的性能。
在外部,David Krauss针对GNU的libstdc ++流提交了bug,最近,Paolo Carlini检查了修复程序。大家一致认为,不良行为属于标准范围,但对我所描述的边缘情况有合理的解决方法。
非常感谢StackOverflow,David Krauss,Paolo Carlini以及所有GNU开发人员!
答案 0 :(得分:1)
好吧,我不知道改变的确切原因,但显然已经完成了修改(参见GCC 3.4 Series Changelog):
我怀疑大文件支持是需要这样更改的重要功能,因为IOStreams不能再认为它可以将整个文件映射到内存中。
与cstdio
正确同步也是一项操作,可能需要更多次刷新到磁盘。您可以使用std::sync_with_stdio
禁用该功能。
答案 1 :(得分:1)
seekoff
的要求当然令人困惑,但seekoff(0, ios::cur)
应该是一个不同步的特殊情况。所以这可能被视为一个错误。
它仍然发生在GCC 4.2.1和4.5 ......
问题是(0, ios::cur)
_M_seek
并非特殊情况,seekoff
用于调用fseek
来获取其返回值。只要成功,_M_seek
无条件地调用_M_set_buffer(-1);
,这可预测地使内部缓冲区无效。下一个读取操作会导致underflow
。
Found the diff!请参阅更改-473,41 +486,26
。评论是
(seekoff): Simplify, set _M_reading, _M_writing to false, call
_M_set_buffer(-1) ('uncommitted').
所以这不是为了修复错误。