私の会社の製品は、多くの認定されたLinuxハードウェア/ソフトウェア構成で動作します。歴史的に、使用されているコンパイラはGNUC++でした。この投稿の目的のために、バージョン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)呼び出しをコメントアウトすると、過剰なunderflow()呼び出しがなくなります。したがって、明らかに、g ++の新しいバージョンでは、pubseekoff()を呼び出すとバッファが無効になり、underflow()が強制的に呼び出されます。
標準ドキュメントを読みましたが、pubseekoff()の言い回しは確かにあいまいです。たとえば、基になるファイルポインタの位置とgptr()の位置との関係は何ですか?underflow()の呼び出しの前または後?それにもかかわらず、いわば、g++が「中流で馬を変えた」ことは苛立たしいことだと思います。さらに、一般的なseekoff()でバッファポインタを無効にする必要がある場合でも、ftell()と同等のものが必要なのはなぜですか?
この振る舞いの変化につながった実装者間のディスカッションスレッドを誰かに教えてもらえますか?関連する選択とトレードオフについて簡潔に説明していますか?
追加クレジット
明らかに、私は自分が何をしているのか本当にわかりません。私は、オフセットが0で、seekdirがios :: curの場合に無効化をバイパスする方法があるかどうかを判断するために実験していましたが、移植性はありません。私は次のハックを思いつき、 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);
}
ただし、100回のシークオフ試行ごとに位置を出力する診断では、毎回8192が生成されます。は?これはfilebuf自体のFILE *
メンバーであるため、ファイル位置ポインターがfilebufによって行われるunderflow()呼び出しと同期していると思います。なぜ私は間違っているのですか?
アップデート
まず、私の投稿のこの後半部分は、移植性のないハッキングに関するものであることを理解していることを強調しておきます。それでも、ここで核心を理解していません。電話してみました
pos_type pos = _M_file.seekoff(0,ios::cur);
代わりに、これは8192でスタックするのではなく、サンプルファイルを順調に進みます。
最終更新
私の会社の内部では、パフォーマンスへの影響を十分に減らすためのいくつかの回避策を作成しました。
外部的には、DavidKraussがGNUのlibstdc++ストリームに対してバグを報告し、最近、PaoloCarliniが修正をチェックインしました。コンセンサスは、望ましくない動作は標準の範囲内であるが、私が説明したエッジケースには合理的な修正があったというものでした。
StackOverflow、David Krauss、Paolo Carlini、そしてすべてのGNU開発者に感謝します!