9

我正在运行一些基准测试,以找到将大型数组写入 C++ 文件的最有效方法(ASCII 中超过 1Go)。

所以我将 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 似乎比 std::ofstream 慢了 12 倍以上。您知道我的代码中问题的根源是什么吗?或者也许 std::ofstream 与 fprintf 相比非常优化?

(还有另一个问题:你知道另一种更快的写入文件的方法吗)

非常感谢你

(细节:我正在用 g++ -Wall -O3 编译)

4

5 回答 5

18

fprintf("%d"需要对格式字符串进行运行时解析,每个整数一次。ostream& operator<<(ostream&, int)由编译器解析,每次编译一次。

于 2011-10-24T14:59:31.547 回答
4

好吧,fprintf()在运行时确实需要做更多的工作,因为它必须解析和处理格式字符串。但是,鉴于您的输出文件的大小,我希望这些差异不会产生什么影响,并且希望代码受 I/O 限制。

因此,我怀疑您的基准在某些方面存在缺陷。

  1. 如果您重复运行测试,您是否始终获得 12 倍的差异?
  2. 如果您颠倒运行测试的顺序,时间会发生什么变化?
  3. 如果你fsync()/sync()最后打电话会发生什么?
于 2011-10-24T15:02:04.213 回答
2

ofstream中有一个文件缓冲区,这可能会减少访问磁盘的次数。另外,fprintf是一个带可变参数的函数,它会调用一些va_#函数,而ofstream不会。我认为你可以使用fwrite()或putc()来进行测试。

于 2011-10-24T15:30:18.720 回答
1

您是否在显示的代码上游某处设置了 sync_with_stdio?

虽然您报告的内容与经验上看到的相反,但大多数人认为并相信您所看到的应该是常态。iostreams 是类型安全的,而 printf 系列函数是可变参数函数,必须从格式说明符推断 va_list 的类型。

于 2011-10-24T15:00:16.550 回答
1

我在这里展示了一种使用 unix 函数打开、读取和写入在文本文件上写入整数的真正优化方法。它们也可以在 Windows 上使用,只是给你一些可以使用的警告。此实现仅适用于 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 文件的方法:) 使用 Windows 低级 apis WriteFile 和 ReadFile 可能会获得更高的性能,但这不值得付出努力。

要使用它...

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

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

    return 0;
}

如果您不指定任何文件,它将使用标准输出(控制台)。

于 2011-10-24T15:50:38.963 回答