Ваш алгоритм имеет два детали:
Сериализация двойных чисел в строку или буфер символов.
Написать результаты в файл.
Первый элемент может быть улучшена (> 20%) с помощью Sprintf или fmt. Второй элемент может ускоряться путем кэширования результатов в буфер или расширения размера буфера потока выходных файлов, прежде чем записывать результаты в выходной файл. Вы не должны использовать std :: endl, потому что it is much slower than using "\n". Если вы все еще хотите сделать это быстрее, тогда напишите свои данные в двоичном формате. Ниже приведен мой полный образец кода, который включает в себя мои предлагаемые решения и один от Эдгара Рокьяна. Я также включил предложения 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()));
}
Ниже приведены результаты работы, полученные в моем окне Linux с использованием лязг-3.9.1 и -O3 флаг. Я использую 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.
Я также тест для числовых алгоритмов преобразования строки для сравнения производительности станда :: stringstream, FMT :: MemoryWriter и зОго :: вектора.
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.
Из приведенных выше таблиц видно, что:
решение Edgar Rokyan 10% медленнее, чем решение stringstream. Решение, использующее библиотеку fmt, является лучшим для трех изученных типов данных, которые являются double, int и size_t. Решение sprintf + std :: vector на 1% быстрее, чем решение fmt для двойного типа данных. Тем не менее, я не рекомендую решения, которые используют sprintf для производственного кода, потому что они не изящны (все еще написаны в стиле C) и не работают из коробки для разных типов данных, таких как int или size_t.
Результаты тестов также показывают, что fmt является сериализацией типа интегрального интегратора, поскольку он по крайней мере в 7 раз быстрее, чем другие подходы.
Мы можем ускорить этот алгоритм 10x, если мы используем двоичный формат. Этот подход значительно быстрее, чем запись в форматированный текстовый файл, потому что мы делаем только исходную копию из памяти на выходе. Если вы хотите иметь более гибкие и портативные решения, попробуйте cereal или boost::serialization или protocol-buffer. Согласно this performance study, зерновые, кажется, самые быстрые.
Как большой ваш вектор? и как долго «какое-то время»? –
Что содержит ваш вектор? –
его, как 300k удваивает, и занимает почти 3 минуты. –