ofstream::operator‹‹(streambuf) — медленный способ копирования файла

Мне нужен кроссплатформенный, без внешней библиотеки, способ копирования файла. В моем первом проходе я придумал (обработка ошибок опущена):

char buffer[LEN];
ifstream src(srcFile, ios::in | ios::binary);
ofstream dest(destFile, ios::out | ios::binary);

while (!src.eof()) {
  src.read(buffer, LEN);
  dest.write(buffer, src.gcount());
}

Это прекрасно сработало, и я точно знал, что он делает.

Затем я нашел сообщение в stackoverflow (извините, не могу найти ссылку прямо сейчас), в котором говорится, что я могу заменить весь приведенный выше код на:

dest << src.rdbuf();

Это красиво и компактно, но многое скрывает о том, что он делает. Это также оказывается очень медленным , потому что реализация ofstream::operator‹‹(streambuf) перемещает вещи по 1 символу за раз (используя snetxc()/sputc()).

Есть ли способ сделать этот метод быстрее? Есть ли недостаток в моем оригинальном методе?

Обновление: есть что-то неэффективное в использовании оператора‹‹(streambuf) в Windows. Цикл .read()/.write() всегда работает лучше, чем оператор‹‹.

Также изменение размера буфера в приведенном выше коде не влияет на размер операций чтения и записи на жесткий диск. Для этого вам нужно установить буферы с помощью stream.rdbuf()->pubsetbuf().


person Erik    schedule 30.08.2011    source источник
comment
почему бы вам не define скопировать для каждой платформы? или использовать буст?   -  person Daniel A. White    schedule 31.08.2011
comment
См. stackoverflow.com /вопросы/829468/   -  person Daniel A. White    schedule 31.08.2011
comment
Как насчет dest.write(src.rdbuf(), size);?   -  person Kerrek SB    schedule 31.08.2011
comment
@Kerrek: write принимает char const*, но rdbuf возвращает std::filebuf - как это будет работать?   -  person ildjarn    schedule 31.08.2011
comment
Мне нужна кросс-платформа, без внешней библиотеки, так как я не уверен, какие обстоятельства потребуют, чтобы кому-то нужно было быть как кроссплатформенным, и запрещено использовать библиотеки. Тем более, что библиотеки — самые эффективные инструменты для кроссплатформенной разработки.   -  person Nicol Bolas    schedule 31.08.2011
comment
Каков средний размер этих файлов? Каков размер памяти вашего компьютера? Какие еще вещи работают? Если маленькие файлы, большая память, больше ничего не работает обычно - Храните партию в буфере. В противном случае нужно думать о компромиссе.   -  person Ed Heal    schedule 31.08.2011
comment
если вы используете простые вызовы c, вы увидите лучшую производительность. потоки хромают с точки зрения производительности. Не используйте их, если важна производительность   -  person Pavel P    schedule 31.08.2011
comment
Я не думаю, что стандартная библиотека может превзойти первый метод, который вы запрограммировали, хотя для больших файлов второй намного быстрее, потому что он буферизует меньшие фрагменты. Вам придется использовать внешнюю библиотеку. Я бы порекомендовал boost, который содержит функции для этого, является переносимым, и библиотеки C++0x были частично смоделированы по ним.   -  person Mooing Duck    schedule 31.08.2011
comment
@Pavel: потоки были разработаны для простой работы с большими файлами. Я считаю, что они лишь немного медленнее, чем C для небольших файлов.   -  person Mooing Duck    schedule 31.08.2011
comment
производительность не критична и память не ограничена. Но когда такая простая вещь, как добавление буфера размером 1 КБ, увеличивает производительность более чем в 20 раз, я задаюсь вопросом, не использую ли я эту вещь должным образом. У streambuf есть возможность работать с блоками, поэтому мне интересно, почему его нельзя настроить для этого.   -  person Erik    schedule 31.08.2011
comment
@Erik: Потому что скрипучее колесо получает смазку. Многие программисты вообще не используют iostreams. В свою очередь, разработчики библиотек не утруждают себя оптимизацией возможных вариантов использования. Кроме того, существует так много способов использования iostreams, что невозможно учесть их все. Этот трюк std::filebuf, например, вероятно, не первое, о чем разработчик может подумать как о проблеме с производительностью.   -  person Nicol Bolas    schedule 31.08.2011
comment
@ildjarn: О, действительно. Хорошо, как насчет src.rdbuf().pbase()?   -  person Kerrek SB    schedule 31.08.2011
comment
@Kerrek: pptr и pbase защищены, поэтому не напрямую. В любом случае, я на самом деле просто надеялся, что ты знаешь что-то, чего не знал я о std::filebuf, что позволило бы этому сработать. :-П   -  person ildjarn    schedule 31.08.2011
comment
Это на какой системе? Использование strace в Linux (GCC 4.5.1) показывает, что метод rdbuf() вызывает чтение/запись с использованием буфера размером 8 КБ. Мне интересно, не буферизована ли реализация fstream в вашей системе. Кроме того, вы пытались изменить размеры буфера чтения/записи в ваших потоках?   -  person Dave S    schedule 31.08.2011
comment
Windows VS2005. Я пробовал srcStream-›rdbuf()-›pubsetbuf(buffer), но очевидно, что это не работает, потому что проблема в ofstream::operator‹‹ с использованием sgetc/sputc. Есть ли другой размер буфера, который я должен настроить?   -  person Erik    schedule 31.08.2011
comment
Ну, я ожидаю, что узким местом будет чтение/запись отдельных символов на диск. Вы установили буферы чтения и записи? Реализация GCC, по-видимому, также выполняет sgetc/sputc, но это просто копирование памяти, запись на диск по-прежнему должна быть блоками за раз.   -  person Dave S    schedule 31.08.2011
comment
хорошее замечание, завтра еще раз проверю.   -  person Erik    schedule 31.08.2011
comment
Однако может быть, как вы сказали, их реализация игнорирует буферы. Мне было бы любопытно, если бы более поздние версии VS имели такое же поведение.   -  person Dave S    schedule 31.08.2011
comment
Я добавил несколько комментариев о своих выводах под ответом Дэйва ниже.   -  person Erik    schedule 31.08.2011


Ответы (3)


Интересно, не буферизуется ли ваш fstream по умолчанию. GCC 4.5.2 по умолчанию использует внутренний буфер, но я не думаю, что это требуется по стандарту. Пробовали ли вы использовать pubsetbuf (см. ниже), чтобы установить буфер для ваших входящих/исходящих потоков.

Быстрый тест в моей системе: если я установил LEN на 0 (и, следовательно, без буферизации), копирование файла размером 1 МБ заняло 10 секунд. С буфером 4k это завершилось менее чем за секунду.

#include <iostream>
#include <fstream>

int main() {
  using namespace std;
  const char* srcFile = "test.in";
  const char* destFile = "test.out";

  ifstream src;
  ofstream dest;

  const int LEN=8192;
  char buffer_out[LEN];
  char buffer_in[LEN];
  if (LEN) {
    src.rdbuf()->pubsetbuf(buffer_in, LEN );
    dest.rdbuf()->pubsetbuf(buffer_out, LEN);
  } else {
    src.rdbuf()->pubsetbuf(NULL, 0 );
    dest.rdbuf()->pubsetbuf(NULL, 0);
  }
  src.open(srcFile, ios::in | ios::binary);
  dest.open(destFile, ios::out | ios::binary);
  dest << src.rdbuf();

}
person Dave S    schedule 30.08.2011
comment
Хороший улов. В Windows (vs2005) поведение ofstream::operator‹‹ не использует буфер. - person Erik; 31.08.2011
comment
У меня есть некоторые интересные результаты, которые я не могу полностью объяснить в комментарии здесь. Но основы: оператор‹‹ и .read()/.write() используют буфер rdbuf() (по умолчанию он равен 4k). Изменение размера этого буфера при использовании оператора‹‹ действительно влияет на скорость, но она все еще не такая быстрая, как .read()/.write(). В операторе‹‹ есть что-то еще неэффективное, что действительно замедляет работу в Windows. - person Erik; 31.08.2011
comment
@Dave S вау - очень полезный ответ. Я просто часами задавался вопросом, почему ifstream, возвращаемый из метода, а не используемый непосредственно из вызывающего метода, был примерно в 10 раз медленнее - оказывается, это был буфер. - person David Hall; 29.09.2011

Конечно, метод src.rdbuf медленнее. Он выполняет чтение и запись одновременно. Если вы не копируете на другой жесткий диск или какое-либо сетевое или подключенное хранилище, это будет медленнее, чем чтение блока, а затем запись блока.

Тот факт, что код компактен, не делает его быстрее.


Поскольку вы не можете перегрузить operator<< для std::filebuf (поскольку он уже перегружен), вы мало что можете сделать. Лучше просто использовать метод, который работает достаточно хорошо.

person Nicol Bolas    schedule 30.08.2011
comment
Хм? Он чередует чтение с записью. Если LEN достаточно большой (может быть, несколько сотен КБ или около того), то не будет большой перегрузки диска. - person Adam Rosenfield; 31.08.2011
comment
Я не спрашиваю, почему один способ быстрее. У streambuf есть возможность работать с блоками, почему бы оператору не воспользоваться этим? - person Erik; 31.08.2011
comment
@Adam: я говорил о версии rdbuf. - person Nicol Bolas; 31.08.2011

Вместо этого попробуйте использовать C stdio API, во многих реализациях он может быть быстрее (см. -b4a2-36248f86a8ce/" rel="nofollow">эта тема для некоторых номеров), но не всегда. Например:

// Error checking omitted for expository purposes
char buffer[LEN];
FILE *src = fopen(srcFile, "rb");
FILE *dest = fopen(destFile, "wb");

int n;
while ((n = fread(buffer, 1, LEN, src)) > 0)
{
    fwrite(buffer, 1, n, dest);
}

fclose(src);
fclose(dest);
person Adam Rosenfield    schedule 30.08.2011
comment
да, приятно иметь язык, который не занимается черной магией за твоей спиной ;) - person Erik; 31.08.2011
comment
Некоторые реализации работают быстрее. Другие медленнее. В любом случае, ваша петля неверна. Попробуйте while ((n=fread(...))>0). На самом деле, если он столкнется с проблемой чтения до достижения конца файла, он войдет в бесконечный цикл. - person Jerry Coffin; 31.08.2011
comment
@Jerry: я сказал, что проверка ошибок опущена для пояснительных целей, но в любом случае теперь она должна работать, если fread обнаружит ошибку (что было бы очень необычно - наиболее вероятной причиной будет разрыв сетевого соединения при чтении из файла на сетевое хранилище, такое как AFS или NFS). - person Adam Rosenfield; 01.09.2011