Быстрый способ записи данных из std:: 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();
но это занимает много времени, чтобы закончить. Есть ли более быстрый или эффективный способ сделать это? Я хотел бы увидеть и узнать его.
6 ответов:
Ваш алгоритм состоит из двух частей:
сериализация двойных чисел в строковый или символьный буфер.
запись результатов в файл.
первый элемент может быть улучшен (> 20%) с помощью sprintf или fmt. Второй элемент может быть ускорен путем кэширования результатов в буфер или расширения размера буфера потока выходного файла перед записью результатов в выходной файл. Вы не должны использовать std:: endl, потому что это намного медленнее, чем использование "\n". Если вы все еще хотите сделать это быстрее, то пишите ваши данные в двоичном формате. Ниже приведен мой полный пример кода, который включает в себя мои предлагаемые решения и один из Эдгара Рокяна. Я также включил предложения Бена Фойта и Матье м в тестовый код.
#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 box с помощью Clang-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.
Я также Тест для числовых алгоритмов преобразования в строку для сравнения производительности 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.
из приведенных выше таблиц видно, что:
решение Эдгара Рокяна на 10% медленнее, чем решение stringstream. Решение, которое использует fmt библиотека является лучшим для трех изученных типов данных, которые являются double, int и size_t. sprintf + std:: векторное решение на 1% быстрее, чем fmt решение для типа данных Double. Однако я не рекомендую решения, которые используют sprintf для производственного кода, потому что они не являются элегантными (все еще написаны в стиле C) и не работают из коробки для разных типов данных, таких как int или size_t.
результаты тестов также показывают, что fmt является ли сериализация типа данных superrior integral, поскольку она по крайней мере в 7 раз быстрее, чем другие подходы.
мы можем ускорить этот алгоритм в 10 раз, если мы используем двоичный формат. Этот подход значительно быстрее, чем запись в форматированный текстовый файл, потому что мы делаем только сырую копию из памяти на выход. Если вы хотите иметь более гибкие и портативные решения, то попробуйте каши или boost:: сериализация или протокол-буфера. Согласно данное исследование производительности хлопья, кажется, самый быстрый.
std::ofstream fout("vector.txt"); fout << std::setprecision(10); for(auto const& x : vector) fout << x << '\n';
все, что я изменил, теоретически имело худшую производительность в вашей версии кода, но
std::endl
был настоящий убийца.std::vector::at
(с проверкой границ, которая вам не нужна) будет второй, а затем тот факт, что вы не использовали итераторы.почему по умолчанию-построить
std::ofstream
а потом позвонитеopen
, когда вы можете сделать это в один шаг? Зачем звонитьclose
когда RAII (деструктор) заботится об этом для вас? Вы также можете звонитеfout << std::setprecision(10)
только один раз, перед циклом.
как отмечено в комментарии ниже, если ваш вектор имеет элементы фундаментального типа, вы можете получить лучшую производительность с
for(auto x : vector)
. Измерьте время выполнения / проверьте выходные данные сборки.
просто чтобы отметить еще одну вещь, которая привлекла мое внимание, это:
for(l = 0; l < vector.size(); l++)
что это
l
? Зачем объявлять его вне цикла? Кажется, вам это не нужно во внешней области, так что нет. А также пост-инкремент.результат:
for(size_t l = 0; l < vector.size(); ++l)
Я сожалею, что сделал обзор кода из этого поста.
вы также можете использовать довольно аккуратную форму вывода содержимого любого
vector
в файл, с помощью итераторов и
ОК, мне грустно, что есть три решения, которые пытаются дать вам рыбу, но нет решения, которое пытается учить вас, как ловить рыбу.
Если у вас есть проблема с производительностью, решение состоит в том, чтобы использовать профилировщик и исправить любую проблему, которую показывает профилировщик.
преобразование double-to-string для 300 000 дублей не займет 3 минуты на любом компьютере, который был отправлен за последние 10 лет.
Запись 3 МБ данных на диск (средний размер 300,000 удваивается) не займет 3 минуты на любом компьютере, который был отправлен за последние 10 лет.
Если вы профиль это, я думаю, что вы найдете то, что Железный сбрасывается 300.000 времен, и что топить медленно, потому что это может повлечь блокировку или полу-блокирующий, в/О. Таким образом, вы должны избегать блокирования ввода/вывода. типичный способ сделать это, чтобы подготовить все ваши ввода/вывода в один буфер (создать stringstream, напишите, что), а потом писать, что буфер физическому файлу за один раз. Это решение hungptit описывает, за исключением того, что я думаю, что отсутствует объяснение, почему это решение является хорошим решением.
или, другими словами: профилировщик скажет вам, что вызов write() (в Linux) или WriteFile() (в Windows) намного медленнее, чем просто копирование нескольких байтов в буфер памяти, потому что это переход на уровне пользователя/ядра. Если std:: endl вызывает это для каждого двойника, у вас будет плохое (медленное) время. Заменить его чем-то, что просто остается в пространстве пользователя и помещает данные в оперативную память!
Если это все еще недостаточно быстро, может быть, что версия operator
у вас есть два основных узких мест в вашей программе: вывод и форматирование текста.
чтобы увеличить производительность, вы хотите увеличить объем вывода данных на вызов. Например, 1 выходная передача 500 символов выполняется быстрее, чем 500 передач 1 символа.
моя рекомендация-вы форматируете данные в большой буфер, а затем блокируете запись буфера.
вот пример:
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);
вы должны сначала попробовать изменить оптимизацию настройки в компиляторе, прежде чем возиться с кодом.
вот немного другое решение: сохраните ваши двойники в двоичной форме.
int fd = ::open("/path/to/the/file", O_WRONLY /* whatever permission */); ::write(fd, &vector[0], vector.size() * sizeof(vector[0]));
поскольку вы упомянули, что у вас есть 300k удваивается, что равно 300k * 8 байт = 2,4 м, вы можете сохранить все из них в локальный дисковый файл в менее 0,1 секунды. Единственный недостаток этого метода-сохраненный файл не так читаем, как строковое представление, но HexEditor может решить эту проблему.
Если вы предпочитаете более надежный способ, есть много сериализации библиотеки / инструменты доступны в режиме онлайн. Они обеспечивают больше преимуществ, таких как нейтральный к языку, машинно-независимый, гибкий алгоритм сжатия и т. д. Это те два, которые я обычно использую: