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

发布于 2024-09-18 14:26:20 字数 3524 浏览 4 评论 0原文

我公司的产品在许多合格的 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() em> 来电消失。很明显,在较新版本的 g++ 中,调用 pubseekoff() 会使缓冲区“无效”,从而强制调用 underflow()。

我已经阅读了标准文档,并且 pubseekoff() 上的措辞肯定是不明确的。例如,底层文件指针位置与 gptr() 的位置有什么关系?在调用 underflow() 之前还是之后?不管怎样,可以这么说,我觉得 g++“中途换马”很令人恼火。此外,即使通用 seekoff() 需要使缓冲区指针无效,为什么要使用 ftell() 的等效项呢?

谁能向我指出导致这种行为变化的实现者之间的讨论线程?您对所涉及的选择和权衡有简洁的描述吗?

额外的学分

显然我真的不知道我在做什么。我正在尝试确定是否有一种方法(但不可移植)在 offset 为 0 且eekdir 为 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);
}

但是,诊断每一百次 seekoff 尝试打印出位置,每次都会产生 8192。啊?由于这是 filebuf 本身的 FILE * 成员,我希望它的文件位置指针与任何 同步filebuf 进行的 underflow() 调用。为什么我错了?

更新

首先,让我强调一下,我明白我的帖子的后半部分都是关于不可移植的黑客。尽管如此,还是不​​明白这里的实质内容。我尝试拨打电话

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

,这高兴通过示例文件进行,而不是陷入 8192。

最终更新

在我的公司内部,我们制定了一些解决方法,以减少我们可以承受的性能损失与它。

在外部,David Krauss 针对 GNU 的 libstdc++ 流提交了一个 bug,最近,保罗·卡利尼 (Paolo Carlini) 检查了修复。共识是,不良行为在标准的范围内,但对于我描述的边缘情况有一个合理的修复。

感谢 StackOverflow、David Krauss、Paolo Carlini 和所有 GNU 开发人员!

My company's products run on a number of qualified Linux hardware/software configurations. Historically, the compiler used has been GNU C++. For purposes of this post, let's consider version 3.2.3 the baseline, as our software 'worked as expected' through that version.

With the introduction of a newer qualified platform, using GNU C++ version 3.4.4, we began to observe some performance problems which we had not seen before. After some digging, one of our engineers came up with this test program:

#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;
}

We ran it against a log file of approximately 751KB chars. In the previous configurations, we got the result:

$ buftest
pos=768058
read chars=768058
underflows=0

In the newer version, the result is:

$ buftest
pos=768058
read chars=768058
underflows=768059

Comment out the pubseekoff(0, ios::cur) call and the excessive underflow() calls go away. So clearly, in newer versions of g++, calling pubseekoff() 'invalidates' the buffer, forcing a call to underflow().

I've read the standards document, and the verbiage on pubseekoff() is certainly ambiguous. What is the relationship of the underlying file pointer position to that of gptr(), for instance? Before or after a call to underflow()? Regardless of this, I find it irritating that g++ 'changed horses in midstream', so to speak. Moreover, even if a general seekoff() required invalidating the buffer pointers, why should the equivalent of ftell()?

Can anyone point me to a discussion thread amongst the implementors which led up to this change in behavior? Do you have a succinct description of the choices and tradeoffs involved?

Extra Credit

Clearly I really don't know what I'm doing. I was experimenting to determine if there was a way, however non portable, to bypass the invalidation in the case where offset is 0 and seekdir is ios::cur. I came up with the following hack, directly accessing the filebuf data member _M_file (this only wanted to compile with the 3.4.4 version on my machine):

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);
}

However, the diagnostic to print out the position every hundred seekoff attempts yields 8192 every time. Huh? Since this is the FILE * member of the filebuf itself, I'd expect it's file position pointer to be in synch with any underflow() calls made by the filebuf. Why am I wrong?

Update

First, let me emphasize that I understand this later part of my post is all about non-portable hacks. Still, not understanding the nitty-gritty here. I tried calling

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

instead, and this happily progresses through the sample file, rather than getting stuck at 8192.

Final Update

Internally to my company, we've made some workarounds that reduce the performance hit enough we can live with it.

Externally, David Krauss filed a bug against GNU's libstdc++ streams, and recently, Paolo Carlini checked in a fix. The consensus was that the undesired behavior was within the scope of the Standard, but that there was a reasonable fix for the edge case I described.

So thanks, StackOverflow, David Krauss, Paolo Carlini, and all the GNU developers!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

自找没趣 2024-09-25 14:26:20

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

在 GCC 4.2.1 和 4.5 中仍然会发生这种情况……

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

找到差异!请参阅更改-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

The requirements of seekoff certainly are confusing, but seekoff(0, ios::cur) is supposed to be a special case that doesn't synchronize anything. So this could probably be considered a bug.

And it still happens in GCC 4.2.1 and 4.5…

The problem is that (0, ios::cur) is not special-cased in _M_seek, which seekoff uses to call fseek to obtain its return value. So long as that succeeds, _M_seek unconditionally calls _M_set_buffer(-1);, which predictably invalidates the internal buffer. The next read operation causes underflow.

Found the diff! See change -473,41 +486,26. Comment was

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

So this wasn't done to fix a bug.

Filed bug: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=45628

风筝在阴天搁浅。 2024-09-25 14:26:20

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

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

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

cstdio 正确同步也是一项可能需要大量刷新磁盘的操作。您可以使用 std::sync_with_stdio

Well, I don't know the exact reason for the change, but apparently the changes were done for (See the GCC 3.4 Series Changelog):

  • Streamlined streambuf, filebuf, separate synched with C Standard I/O streambuf.
  • Large File Support (files larger than 2 GB on 32-bit systems).

I suspect that large file support is the big feature that would require a change like this, because IOStreams can no longer assume it can map the whole file into memory.

Correct syncing with cstdio also is an operation which might require a greater number of flushes to the disk. You can disable that using std::sync_with_stdio.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文