解释对GNU C ++的更改filebuf :: underflow()与filebuf :: seekoff()交互

时间:2010-09-09 15:24:18

标签: c++ g++ underflow filebuf

我公司的产品运行在许多合格的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()

有人能指出实施者之间的讨论线程导致这种行为改变吗?您是否对所涉及的选择和权衡有简洁的描述?

额外学分

显然,我真的不知道自己在做什么。我正在尝试确定在偏移量为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 发出的调用。为什么我错了?

更新

首先,我要强调一点,我理解这篇文章的后半部分都是关于非便携式黑客的。尽管如此,还是不​​了解这里的细节。我试着打电话

pos_type pos = _M_file.seekoff(0,ios::cur);

相反,这个愉快地在示例文件中前进,而不是卡在8192。

最终更新

在我公司的内部,我们制定了一些解决方案,可以降低我们可以忍受的性能。

在外部,David Krauss针对GNU的libstdc ++流提交了bug,最近,Paolo Carlini检查了修复程序。大家一致认为,不良行为属于标准范围,但对我所描述的边缘情况有合理的解决方法。

非常感谢StackOverflow,David Krauss,Paolo Carlini以及所有GNU开发人员!

2 个答案:

答案 0 :(得分:1)

好吧,我不知道改变的确切原因,但显然已经完成了修改(参见GCC 3.4 Series Changelog):

  • 简化的streambuf,filebuf,与C标准I / O streambuf同步。
  • 大文件支持(32位系统上大于2 GB的文件)。

我怀疑大文件支持是需要这样更改的重要功能,因为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').

所以这不是为了修复错误。

提交错误:http://gcc.gnu.org/bugzilla/show_bug.cgi?id=45628