кэширование буфера std :: ifstream - PullRequest
8 голосов
/ 30 декабря 2010

В моем приложении я пытаюсь объединить отсортированные файлы (конечно, сохраняя их отсортированными), поэтому мне приходится перебирать каждый элемент в обоих файлах, чтобы записать минимальный в третий.Это работает довольно медленно на больших файлах, поскольку я не вижу другого выбора (итерация должна быть сделана), я пытаюсь оптимизировать загрузку файлов.Я могу использовать некоторое количество оперативной памяти, которое я могу использовать для буферизации.Я имею в виду, вместо того, чтобы читать 4 байта из обоих файлов каждый раз, когда я могу прочитать что-то вроде 100 Мб и работать с этим буфером после этого, пока в буфере не будет элемента, тогда я снова заполню буфер.Но я думаю, если поток уже делает это, это даст мне больше производительности и есть ли причина?Если fstream делает, может я могу изменить размер этого буфера?

добавлено

Мой текущий код выглядит так (псевдокод)

// this is done in loop
int i1 = input1.read_integer();
int i2 = input2.read_integer();
if (!input1.eof() && !input2.eof())
{
   if (i1 < i2)
   {
      output.write(i1);
      input2.seek_back(sizeof(int));
   } else
      input1.seek_back(sizeof(int));
      output.write(i2);
   }
} else {
   if (input1.eof())
      output.write(i2);
   else if (input2.eof())
      output.write(i1);
}

Что мне здесь не нравится, так это

  • seek_back - мне нужно вернуться к предыдущей позиции, поскольку нет способа посмотреть 4 байта
  • слишком много чтения из файла
  • если один из потоков находится в EOF, он все равно продолжает проверять этот поток, вместо того, чтобы помещать содержимое другого потока непосредственно в вывод, но это не является большой проблемой, поскольку размеры чанков почти всегда равны.

Можете ли вы предложить улучшение для этого?

Спасибо.

Ответы [ 6 ]

5 голосов
/ 30 декабря 2010

Не вдаваясь в обсуждение потоковых буферов, вы можете избавиться от seek_back и вообще сделать код намного проще, выполнив:

using namespace std;
merge(istream_iterator<int>(file1), istream_iterator<int>(),
           istream_iterator<int>(file2), istream_iterator<int>(),
           ostream_iterator<int>(cout));

Редактировать:

Добавлен двоичный файлвозможность

#include <algorithm>
#include <iterator>
#include <fstream>
#include <iostream>

struct BinInt
{
    int value;
    operator int() const { return value; }
    friend std::istream& operator>>(std::istream& stream, BinInt& data)
    {
        return stream.read(reinterpret_cast<char*>(&data.value),sizeof(int));
    }
};

int main()
{
    std::ifstream   file1("f1.txt");
    std::ifstream   file2("f2.txt");

    std::merge(std::istream_iterator<BinInt>(file1), std::istream_iterator<BinInt>(),
               std::istream_iterator<BinInt>(file2), std::istream_iterator<BinInt>(),
               std::ostream_iterator<int>(std::cout));
}
3 голосов
/ 30 декабря 2010

В порядке убывания производительности (лучше всего сначала):

  • ввод-вывод с отображением в память
  • ОС-специфичные ReadFile или read вызовы.
  • fread в большой буфер
  • ifstream.read в большой буфер
  • ifstream и экстракторы
2 голосов
/ 30 декабря 2010

Программа, подобная этой, должна быть связана с вводом / выводом, то есть она должна тратить как минимум 80% своего времени на ожидание завершения чтения или записи буфера, а если буферы достаточно велики, она должна сохранять дискголовы заняты.Это то, что вы хотите.

Не думайте, что это связано с вводом / выводом, без доказательств.Чтобы доказать это, нужно сделать несколько стеков .Если это так, большинство примеров покажет программу, ожидающую завершения ввода / вывода.

Возможно, что она не привязана к вводу / выводу, что означает, что в некоторых примерах могут происходить другие событиячто ты никогда не ожидал.Если это так, то вы знаете, что исправить, чтобы ускорить его.Я видел такой код, который тратит гораздо больше времени, чем необходимо, в цикле слияния, проверке конца файла, получении данных для сравнения и т. Д., Например.

0 голосов
/ 30 декабря 2010

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

Попробуйте открыть файл с ios::binary в качестве аргумента, затем используйте istream :: read для чтения данных.

Если вам нужна максимальная производительность, я бы фактически предложил вообще пропустить iostreams и использовать вместо этого cstdio . Но я думаю, это не то, что вы хотите.

0 голосов
/ 30 декабря 2010

Вы можете просто использовать функцию чтения ifstream для чтения больших блоков.

http://www.cplusplus.com/reference/iostream/istream/read/

Второй параметр - это количество байтов. Вы должны сделать это кратным 4 в вашем случае - может быть, 4096? :)

Просто читайте порцию за раз и работайте над ней.

Как сказал Мартин-Йорк, это может не оказать какого-либо положительного влияния на вашу производительность, но попробуйте и узнайте.

0 голосов
/ 30 декабря 2010

Если в ваших данных нет ничего особенного, маловероятно, что вы улучшите буферизацию, встроенную в объект std :: fstream.

Объекты std :: fstream спроектированы так, чтобы быть очень эффективными для доступа к файлам общего назначения. Не похоже, что вы делаете что-то особенное, получая доступ к данным 4 байта за раз. Вы всегда можете профилировать свой код, чтобы увидеть, где фактическое время тратится на ваш код.

Может быть, если вы поделитесь кодом с ous, мы можем заметить некоторые существенные недостатки.

Edit:

Мне не нравится твой алгоритм. Поиск назад и вперед может быть затруднен для потока, особенно из-за количества, лежащего за границей буфера. Я бы прочитал только один номер каждый раз в цикле.

Попробуйте это:
Примечание: это не оптимально (и предполагает потоковый ввод чисел (пока ваш выглядит двоичным)) Но я уверен, что вы можете использовать его как отправную точку.

#include <fstream>
#include <iostream>

// Return the current val (that was the smaller value)
// and replace it with the next value in the stream.
int getNext(int& val, std::istream& str)
{
    int result = val;
    str >> val;

    return result;
}

int main()
{
    std::ifstream   f1("f1.txt");
    std::ifstream   f2("f2.txt");
    std::ofstream   re("result");

    int v1;
    int v2;

    f1 >> v1;
    f2 >> v2;

    // While there are values in both stream
    // Output one value and replace it using getNext()
    while(f1 && f2)
    {
        re << (v1 < v2)? getNext(v1, f1) : getNext(v2, f2);
    }
    // At this point one (or both) stream(s) is(are) empty.
    // So dump the other stream.
    for(;f1;f1 >> v1)
    {
        // Note if the stream is at the end it will
        // never enter the loop
        re << v1;
    }
    for(;f2;f2 >> v2)
    {
        re << v2;
    }
}
...