filebuf::seekoff() と対話する GNU C++ filebuf::underflow() への変更について説明します
質問
私の会社の製品は、多数の認定された Linux ハードウェア/ソフトウェア構成で実行されます。歴史的に、コンパイラは GNU C++ が使用されてきました。この投稿では、ソフトウェアがそのバージョンを通じて「期待どおりに動作した」ため、バージョン 3.2.3 をベースラインとみなします。
GNU C++ バージョン 3.4.4 を使用した新しい認定プラットフォームの導入により、これまでに見られなかったいくつかのパフォーマンスの問題が観察され始めました。いくつかの調査の後、当社のエンジニアの 1 人が次のテスト プログラムを思いつきました。
#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) 電話と過剰な アンダーフロー() 電話は消えます。明らかに、g++ の新しいバージョンでは、 パブシークオフ() バッファを「無効化」し、強制的に呼び出します。 アンダーフロー().
標準文書とその説明を読みました。 パブシークオフ() 確かに曖昧です。基になるファイル ポインタの位置とファイル ポインタの位置との関係は何ですか? gptr(), 、 例えば?前または 後 への電話 アンダーフロー()?それとは関係なく、g++ がいわば「途中で馬を変えた」のは腹立たしいです。さらに、たとえ 一般的な シークオフ() バッファポインタを無効にする必要がありますが、なぜ同等のものを必要とするのですか ftell()?
この動作の変更に至った実装者間のディスカッション スレッドを誰かが教えてくれませんか?関連する選択肢とトレードオフについて簡潔に説明していますか?
追加クレジット
明らかに、私は自分が何をしているのか本当に分かりません。私は、オフセットが0でseekdirが ios::cur. 。私は次のハックを思いつきました。 ファイルバッファ データメンバー _M_ファイル (これは私のマシン上の 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);
}
ただし、100 ごとに位置を出力する診断 シークオフ 試行すると毎回 8192 が返されます。はぁ?これは ファイル *
のメンバー ファイルバッファ それ自体は、ファイル位置ポインターが任意のファイルと同期していることを期待します。 アンダーフロー() によって行われた通話 ファイルバッファ. 。なぜ私が間違っているのでしょうか?
アップデート
まず、私の投稿の後半はすべて非移植性のハックに関するものであることを理解していることを強調しておきます。まだ、ここの核心部分を理解していません。電話してみました
pos_type pos = _M_file.seekoff(0,ios::cur);
代わりに、そしてこれ 幸せに 8192 で停止するのではなく、サンプル ファイルを進めていきます。
最終更新
私の会社では、パフォーマンスへの影響を許容できる程度に軽減するいくつかの回避策を講じました。
対外的には、David Krauss が次の書類を提出しました。 バグ GNU の libstdc++ ストリームに対して、最近、Paolo Carlini が修正をチェックインしました。コンセンサスは、望ましくない動作は標準の範囲内であるが、私が説明したエッジケースには合理的な修正があるということでした。
StackOverflow、David Krauss、Paolo Carlini、そしてすべての GNU 開発者に感謝します。
解決
の要件 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
.
diffを見つけました! 変更を参照してください -473,41 +486,26
. 。コメントはそうでした
(seekoff): Simplify, set _M_reading, _M_writing to false, call
_M_set_buffer(-1) ('uncommitted').
したがって、これはバグを修正するために行われませんでした。
他のヒント
そうですね、変更の正確な理由はわかりませんが、どうやら変更は次の目的で行われたようです( GCC 3.4 シリーズの変更履歴):
- 合理化された streambuf、filebuf、C 標準 I/O streambuf と個別に同期。
- 大きなファイルのサポート (32 ビット システムで 2 GB を超えるファイル)。
IOStreams はファイル全体をメモリにマップできると想定できなくなっているため、このような変更が必要となる大きな機能は、大きなファイルのサポートではないかと思います。
との正しい同期 cstdio
また、ディスクへのフラッシュ回数が増加する可能性がある操作です。を使用してそれを無効にできます std::sync_with_stdio
.