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

我目前从vector写入一组双打到这样的文本文件:

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(); 

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

你的algorithm有两个部分:

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

  2. 将结果写入文件。

第一项可以通过使用sprintf或fmt来改进(> 20%)。 在将结果写入输出文件之前,可以通过将结果caching到缓冲区或扩展输出文件stream缓冲区大小来加速第二项。 你不应该使用std :: endl,因为它比使用“\ n”慢得多 。 如果您仍然希望使其更快,那么以二进制格式写入您的数据。 以下是我的完整代码示例,其中包括我提出的解决scheme,以及来自Edgar Rokyan的解决scheme。 我还在testing代码中包含了Ben Voigt和Matthieu M的build议。

 #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来收集所有的performance结果。

 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. 

我也基于数值到string转换algorithm来比较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解决scheme比stringstream解决scheme慢10%。 使用fmt库的解决scheme对于三种研究数据types(double,int和size_t)来说是最好的。 sprintf + std :: vector解决scheme比双数据types的fmt解决scheme快1%。 但是,我不推荐使用sprintf作为生产代码的解决scheme,因为它们不够优雅(仍然使用C风格编写),并且不适用于不同的数据types,如int或size_t。

  2. 基准testing结果还显示fmt是超级整数数据types序列化,因为它比其他方法至less快7倍。

  3. 如果我们使用二进制格式,我们可以加快这个algorithm10倍。 这种方法比写入格式化的文本文件要快得多,因为我们只做内存到输出的原始拷贝。 如果你想有更灵活和便携的解决scheme,那么试试谷类或boost :: serialization或协议缓冲区 。 根据这个performance研究谷物似乎是最快的。

 std::ofstream fout("vector.txt"); fout << std::setprecision(10); for(auto const& x : vector) fout << x << '\n'; 

我所改变的一切在理论上在你的代码版本中性能较差,但std::endl是真正的杀手 。 std::vector::at (与边界检查,你不需要)将是第二,那么你没有使用迭代器的事实。

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

 fout << std::setprecision(10) 

只是一次,在循环之前。

正如下面的评论所指出的,如果你的向量是基本types的元素,你可能会得到更好的性能for(auto x : vector) 。 测量运行时间/检查assembly输出。


只是指出另一个引起我注意的事情,这个:

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

这是什么? 为什么要在循环之外声明它? 看来你不需要它在外面的范围,所以不要。 还有后期增量 。

结果:

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

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

在迭代器和copyfunction的帮助下,您还可以使用相当整洁的forms将任何vector内容输出到文件中。

 std::ofstream fout("vector.txt"); fout.precision(10); std::copy(numbers.begin(), numbers.end(), std::ostream_iterator<double>(fout, "\n")); 

这个解决scheme在执行时间方面与LogicStuff的解决scheme实际上是一样的。 但是它也说明了如何使用单一的copyfunction来打印内容,正如我所想的那样,它看起来相当不错。

好吧,我很伤心,有三种解决方法试图给你一条鱼,但没有解决方法,试图教你如何钓鱼。

当出现性能问题时,解决scheme是使用分析器,并解决分析器显示的问题。

在过去的10年中,任何一台计算机上都不会花费3分钟时间来转换300000双精度的string。

在过去10年内出货的计算机上,3 MB数据写入磁盘(平均大小为30万双)将不会花费3分钟的时间。

如果你分析这个,我的猜测是你会发现fout被刷新了30万次,并且刷新很慢,因为它可能涉及到阻塞或者半阻塞I / O。 因此,您需要避免阻塞I / O。 这样做的典型方法是将所有I / O准备到单个缓冲区(创build一个stringstream,写入),然后将该缓冲区写入一个物理文件。 这是hungptit描述的解决scheme,除了我认为缺less的是解释为什么解决scheme是一个很好的解决scheme。

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

如果这还不够快,可能是string运算符<<()的特定精度版本很慢,或者涉及到不必要的开销。 如果是这样,那么在最终将整个缓冲区一次写入文件之前,可以使用sprintf()或其他可能更快的函数将数据生成到内存缓冲区,从而进一步加快代码速度。

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

为了提高性能,您需要增加每个呼叫的数据输出量。 例如,500个字符的1个输出传送比1个字符的500个传送快。

我的build议是将数据格式化成一个大的缓冲区,然后写入缓冲区。

这是一个例子:

 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); 

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

这是一个稍微不同的解决scheme:以二进制forms保存你的双打。

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

既然你提到你有300k双打,相当于300k * 8 bytes = 2.4M,你可以把它们全部保存到本地磁盘文件中, 不到0.1秒 。 这种方法唯一的缺点是保存的文件不如string表示可读,但是一个HexEditor可以解决这个问题。

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

  • 谷歌/ protobuf的
  • 的NetCDF