Очень удивительно, что fprintf vs std :: ofstream (fprintf очень медленный) - PullRequest
8 голосов
/ 24 октября 2011

Я провел несколько тестов, чтобы найти наиболее эффективный способ записи огромного массива в файл на C ++ (более 1Go в ASCII).

Поэтому я сравнил std :: ofstream с fprintf (см.переключатель, который я использовал ниже)

    case 0: {
        std::ofstream out(title, std::ios::out | std::ios::trunc);
        if (out) {
            ok = true;
            for (i=0; i<M; i++) {
                for (j=0; j<N; j++) {
                    out<<A[i][j]<<" ";
                }
                out<<"\n";
            }
            out.close();
        } else {
            std::cout<<"Error with file : "<<title<<"\n";
        }
        break;
    }
    case 1: {
        FILE *out = fopen(title.c_str(), "w");
        if (out!=NULL) {
            ok = true;
            for (i=0; i<M; i++) {
                for (j=0; j<N; j++) {
                    fprintf(out, "%d ", A[i][j]);
                }
                fprintf(out, "\n");
            }
            fclose(out);
        } else {
            std::cout<<"Error with file : "<<title<<"\n";
        }
        break;
    }

И моя огромная проблема в том, что fprintf кажется в 12 раз медленнее, чем std :: ofstream.У вас есть представление о происхождении проблемы в моем коде?Или, может быть, std :: ofstream очень оптимизирован по сравнению с fprintf?

(и другой вопрос: знаете ли вы еще один более быстрый способ записи файла)

Большое спасибо

(подробно: я компилировал с помощью g ++ -Wall -O3)

Ответы [ 5 ]

17 голосов
/ 24 октября 2011

fprintf("%d" требует разбора строки формата во время выполнения, один раз на целое число.ostream& operator<<(ostream&, int) разрешается компилятором, один раз за компиляцию.

4 голосов
/ 24 октября 2011

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

Поэтому я подозреваю, что ваш тест в некотором роде некорректен.

  1. Получаете ли вы последовательно 12-кратную разницу, если вы запускаете тесты повторно?
  2. Что произойдет с таймингами, если вы измените порядок, в котором вы выполняете тесты?
  3. Что произойдет, если вы позвоните fsync()/sync() в конце?
2 голосов
/ 24 октября 2011

В офсайте есть файловый буфер, это может сократить время доступа к диску. Кроме того, fprintf - это функция с переменными параметрами, которая будет вызывать некоторые функции va_ #, но ofstream не будет. Я думаю, что вы можете использовать fwrite () или putc () для проведения теста.

1 голос
/ 24 октября 2011

Я представляю здесь действительно оптимизированный способ записи целых чисел в текстовые файлы с использованием функций unix open, read и write. Они также доступны на окнах, просто предупреждаю, с которыми можно работать. Эта реализация работает только для 32-битного целого числа.

В вашем включаемом файле:

class FastIntegerWriter
{
private:

    const int bufferSize;
    int offset;
    int file;
    char* buffer;

public:

    FastIntegerWriter(int bufferSize = 4096);
    int Open(const char *filename);
    void Close();
    virtual ~FastIntegerWriter();
    void Flush();
    void Writeline(int value);
};

В вашем исходном файле

#ifdef _MSC_VER
# include <io.h>
# define open _open
# define write _write
# define read _read
# define close _close
#else
# include <unistd.h>
#endif
#include <fcntl.h>

FastIntegerWriter::FastIntegerWriter(int bufferSize) :
    bufferSize(bufferSize),
    buffer(new char[bufferSize]),
    offset(0),
    file(0)
{
}

int FastIntegerWriter::Open(const char* filename)
{
    this->Close();
    if (filename != NULL)
        this->file = open(filename, O_WRONLY | O_CREAT | O_TRUNC);
    return this->file;
}

void FastIntegerWriter::Close()
{
    this->Flush();
    if (this->file > 0)
    {
        close(this->file);
        this->file = 0;
    }
}

FastIntegerWriter::~FastIntegerWriter()
{
    this->Close();
    delete[] this->buffer;
}

void FastIntegerWriter::Flush()
{
    if (this->offset != 0)
    {
        write(this->file, this->buffer, this->offset);
        this->offset = 0;
    }
}

void FastIntegerWriter::Writeline(int value)
{
    if (this->offset >= this->bufferSize - 12)
    {
        this->Flush();
    }

    // Compute number of required digits

    char* output = this->buffer + this->offset;

    if (value < 0)
    {
        if (value == -2147483648)
        {
            // Special case, the minimum integer does not have a corresponding positive value.
            // We use an hard coded string and copy it directly to the buffer.
            // (Thanks to Eugene Ryabtsev for the suggestion).

            static const char s[] = "-2147483648\n";
            for (int i = 0; i < 12; ++i)
                output[i] = s[i];
            this->offset += 12;
            return;
        }

        *output = '-';
        ++output;
        ++this->offset;
        value = -value;
    }

    // Compute number of digits (log base 10(value) + 1)

    int digits =
        (value >= 1000000000) ? 10 : (value >= 100000000) ? 9 : (value >= 10000000) ? 8 : 
        (value >= 1000000) ? 7 : (value >= 100000) ? 6 : (value >= 10000) ? 5 : 
        (value >= 1000) ? 4 : (value >= 100) ? 3 : (value >= 10) ? 2 : 1;

    // Convert number to string

    output[digits] = '\n';
    for (int i = digits - 1; i >= 0; --i)
    {
        output[i] = value % 10 + '0';
        value /= 10;
    }

    this->offset += digits + 1;
}

Полагаю, это превзойдет любой другой метод записи в файл ascii :). Вы можете добиться большей производительности, используя низкоуровневые apis Windows WriteFile и ReadFile, но это не стоит усилий.

Чтобы использовать это ...

int main()
{
    FastIntegerWriter fw;
    fw.Open("test.txt");

    for (int i = -2000; i < 1000000; ++i)
        fw.Writeline(i);

    return 0;
}

Если вы не указываете какой-либо файл, он использует стандартный вывод (консоль).

1 голос
/ 24 октября 2011

Вы установили sync_with_stdio где-то перед кодом, который вы показали?

Хотя то, что вы сообщаете, противоположно тому, что эмпирически видно, большинство людей думают и верят в то, что вы видите, должно быть нормой. iostreams являются типобезопасными, тогда как семейство функций printf - это функции с переменными числами, которые должны выводить типы va_list из спецификатора формата.

...