我公司的产品在许多合格的 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()?
任何人都可以指出导致这种行为变化的实施者之间的讨论线程吗?你对所涉及的选择和权衡有一个简洁的描述吗?
额外学分
显然我真的不知道我在做什么。我正在尝试确定是否有一种方法(无论多么不可移植)在 offset 为 0 且 seekdir 为ios::cur的情况下绕过失效。我想出了以下 hack,直接访问filebuf数据成员_M_file(这只是想在我的机器上用 3.4.4 版本编译):
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进行的任何underflow()调用同步。为什么我错了?
更新
首先,让我强调一下,我理解我帖子的后面部分都是关于非便携式黑客的。尽管如此,还是不明白这里的细节。我试着打电话
pos_type pos = _M_file.seekoff(0,ios::cur);
相反,这会愉快地通过示例文件进行,而不是卡在 8192。
最终更新
在我的公司内部,我们已经制定了一些变通办法,可以将性能影响降低到我们可以忍受的程度。
在外部,David Krauss 提交了一个针对 GNU 的 libstdc++ 流的错误,最近,Paolo Carlini 签入了一个修复程序。共识是不受欢迎的行为在标准的范围内,但是对于我描述的边缘情况有一个合理的修复。
非常感谢 StackOverflow、David Krauss、Paolo Carlini 和所有 GNU 开发人员!