Не потокобезопасный файловый ввод / вывод в C / C ++ - PullRequest
5 голосов
/ 29 июля 2009

Устраняя некоторые проблемы с производительностью в наших приложениях, я обнаружил, что функции C stdio.h (и, по крайней мере для нашего поставщика, классы C ++ fstream) являются поточно-ориентированными. В результате каждый раз, когда я делаю что-то простое, например fgetc, RTL должен получить блокировку, прочитать байт и снять блокировку.

Это не хорошо для производительности.

Каков наилучший способ получения файловых операций ввода / вывода без потоков в C и C ++, чтобы я мог самостоятельно управлять блокировками и получать более высокую производительность?

  • MSVC предоставляет _fputc_nolock, а GCC предоставляет unlocked_stdio и flockfile, но я не могу найти аналогичные функции в моем компиляторе (CodeGear C ++ Builder).
  • Я мог бы использовать сырой Windows API, но он не переносимый, и я предполагаю, что он будет медленнее, чем разблокированный fgetc для ввода-вывода за один раз.
  • Я мог бы переключиться на что-то вроде Apache Portable Runtime , но это может быть много работы.

Как другие подходят к этому?

Редактировать : Поскольку несколько человек задались вопросом, я проверил это перед публикацией. fgetc не выполняет системные вызовы, если может удовлетворить запросы чтения из своего буфера, но все же выполняет блокировку, поэтому блокировка занимает огромное количество времени (сотни блокировок для получения и освобождения для одного блока чтения данных с диска). Если бы не было посимвольного ввода-вывода, это было бы решением, но классы C ++ Builder fstream, к сожалению, используют fgetc (поэтому, если я хочу использовать классы iostream, я застрял с этим), и У меня много устаревшего кода, который использует fgetc и друзей для чтения полей из файлов стиля записи (что было бы разумно, если бы не проблемы с блокировкой).

Ответы [ 6 ]

3 голосов
/ 29 июля 2009

Я бы просто не делал IO символ за раз, если это разумно с точки зрения производительности.

1 голос
/ 29 июля 2009

Одна вещь, которую следует учитывать, - это создание собственной среды выполнения. Большинство компиляторов предоставляют исходный код для библиотеки времени выполнения (я был бы удивлен, если бы ее не было в пакете C ++ Builder).

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

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

1 голос
/ 29 июля 2009

мультиплатформенный подход довольно прост. Избегайте функций или операторов, где стандарт указывает, что они должны использовать часового. sentry - это внутренний класс в классах iostream, который обеспечивает согласованность потоков для каждого выходного символа, а в многопоточной среде он блокирует мьютекс, связанный с потоком, для каждого выводимого символа. Это позволяет избежать состояния гонки на низком уровне, но при этом делает вывод нечитаемым, поскольку строки из двух потоков могут выводиться одновременно, как показано в следующем примере:

Тема 1 должна написать: abc
поток 2 должен написать: def

Вывод может выглядеть следующим образом: adebcf вместо abcdef или defabc. Это связано с тем, что часовой механизм реализован для блокировки и разблокировки каждого символа.

Стандарт определяет его для всех функций и операторов, работающих с istream или ostream. Единственный способ избежать этого - использовать потоковые буферы и собственную блокировку (например, для каждой строки).

Я написал приложение, которое выводит некоторые данные в файл и измеряет скорость. Если вы добавите сюда функцию, которая выходит непосредственно с использованием fstream, без использования буфера и сброса, вы увидите разницу в скорости. Это использует повышение, но я надеюсь, что это не проблема для вас. Попробуйте удалить все потоковые буферы и увидеть разницу с ними и без них. В моем случае недостаток производительности составлял 2-3 раза.

Следующая статья Н. Майерса объяснит, как работают локали и часовые в c ++ IOStreams. И наверняка вам следует поискать в документе ISO C ++ Standard, какие функции используют sentry.

Удачи,
Ованес

#include <vector>
#include <fstream>
#include <iterator>
#include <algorithm>
#include <iostream>
#include <cassert>
#include <cstdlib>

#include <boost/progress.hpp>
#include <boost/shared_ptr.hpp>

double do_copy_via_streambuf()
{
  const size_t len = 1024*2048;
  const size_t factor = 5;
  ::std::vector<char> data(len, 1);

  std::vector<char> buffer(len*factor, 0);

  ::std::ofstream
    ofs("test.dat", ::std::ios_base::binary|::std::ios_base::out);
  noskipws(ofs);

  std::streambuf* rdbuf = ofs.rdbuf()->pubsetbuf(&buffer[0], buffer.size());

  ::std::ostreambuf_iterator<char> oi(rdbuf);

  boost::progress_timer pt;

  for(size_t i=1; i<=250; ++i)
  {
    ::std::copy(data.begin(), data.end(), oi);
    if(0==i%factor)
      rdbuf->pubsync();
  }

  ofs.flush();
  double rate = 500 / pt.elapsed();
  std::cout << rate << std::endl;
  return rate;
}

void count_avarage(const char* op_name, double (*fct)())
{
    double av_rate=0;
    const size_t repeat = 1;
    std::cout << "doing " << op_name << std::endl;
    for(size_t i=0; i<repeat; ++i)
        av_rate+=fct();

    std::cout << "average rate for " << op_name << ": " << av_rate/repeat 
            << "\n\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n"
            << std::endl;
}


int main()
{
    count_avarage("copy via streambuf iterator", do_copy_via_streambuf);
    return 0;
}
1 голос
/ 29 июля 2009

Почему бы просто не отобразить карту в файле? Отображение памяти является переносимым (за исключением Windows Vista, которая требует от вас прыгать в надежде использовать его сейчас, тупицы). Во всяком случае, сопоставьте ваш файл в памяти, и вы сами блокируете / не блокируете результирующую ячейку памяти.

ОС обрабатывает все блокировки, необходимые для фактического чтения с диска - вы НИКОГДА не сможете устранить эти издержки. Но, с другой стороны, ваши накладные расходы не будут затронуты посторонней блокировкой, кроме той, которую вы делаете сами.

1 голос
/ 29 июля 2009

Самый простой способ - прочитать весь файл в памяти, а затем предоставить свой собственный fgetc-подобный интерфейс для этого буфера.

1 голос
/ 29 июля 2009

fgetc почти наверняка не читает байт каждый раз, когда вы вызываете его (где «чтение» означает вызов системного вызова для выполнения ввода-вывода). Найдите где-нибудь еще свое узкое место в производительности, так как это, вероятно, не проблема, и использование небезопасных функций, безусловно, не является решением. Любая обработка блокировки, которую вы делаете, вероятно, будет менее эффективной, чем обработка, выполняемая стандартными процедурами.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...