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

我公司的产品运行在许多合格的
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开发人员!

最佳答案 寻求的要求当然令人困惑,但是寻求(0,ios :: cur)应该是一种不同步的特殊情况.所以这可能被视为一个错误.

它仍然发生在GCC 4.2.1和4.5 ……

问题是(0,ios :: cur)在_M_seek中不是特殊的,其中seekoff用来调用fseek来获取它的返回值.只要成功,_M_seek无条件地调用_M_set_buffer(-1);,这可预测地使内部缓冲区无效.下一个读操作会导致下溢.

Found the diff!见变化-473,41 486,26.评论是

    (seekoff): Simplify, set _M_reading, _M_writing to false, call
    _M_set_buffer(-1) ('uncommitted').

所以这不是为了修复bug.

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

点赞