4

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

4

2 回答 2

1

的要求seekoff当然令人困惑,但seekoff(0, ios::cur)应该是不同步任何东西的特殊情况。所以这可能被认为是一个错误。

它仍然发生在 GCC 4.2.1 和 4.5 中……</p>

问题是(0, ios::cur)in 不是特例_M_seek,它seekoff使用 callfseek来获取它的返回值。只要成功,就会_M_seek无条件地调用_M_set_buffer(-1);,这可以预见地使内部缓冲区无效。下一次读取操作导致underflow.

发现差异!见变化-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

于 2010-09-10T04:55:57.297 回答
1

好吧,我不知道更改的确切原因,但显然更改已完成(请参阅GCC 3.4 系列更改日志):

  • 流线型 streambuf、filebuf,与 C 标准 I/O streambuf 单独同步。
  • 大文件支持(32 位系统上大于 2 GB 的文件)。

我怀疑大文件支持是需要像这样进行更改的大功能,因为 IOStreams 不能再假设它可以将整个文件映射到内存中。

与也正确同步cstdio是一项可能需要对磁盘进行更多刷新的操作。您可以使用std::sync_with_stdio.

于 2010-09-09T16:03:27.007 回答