Объясните изменение в GNU 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;
}

Мы запустили его с файлом журнала размером примерно 751 КБ. В предыдущих конфигурациях мы получили результат:

$ 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. Я придумал следующий хак, напрямую обращаясь к члену данных 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. Хм? Поскольку это элемент FILE * самого filebuf, я ожидаю, что его указатель позиции в файле будет синхронизирован с любым underflow() вызовы, сделанные filebuf. Почему я ошибаюсь?

Обновлять

Во-первых, позвольте мне подчеркнуть, что я понимаю, что эта последняя часть моего поста посвящена непортативным хакам. И все же, не разбираясь в мелочах здесь. я пытался позвонить

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

вместо этого, и это счастливо проходит через образец файла, а не застревает на 8192.

Окончательное обновление

Внутри моей компании мы сделали несколько обходных путей, которые снижают производительность настолько, что мы можем с этим смириться.

Извне Дэвид Краусс зарегистрировал ошибку в потоках GNU libstdc++, а недавно , Паоло Карлини проверил исправление. Все пришли к единому мнению, что нежелательное поведение находится в рамках Стандарта, но существует разумное решение для описанного мною пограничного случая.

Так что спасибо, StackOverflow, Дэвиду Крауссу, Паоло Карлини и всем разработчикам GNU!


person Don Wakefield    schedule 09.09.2010    source источник
comment
Я удивился, когда увидел, что 3.4.4 — это новая платформа ;)   -  person Billy ONeal    schedule 09.09.2010
comment
Да, я знаю ;^)~ ... У нас есть большая историческая клиентская база, и им нравится, когда мы поддерживаем машины/конфигурации, которые у них имеются, а не (только) то, что продается сегодня. Наше открытие просто отмечает, что изменение появилось где-то между двумя приведенными версиями.   -  person Don Wakefield    schedule 09.09.2010


Ответы (2)


Требования 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').

Так что это было сделано не для исправления ошибки.

Зарегистрированная ошибка: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=45628

person Potatoswatter    schedule 10.09.2010
comment
И это все еще происходит в последних версиях? Так что, учитывая требования нашей платформы, мы не получим облегчения в течение долгого времени! Вы говорите, что seekoff(0,ios::cur) должен быть особым случаем... Это, безусловно, имеет для меня смысл, но я не припоминаю, чтобы видел какое-либо обсуждение этого в стандарте. У вас есть цитата? Спасибо. - person Don Wakefield; 10.09.2010
comment
P.S. - Я уже прочитал раздел стандарта, указанный в сообщении об ошибке, которое вы подали (§27.8.1.4/11), и подумал, что Next, seek... сделал его безусловным, независимо от (o,ios::curr). - person Don Wakefield; 10.09.2010
comment
Кроме того, похоже, что из комментариев к ошибке, опубликованной Паоло Карлини, он в конечном итоге приходит к выводу, что они должны аннулироваться из-за их реализации отношения указателя файла и gptr( ). Таким образом, похоже, что мы просто не должны спрашивать позицию, когда только читаем, если нам нужна разумная производительность... - person Don Wakefield; 10.09.2010
comment
Я добавил комментарий к ошибке в ответ на комментарий № 5, объясняя, почему я думал, что они могут использовать ее в особом случае. Надеюсь, я правильно понял! - person Don Wakefield; 10.09.2010
comment
Дэвид, я вижу, что вы добавили gcc.gnu.org/viewcvs?view=revision&revision =164529 в libstdС++. Поздравляю, и пока мне не удастся использовать эту версию, спасибо! - person Don Wakefield; 24.09.2010
comment
@Don: Ну, может быть, больше причин для обновления приведет к более быстрому процессу обновления: v) . Над этим кодом предстоит проделать большую работу, и похоже, что в последнее время в разработке Boost активно используются iostreams. - person Potatoswatter; 25.09.2010

Ну, я не знаю точной причины изменения, но, видимо, изменения были сделаны для (см. Журнал изменений GCC 3.4):

  • Упрощенный streambuf, filebuf, отдельная синхронизация с C Standard I/O streambuf.
  • Поддержка больших файлов (файлы размером более 2 ГБ в 32-разрядных системах).

Я подозреваю, что поддержка больших файлов — это большая функция, которая потребует подобного изменения, потому что IOStreams больше не может предполагать, что он может отображать весь файл в память.

Правильная синхронизация с cstdio также является операцией, которая может потребовать большего количества сбросов на диск. Вы можете отключить это, используя std::sync_with_stdio.

person Billy ONeal    schedule 09.09.2010
comment
Я попытался установить std::sync_with_stdio(false), но это не повлияло на результаты ни на одной из платформ. В любом случае спасибо за предложение. - person Don Wakefield; 09.09.2010