将数据从std :: vector写入文本文件的快速方法

时间:2016-09-28 17:09:08

标签: c++ std ofstream

我目前在矢量中写了一组双打到这样的文本文件:

std::ofstream fout;
fout.open("vector.txt");

for (l = 0; l < vector.size(); l++)
    fout << std::setprecision(10) << vector.at(l) << std::endl;

fout.close();

但这需要花费很多时间才能完成。有没有更快或更有效的方法来做到这一点?我很乐意看到并学习它。

6 个答案:

答案 0 :(得分:72)

std::ofstream fout("vector.txt");
fout << std::setprecision(10);

for(auto const& x : vector)
    fout << x << '\n';

我改变的所有内容在理论上都会在您的代码版本中表现更差,但std::endl was the real killerstd::vector::at(带边界检查,你不需要)将是第二个,然后是你没有使用迭代器的事实。

为什么默认构建std::ofstream然后调用open,当你可以一步完成时?为什么在RAII(析构函数)为您处理它时调用close?你也可以打电话

fout << std::setprecision(10)

只有一次,在循环之前。

如下面的评论中所述,如果您的向量是基本类型的元素,那么使用for(auto x : vector)可能会获得更好的性能。测量运行时间/检查装配输出。

只是指出另一件引起我注意的事情:

for(l = 0; l < vector.size(); l++)

这是l是什么?为什么要在循环之外声明它?看来你在外部范围内不需要它,所以不要。还有post-increment

结果:

for(size_t l = 0; l < vector.size(); ++l)

我很抱歉在这篇文章中进行了代码审查。

答案 1 :(得分:33)

您的算法有两部分:

  1. 将双数序列化为字符串或字符缓冲区。

  2. 将结果写入文件。

  3. 使用sprintf或fmt可以改进第一项(> 20%)。在将结果写入输出文件之前,可以通过将结果缓存到缓冲区或扩展输出文件流缓冲区大小来加速第二项。你不应该使用std :: endl,因为it is much slower than using "\n"。如果您仍希望加快速度,请以二进制格式编写数据。下面是我的完整代码示例,其中包括我提出的解决方案和Edgar Rokyan的解决方案。我还在测试代码中包含了Ben Voigt和Matthieu M的建议。

    #include <algorithm>
    #include <cstdlib>
    #include <fstream>
    #include <iomanip>
    #include <iostream>
    #include <iterator>
    #include <vector>
    
    // https://github.com/fmtlib/fmt
    #include "fmt/format.h"
    
    // http://uscilab.github.io/cereal/
    #include "cereal/archives/binary.hpp"
    #include "cereal/archives/json.hpp"
    #include "cereal/archives/portable_binary.hpp"
    #include "cereal/archives/xml.hpp"
    #include "cereal/types/string.hpp"
    #include "cereal/types/vector.hpp"
    
    // https://github.com/DigitalInBlue/Celero
    #include "celero/Celero.h"
    
    template <typename T> const char* getFormattedString();
    template<> const char* getFormattedString<double>(){return "%g\n";}
    template<> const char* getFormattedString<float>(){return "%g\n";}
    template<> const char* getFormattedString<int>(){return "%d\n";}
    template<> const char* getFormattedString<size_t>(){return "%lu\n";}
    
    
    namespace {
        constexpr size_t LEN = 32;
    
        template <typename T> std::vector<T> create_test_data(const size_t N) {
            std::vector<T> data(N);
            for (size_t idx = 0; idx < N; ++idx) {
                data[idx] = idx;
            }
            return data;
        }
    
        template <typename Iterator> auto toVectorOfChar(Iterator begin, Iterator end) {
            char aLine[LEN];
            std::vector<char> buffer;
            buffer.reserve(std::distance(begin, end) * LEN);
            const char* fmtStr = getFormattedString<typename std::iterator_traits<Iterator>::value_type>();
            std::for_each(begin, end, [&buffer, &aLine, &fmtStr](const auto value) {
                sprintf(aLine, fmtStr, value);
                for (size_t idx = 0; aLine[idx] != 0; ++idx) {
                    buffer.push_back(aLine[idx]);
                }
            });
            return buffer;
        }
    
        template <typename Iterator>
        auto toStringStream(Iterator begin, Iterator end, std::stringstream &buffer) {
            char aLine[LEN];
            const char* fmtStr = getFormattedString<typename std::iterator_traits<Iterator>::value_type>();
            std::for_each(begin, end, [&buffer, &aLine, &fmtStr](const auto value) {            
                sprintf(aLine, fmtStr, value);
                buffer << aLine;
            });
        }
    
        template <typename Iterator> auto toMemoryWriter(Iterator begin, Iterator end) {
            fmt::MemoryWriter writer;
            std::for_each(begin, end, [&writer](const auto value) { writer << value << "\n"; });
            return writer;
        }
    
        // A modified version of the original approach.
        template <typename Container>
        void original_approach(const Container &data, const std::string &fileName) {
            std::ofstream fout(fileName);
            for (size_t l = 0; l < data.size(); l++) {
                fout << data[l] << std::endl;
            }
            fout.close();
        }
    
        // Replace std::endl by "\n"
        template <typename Iterator>
        void improved_original_approach(Iterator begin, Iterator end, const std::string &fileName) {
            std::ofstream fout(fileName);
            const size_t len = std::distance(begin, end) * LEN;
            std::vector<char> buffer(len);
            fout.rdbuf()->pubsetbuf(buffer.data(), len);
            for (Iterator it = begin; it != end; ++it) {
                fout << *it << "\n";
            }
            fout.close();
        }
    
        //
        template <typename Iterator>
        void edgar_rokyan_solution(Iterator begin, Iterator end, const std::string &fileName) {
            std::ofstream fout(fileName);
            std::copy(begin, end, std::ostream_iterator<double>(fout, "\n"));
        }
    
        // Cache to a string stream before writing to the output file
        template <typename Iterator>
        void stringstream_approach(Iterator begin, Iterator end, const std::string &fileName) {
            std::stringstream buffer;
            for (Iterator it = begin; it != end; ++it) {
                buffer << *it << "\n";
            }
    
            // Now write to the output file.
            std::ofstream fout(fileName);
            fout << buffer.str();
            fout.close();
        }
    
        // Use sprintf
        template <typename Iterator>
        void sprintf_approach(Iterator begin, Iterator end, const std::string &fileName) {
            std::stringstream buffer;
            toStringStream(begin, end, buffer);
            std::ofstream fout(fileName);
            fout << buffer.str();
            fout.close();
        }
    
        // Use fmt::MemoryWriter (https://github.com/fmtlib/fmt)
        template <typename Iterator>
        void fmt_approach(Iterator begin, Iterator end, const std::string &fileName) {
            auto writer = toMemoryWriter(begin, end);
            std::ofstream fout(fileName);
            fout << writer.str();
            fout.close();
        }
    
        // Use std::vector<char>
        template <typename Iterator>
        void vector_of_char_approach(Iterator begin, Iterator end, const std::string &fileName) {
            std::vector<char> buffer = toVectorOfChar(begin, end);
            std::ofstream fout(fileName);
            fout << buffer.data();
            fout.close();
        }
    
        // Use cereal (http://uscilab.github.io/cereal/).
        template <typename Container, typename OArchive = cereal::BinaryOutputArchive>
        void use_cereal(Container &&data, const std::string &fileName) {
            std::stringstream buffer;
            {
                OArchive oar(buffer);
                oar(data);
            }
    
            std::ofstream fout(fileName);
            fout << buffer.str();
            fout.close();
        }
    }
    
    // Performance test input data.
    constexpr int NumberOfSamples = 5;
    constexpr int NumberOfIterations = 2;
    constexpr int N = 3000000;
    const auto double_data = create_test_data<double>(N);
    const auto float_data = create_test_data<float>(N);
    const auto int_data = create_test_data<int>(N);
    const auto size_t_data = create_test_data<size_t>(N);
    
    CELERO_MAIN
    
    BASELINE(DoubleVector, original_approach, NumberOfSamples, NumberOfIterations) {
        const std::string fileName("origsol.txt");
        original_approach(double_data, fileName);
    }
    
    BENCHMARK(DoubleVector, improved_original_approach, NumberOfSamples, NumberOfIterations) {
        const std::string fileName("improvedsol.txt");
        improved_original_approach(double_data.cbegin(), double_data.cend(), fileName);
    }
    
    BENCHMARK(DoubleVector, edgar_rokyan_solution, NumberOfSamples, NumberOfIterations) {
        const std::string fileName("edgar_rokyan_solution.txt");
        edgar_rokyan_solution(double_data.cbegin(), double_data.end(), fileName);
    }
    
    BENCHMARK(DoubleVector, stringstream_approach, NumberOfSamples, NumberOfIterations) {
        const std::string fileName("stringstream.txt");
        stringstream_approach(double_data.cbegin(), double_data.cend(), fileName);
    }
    
    BENCHMARK(DoubleVector, sprintf_approach, NumberOfSamples, NumberOfIterations) {
        const std::string fileName("sprintf.txt");
        sprintf_approach(double_data.cbegin(), double_data.cend(), fileName);
    }
    
    BENCHMARK(DoubleVector, fmt_approach, NumberOfSamples, NumberOfIterations) {
        const std::string fileName("fmt.txt");
        fmt_approach(double_data.cbegin(), double_data.cend(), fileName);
    }
    
    BENCHMARK(DoubleVector, vector_of_char_approach, NumberOfSamples, NumberOfIterations) {
        const std::string fileName("vector_of_char.txt");
        vector_of_char_approach(double_data.cbegin(), double_data.cend(), fileName);
    }
    
    BENCHMARK(DoubleVector, use_cereal, NumberOfSamples, NumberOfIterations) {
        const std::string fileName("cereal.bin");
        use_cereal(double_data, fileName);
    }
    
    // Benchmark double vector
    BASELINE(DoubleVectorConversion, toStringStream, NumberOfSamples, NumberOfIterations) {
        std::stringstream output;
        toStringStream(double_data.cbegin(), double_data.cend(), output);
    }
    
    BENCHMARK(DoubleVectorConversion, toMemoryWriter, NumberOfSamples, NumberOfIterations) {
        celero::DoNotOptimizeAway(toMemoryWriter(double_data.cbegin(), double_data.cend()));
    }
    
    BENCHMARK(DoubleVectorConversion, toVectorOfChar, NumberOfSamples, NumberOfIterations) {
        celero::DoNotOptimizeAway(toVectorOfChar(double_data.cbegin(), double_data.cend()));
    }
    
    // Benchmark float vector
    BASELINE(FloatVectorConversion, toStringStream, NumberOfSamples, NumberOfIterations) {
        std::stringstream output;
        toStringStream(float_data.cbegin(), float_data.cend(), output);
    }
    
    BENCHMARK(FloatVectorConversion, toMemoryWriter, NumberOfSamples, NumberOfIterations) {
        celero::DoNotOptimizeAway(toMemoryWriter(float_data.cbegin(), float_data.cend()));
    }
    
    BENCHMARK(FloatVectorConversion, toVectorOfChar, NumberOfSamples, NumberOfIterations) {
        celero::DoNotOptimizeAway(toVectorOfChar(float_data.cbegin(), float_data.cend()));
    }
    
    // Benchmark int vector
    BASELINE(int_conversion, toStringStream, NumberOfSamples, NumberOfIterations) {
        std::stringstream output;
        toStringStream(int_data.cbegin(), int_data.cend(), output);
    }
    
    BENCHMARK(int_conversion, toMemoryWriter, NumberOfSamples, NumberOfIterations) {
        celero::DoNotOptimizeAway(toMemoryWriter(int_data.cbegin(), int_data.cend()));
    }
    
    BENCHMARK(int_conversion, toVectorOfChar, NumberOfSamples, NumberOfIterations) {
        celero::DoNotOptimizeAway(toVectorOfChar(int_data.cbegin(), int_data.cend()));
    }
    
    // Benchmark size_t vector
    BASELINE(size_t_conversion, toStringStream, NumberOfSamples, NumberOfIterations) {
        std::stringstream output;
        toStringStream(size_t_data.cbegin(), size_t_data.cend(), output);
    }
    
    BENCHMARK(size_t_conversion, toMemoryWriter, NumberOfSamples, NumberOfIterations) {
        celero::DoNotOptimizeAway(toMemoryWriter(size_t_data.cbegin(), size_t_data.cend()));
    }
    
    BENCHMARK(size_t_conversion, toVectorOfChar, NumberOfSamples, NumberOfIterations) {
        celero::DoNotOptimizeAway(toVectorOfChar(size_t_data.cbegin(), size_t_data.cend()));
    }
    

    以下是使用clang-3.9.1和-O3标志在我的Linux机箱中获得的性能结果。我使用Celero收集所有效果结果。

    Timer resolution: 0.001000 us
    -----------------------------------------------------------------------------------------------------------------------------------------------
         Group      |   Experiment    |   Prob. Space   |     Samples     |   Iterations    |    Baseline     |  us/Iteration   | Iterations/sec  | 
    -----------------------------------------------------------------------------------------------------------------------------------------------
    DoubleVector    | original_approa | Null            |              10 |               4 |         1.00000 |   3650309.00000 |            0.27 | 
    DoubleVector    | improved_origin | Null            |              10 |               4 |         0.47828 |   1745855.00000 |            0.57 | 
    DoubleVector    | edgar_rokyan_so | Null            |              10 |               4 |         0.45804 |   1672005.00000 |            0.60 | 
    DoubleVector    | stringstream_ap | Null            |              10 |               4 |         0.41514 |   1515377.00000 |            0.66 | 
    DoubleVector    | sprintf_approac | Null            |              10 |               4 |         0.35436 |   1293521.50000 |            0.77 | 
    DoubleVector    | fmt_approach    | Null            |              10 |               4 |         0.34916 |   1274552.75000 |            0.78 | 
    DoubleVector    | vector_of_char_ | Null            |              10 |               4 |         0.34366 |   1254462.00000 |            0.80 | 
    DoubleVector    | use_cereal      | Null            |              10 |               4 |         0.04172 |    152291.25000 |            6.57 | 
    Complete.
    

    我还对数字到字符串转换算法进行了基准测试,以比较std :: stringstream,fmt :: MemoryWriter和std :: vector的性能。

    Timer resolution: 0.001000 us
    -----------------------------------------------------------------------------------------------------------------------------------------------
         Group      |   Experiment    |   Prob. Space   |     Samples     |   Iterations    |    Baseline     |  us/Iteration   | Iterations/sec  | 
    -----------------------------------------------------------------------------------------------------------------------------------------------
    DoubleVectorCon | toStringStream  | Null            |              10 |               4 |         1.00000 |   1272667.00000 |            0.79 | 
    FloatVectorConv | toStringStream  | Null            |              10 |               4 |         1.00000 |   1272573.75000 |            0.79 | 
    int_conversion  | toStringStream  | Null            |              10 |               4 |         1.00000 |    248709.00000 |            4.02 | 
    size_t_conversi | toStringStream  | Null            |              10 |               4 |         1.00000 |    252063.00000 |            3.97 | 
    DoubleVectorCon | toMemoryWriter  | Null            |              10 |               4 |         0.98468 |   1253165.50000 |            0.80 | 
    DoubleVectorCon | toVectorOfChar  | Null            |              10 |               4 |         0.97146 |   1236340.50000 |            0.81 | 
    FloatVectorConv | toMemoryWriter  | Null            |              10 |               4 |         0.98419 |   1252454.25000 |            0.80 | 
    FloatVectorConv | toVectorOfChar  | Null            |              10 |               4 |         0.97369 |   1239093.25000 |            0.81 | 
    int_conversion  | toMemoryWriter  | Null            |              10 |               4 |         0.11741 |     29200.50000 |           34.25 | 
    int_conversion  | toVectorOfChar  | Null            |              10 |               4 |         0.87105 |    216637.00000 |            4.62 | 
    size_t_conversi | toMemoryWriter  | Null            |              10 |               4 |         0.13746 |     34649.50000 |           28.86 | 
    size_t_conversi | toVectorOfChar  | Null            |              10 |               4 |         0.85345 |    215123.00000 |            4.65 | 
    Complete.
    

    从上表中我们可以看到:

    1. Edgar Rokyan解决方案比stringstream解决方案慢10%。使用fmt库的解决方案最适合三种研究数据类型,即double,int和size_t。对于双数据类型,sprintf + std :: vector解决方案比fmt解决方案快1%。但是,我不建议将sprintf用于生产代码的解决方案,因为它们不优雅(仍然用C风格编写),并且对于不同的数据类型(如int或size_t)不能开箱即用。

    2. 基准测试结果还表明fmt是超级整数数据类型序列化,因为它比其他方法快至少7倍。

    3. 如果我们使用二进制格式,我们可以将此算法加速10倍。这种方法比写入格式化文本文件要快得多,因为我们只从内存到输出执行原始复制。如果您想拥有更灵活,更便携的解决方案,请尝试cerealboost::serializationprotocol-buffer。根据{{​​3}}谷物似乎是最快的。

答案 2 :(得分:21)

在迭代器和vector函数的帮助下,您还可以使用相当简洁的形式将任何copy的内容输出到文件中。

std::ofstream fout("vector.txt");
fout.precision(10);

std::copy(numbers.begin(), numbers.end(),
    std::ostream_iterator<double>(fout, "\n"));

这个解决方案在执行时间方面几乎与LogicStuff的解决方案相同。但它也说明了如何使用单个copy函数打印内容,正如我想的那样,它看起来非常好。

答案 3 :(得分:11)

好的,我很难过有三种解决方案试图给你一条鱼,但没有解决方法试图教你如何捕鱼。

当您遇到性能问题时,解决方案是使用分析器,并修复分析器显示的任何问题。

在过去10年中发布的任何计算机上,将双倍转换为300,000双打不会花费3分钟。

在过去10年中出货的任何计算机上,将3 MB数据写入磁盘(平均大小为300,000双打)不会花费3分钟。

如果你对此进行分析,我的猜测是你会发现fout被冲洗了300,000次,并且刷新速度很慢,因为它可能涉及阻塞或半阻塞I / O.因此,您需要避免阻塞I / O.这样做的典型方法是将所有I / O准备到单个缓冲区(创建字符串流,写入该字符串流),然后一次性将该缓冲区写入物理文件。这是hungptit描述的解决方案,除了我认为缺少的是解释为什么该解决方案是一个很好的解决方案。

或者,换句话说:分析器告诉你的是调用write()(在Linux上)或WriteFile()(在Windows上)要比将几个字节复制到内存缓冲区要慢得多,因为这是用户/内核级别的转换。如果std :: endl导致每个double发生这种情况,那么你将会遇到一个糟糕(缓慢)的时间。将其替换为仅停留在用户空间中的内容并将数据放入RAM中!

如果仍然不够快,可能是字符串上的运算符&lt;&lt;()的特定精度版本很慢或涉及不必要的开销。如果是这样,您可以通过使用sprintf()或其他一些可能更快的函数来进一步加速代码,以便在最终一次性将整个缓冲区写入文件之前将数据生成到内存缓冲区中。

答案 4 :(得分:5)

您的程序中有两个主要瓶颈:输出和格式化文本。

要提高性能,您需要增加每次调用的数据输出量。例如,500个字符的1次输出传输比1个字符的500次传输快。

我的建议是将数据格式化为大缓冲区,然后阻止写入缓冲区。

以下是一个例子:

char buffer[1024 * 1024];
unsigned int buffer_index = 0;
const unsigned int size = my_vector.size();
for (unsigned int i = 0; i < size; ++i)
{
  signed int characters_formatted = snprintf(&buffer[buffer_index],
                                             (1024 * 1024) - buffer_index,
                                             "%.10f", my_vector[i]);
  if (characters_formatted > 0)
  {
      buffer_index += (unsigned int) characters_formatted;
  }
}
cout.write(&buffer[0], buffer_index);

在弄乱代码之前,您应首先尝试更改编译器中的优化设置。

答案 5 :(得分:2)

这是一个稍微不同的解决方案:以二进制形式保存您的双打。

int fd = ::open("/path/to/the/file", O_WRONLY /* whatever permission */);
::write(fd, &vector[0], vector.size() * sizeof(vector[0]));

由于您提到您有300k双打,相当于300k * 8字节= 2.4M,您可以将所有这些双重保存到小于0.1秒的本地磁盘文件中。此方法的唯一缺点是保存的文件不像字符串表示那样可读,但HexEditor可以解决该问题。

如果您更喜欢更健壮的方式,可以在线提供大量序列化库/工具。它们提供了更多的好处,例如语言中立,机器无关,灵活的压缩算法等。这些是我经常使用的两个: