Как ускорить загрузку 15M целых чисел из файлового потока?
У меня есть массив предварительно вычисляемых целых чисел, он фиксировал размер 15M. Мне нужно загрузить эти значения при запуске программы. В настоящее время для загрузки требуется до 2 минут, размер файла составляет ~ 130 МБ. Это способ ускорить загрузку. Я также могу изменить процесс сохранения.
std::array<int, 15000000> keys;
std::string config = "config.dat";
// how array is saved
std::ofstream out(config.c_str());
std::copy(keys.cbegin(), keys.cend(),
std::ostream_iterator<int>(out, "\n"));
// load of array
std::ifstream in(config.c_str());
std::copy(std::istream_iterator<int>(in),
std::istream_iterator<int>(), keys.begin());
in_ranks.close();
Спасибо заранее.
решаемые. Используется подход, предложенный в принятом ответе. Теперь это займет всего несколько минут.
Спасибо всем за понимание.
Ответы
Ответ 1
У вас есть два вопроса относительно скорости ваших операций записи и чтения.
Во-первых, std:: copy не может оптимизировать блок-копию при записи на output_iterator, поскольку он не имеет прямого доступа к базовой цели.
Во-вторых, вы пишете целые числа как ascii, а не двоичные, поэтому для каждой итерации вашей записи output_iterator создает ascii-представление вашего int, и после чтения он должен анализировать текст обратно в целые числа. Я считаю, что это основная проблема вашей производительности.
Необработанное хранилище вашего массива (предполагая 4 байта int) должно быть только 60 Мбайт, но поскольку каждый символ целого числа в ascii равен 1 байту, любые int с более чем 4 символами будут больше, чем двоичное хранилище, следовательно, ваш файл размером 130 МБ.
Нелегко решить проблему скорости (чтобы файл мог быть прочитан на разных машинах с конечным или внутренним размером) или при использовании std:: copy. Самый простой способ - просто выгрузить весь массив на диск, а затем прочитать его обратно, используя fstream.write и прочитать, просто помните, что он не является строго переносимым.
Чтобы написать:
std::fstream out(config.c_str(), ios::out | ios::binary);
out.write( keys.data(), keys.size() * sizeof(int) );
И читать:
std::fstream in(config.c_str(), ios::in | ios::binary);
in.read( keys.data(), keys.size() * sizeof(int) );
---- Обновление ----
Если вы действительно обеспокоены переносимостью, вы можете легко использовать переносимый формат (например, вашу начальную версию ascii) в своих артефактах распространения, а затем, когда программа запускается впервые, она может преобразовать этот переносимый формат в локально оптимизированную версию для использования в течение последующих казни.
Что-то вроде этого возможно:
std::array<int, 15000000> keys;
// data.txt are the ascii values and data.bin is the binary version
if(!file_exists("data.bin")) {
std::ifstream in("data.txt");
std::copy(std::istream_iterator<int>(in),
std::istream_iterator<int>(), keys.begin());
in.close();
std::fstream out("data.bin", ios::out | ios::binary);
out.write( keys.data(), keys.size() * sizeof(int) );
} else {
std::fstream in("data.bin", ios::in | ios::binary);
in.read( keys.data(), keys.size() * sizeof(int) );
}
Если у вас есть процесс установки, эта предварительная обработка также может быть выполнена в это время...
Ответ 2
если целые числа сохраняются в двоичном формате и вас не интересуют проблемы Endian, попробуйте прочитать весь файл в памяти сразу (fread) и нарисуйте указатель на int *
Ответ 3
Вы можете прекомпилировать массив в файл .o, который не нужно перекомпилировать, если данные не будут изменены.
thedata.hpp:
static const int NUM_ENTRIES = 5;
extern int thedata[NUM_ENTRIES];
thedata.cpp:
#include "thedata.hpp"
int thedata[NUM_ENTRIES] = {
10
,200
,3000
,40000
,500000
};
Чтобы скомпилировать это:
# make thedata.o
Тогда ваше основное приложение будет выглядеть примерно так:
#include "thedata.hpp"
using namespace std;
int main() {
for (int i=0; i<NUM_ENTRIES; i++) {
cout << thedata[i] << endl;
}
}
Предполагая, что данные не часто меняются, и что вы можете обрабатывать данные для создания thedata.cpp, тогда это фактически мгновенное время загрузки. Я не знаю, будет ли компилятор задыхаться от такого большого массива литералов!
Ответ 4
Внимание. Проверка реальности:
Чтение целых чисел из большого текстового файла - операция привязки ввода-вывода, если вы не делаете что-то совершенно неправильное (например, используя потоки С++ для этого). Загрузка 15M целых чисел из текстового файла занимает менее 2 секунд на AMD64 @3GHZ, когда файл уже буферизирован (и только немного длинный, если его нужно было извлечь с достаточно быстрого диска). Вот быстрая и грязная процедура, чтобы доказать свою точку зрения (почему я не проверяю все возможные ошибки в формате целых чисел и не закрываю мои файлы в конце, потому что я все равно выхожу()).
$ wc nums.txt
15000000 15000000 156979060 nums.txt
$ head -n 5 nums.txt
730547560
-226810937
607950954
640895092
884005970
$ g++ -O2 read.cc
$ time ./a.out <nums.txt
=>1752547657
real 0m1.781s
user 0m1.651s
sys 0m0.114s
$ cat read.cc
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <vector>
int main()
{
char c;
int num=0;
int pos=1;
int line=1;
std::vector<int> res;
while(c=getchar(),c!=EOF)
{
if (c>='0' && c<='9')
num=num*10+c-'0';
else if (c=='-')
pos=0;
else if (c=='\n')
{
res.push_back(pos?num:-num);
num=0;
pos=1;
line++;
}
else
{
printf("I've got a problem with this file at line %d\n",line);
exit(1);
}
}
// make sure the optimizer does not throw vector away, also a check.
unsigned sum=0;
for (int i=0;i<res.size();i++)
{
sum=sum+(unsigned)res[i];
}
printf("=>%d\n",sum);
}
UPDATE:, и здесь мой результат при чтении текстового файла (не двоичного) с помощью mmap:
$ g++ -O2 mread.cc
$ time ./a.out nums.txt
=>1752547657
real 0m0.559s
user 0m0.478s
sys 0m0.081s
код на pastebin:
Что я предлагаю
1-2 секунды - это реалистичная нижняя граница для обычной настольной машины для загрузки этих данных. 2 минуты звучат скорее как микроконтроллер с частотой 60 МГц с дешевой SD-карты. Таким образом, либо у вас есть необнаруженное/не указанное аппаратное условие, либо ваша реализация потока С++ как-то сломана или непригодна для использования. Я предлагаю установить нижнюю границу для этой задачи на вашей машине, запустив код образца.
Ответ 5
Сохраните файл в двоичном формате.
Запишите файл, указав указатель на начало массива int
и преобразуйте его в указатель char
. Затем напишите символы 15000000*sizeof(int)
в файл.
И когда вы читаете файл, сделайте то же самое в обратном порядке: прочитайте файл как последовательность символов, возьмите указатель на начало последовательности и преобразуйте его в int*
.
конечно, это предполагает, что утверждение не является проблемой.
Для фактического чтения и записи файла сопоставление памяти, вероятно, является наиболее разумным подходом.
Ответ 6
Если числа никогда не меняются, предварительно обработайте файл в источнике С++ и скомпилируйте его в приложение.
Если число может измениться, и, следовательно, вы должны сохранить их в отдельном файле, который необходимо загрузить при запуске, а затем избегать выполнения этого числа по числу с использованием потоков ввода-вывода С++. С++ IO-потоки - хорошая абстракция, но слишком много для такой простой задачи, как загрузка кучи числа быстро. По моему опыту, большая часть времени выполнения тратится на разбор чисел, а другая на доступ к файлу char на char.
(Предположим, что ваш файл больше, чем одна длинная строка.) Прочитайте файл строки за строкой, используя std::getline()
, разберите номера из каждой строки, используя не потоки, а std::strtol()
. Это позволяет избежать значительной части накладных расходов. Вы можете получить больше скорости из потоков, создав свой собственный вариант std::getline()
, который читает ввод вперед (используя istream::read()
); Стандарт std::getline()
также считывает ввод char на char.
Ответ 7
Используйте буфер размером 1000 (или даже 15M, вы можете изменить этот размер, как вам угодно) целыми числами, а не целыми после целого числа. На мой взгляд, проблема с использованием буфера явно не проблема.
Ответ 8
Если данные в файле двоичные и вам не нужно беспокоиться о endianess, и вы находитесь в системе, которая его поддерживает, используйте mmap. См. Эту статью на веб-сайте IBM:
Высокопроизводительное сетевое программирование, часть 2: Ускорение обработки как на клиенте, так и на сервере
Также см. это сообщение SO:
Когда следует использовать mmap для доступа к файлу?