【问题标题】:Very surprising perfs of fprintf vs std::ofstream (fprintf is very slow)fprintf vs std::ofstream 的性能非常令人惊讶(fprintf 非常慢)
【发布时间】:2011-10-24 14:53:27
【问题描述】:

我正在运行一些基准测试,以找到将大型数组写入 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 似乎比 std::ofstream 慢了 12 倍以上。您知道我的代码中问题的根源是什么吗?或者可能 std::ofstream 与 fprintf 相比非常优化?

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

非常感谢

(详情:我是用 g++ -Wall -O3 编译的)

【问题讨论】:

  • 我认为你应该使用 fputs 而不是 fprintf 以获得更多类似的行为
  • @AndersK.: 不,fputs 相当于一个streambuf(未格式化); fprintf 是 ostream 的正确对应物。
  • @MSalters 你为什么这么认为? fprintf 包含变量参数列表处理,而 ostream 上的 operator
  • FWIW:This program,源自 OP 的程序片段,运行 switch 语句的任一分支所需的时间基本相同。

标签: c++ ofstream printf


【解决方案1】:

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

【讨论】:

    【解决方案2】:

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

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

    1. 如果您重复运行测试,您是否始终获得 12 倍的差异?
    2. 如果您颠倒运行测试的顺序,时间会发生什么变化?
    3. 如果最后调用fsync()/sync() 会发生什么?

    【讨论】:

      【解决方案3】:

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

      【讨论】:

      • putc 会比较慢,因为它只写一个字符,所以会比较慢。
      【解决方案4】:

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

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

      【讨论】:

        【解决方案5】:

        我在这里展示了一种使用 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;
        }
        

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

        【讨论】:

        • 请注意,如果传递了最大的负整数,value = -value 将无法正常工作,因为没有相应的正值。见stackoverflow.com/a/5165813/1353187
        • 正确。没想到写那段代码。处理它的最好和最简单的方法是在字符串中硬编码最负整数并编写 if (value == most_negative_integer) write_the_string
        • 这里展示了更快的实现:stackoverflow.com/q/4351371/103167
        猜你喜欢
        • 1970-01-01
        • 2020-09-17
        • 1970-01-01
        • 2022-06-11
        • 2021-05-24
        • 1970-01-01
        • 2016-12-19
        • 2020-01-20
        • 2010-10-24
        相关资源
        最近更新 更多