Как ускорить загрузку 15М целых чисел из файлового потока? - PullRequest
13 голосов
/ 05 августа 2010

У меня есть массив предварительно вычисленных целых чисел, это фиксированный размер 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();

Заранее спасибо.

решено .Использовал подход, предложенный в принятом ответе.Теперь это всего лишь мгновение.

Спасибо всем за ваши идеи.

Ответы [ 8 ]

12 голосов
/ 05 августа 2010

У вас есть две проблемы, касающиеся скорости ваших операций записи и чтения.

Во-первых, std :: copy не может выполнить оптимизацию блочного копирования при записи в output_iterator, поскольку он не имеет прямого доступа к базовомуtarget.

Во-вторых, вы записываете целые числа как ascii, а не как двоичное, поэтому для каждой итерации записи output_iterator создает представление ascii для вашего int, и при чтении он должен анализировать текст обратно вцелые числа.Я считаю, что это основная проблема вашей производительности.

Необработанное хранилище вашего массива (при условии 4-байтового целого) должно быть только 60 МБ, но, поскольку каждый символ целого числа в ascii равен 1 байту, любые целые числа сбольше, чем 4 символа, будет больше, чем двоичное хранилище, следовательно, ваш файл 130 МБ.

Не существует простого способа решить вашу проблему скорости переноса (чтобы файл мог быть прочитан с другим порядком байтов или числомразмер машины) или при использовании std :: copy.Самый простой способ - просто выгрузить весь массив на диск и затем прочитать его обратно, используя fstream.write и read, просто помните, что он не является строго переносимым.

Для записи:

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

Если у вас есть процесс установки, эта предварительная обработка также может быть выполнена в это время ...

6 голосов
/ 05 августа 2010

Внимание. Проверка реальности впереди:

Чтение целых чисел из большого текстового файла является операцией, связанной с вводом-выводом, если вы не делаете что-то совершенно неправильно (например, для этого используются потоки C ++). Загрузка 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); 
}

ОБНОВЛЕНИЕ: и вот мой результат при чтении текстового файла (не двоичного) с использованием mmap:

$ g++ -O2 mread.cc
$ time ./a.out nums.txt
=>1752547657

real    0m0.559s
user    0m0.478s
sys     0m0.081s

код на вставке:

Что я предлагаю

1-2 секунды - это реалистичная нижняя граница для типичной настольной машины для загрузки этих данных. 2 минуты больше похоже на чтение микроконтроллера 60 МГц с дешевой SD-карты. Таким образом, либо у вас есть необнаруженное / не упомянутое аппаратное условие или , ваша реализация потока C ++ каким-то образом сломана или непригодна для использования. Я предлагаю установить нижнюю границу для этой задачи на вашем компьютере, запустив мой пример кода.

6 голосов
/ 05 августа 2010

Вы можете предварительно скомпилировать массив в файл .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, то это фактически мгновенное время загрузки. Я не знаю, захлопнул ли бы компилятор такой большой массив букв!

6 голосов
/ 05 августа 2010

, если целые числа сохранены в двоичном формате, и вас не беспокоят проблемы с порядком байтов, попробуйте сразу прочитать весь файл в память (fread) и привести указатель к int *

3 голосов
/ 05 августа 2010

Сохранить файл в двоичном формате.

Запишите файл, взяв указатель на начало массива int и преобразовав его в указатель char. Затем запишите 15000000*sizeof(int) символов в файл.

И когда вы читаете файл, делайте то же самое в обратном порядке: считываете файл как последовательность символов, берете указатель на начало последовательности и конвертируете его в int*.

Конечно, это предполагает, что порядковый номер не является проблемой.

Для фактического чтения и записи файла, вероятно, наиболее разумным является отображение памяти.

1 голос
/ 05 августа 2010

Если числа никогда не меняются, предварительно обработайте файл в исходный код C ++ и скомпилируйте его в приложение.

Если число может измениться и, следовательно, вы должны хранить их в отдельном файле, в который нужно загрузитьзапуска, затем избегайте делать это число по номеру, используя потоки ввода-вывода C ++.Потоки ввода-вывода C ++ - хорошая абстракция, но ее слишком много для такой простой задачи, как быстрая загрузка большого числа.По моему опыту, огромная часть времени выполнения тратится на разбор чисел, а другая - на доступ к файлу char с помощью char.

(Предполагается, что ваш файл содержит более одной длинной строки).используя std::getline(), анализируйте номера из каждой строки, используя не потоки, а std::strtol().Это позволяет избежать огромной части накладных расходов.Вы можете получить более высокую скорость из потоков, создав собственный вариант std::getline(), такой, который будет читать входные данные вперед (используя istream::read());стандарт std::getline() также читает входной символ за символом.

0 голосов
/ 05 августа 2010

Если данные в файле являются двоичными, и вам не нужно беспокоиться о порядке байтов, и вы работаете в системе, которая поддерживает их, используйте системный вызов mmap . Смотрите эту статью на сайте IBM:

Высокопроизводительное сетевое программирование, Часть 2. Ускорение обработки как на клиенте, так и на сервере

Также см. Этот пост:

Когда я должен использовать mmap для доступа к файлу?

0 голосов
/ 05 августа 2010

Используйте буфер из 1000 (или даже 15M, вы можете изменить этот размер по своему усмотрению) целых чисел, а не целых после целого числа. На мой взгляд, проблема не в том, чтобы использовать буфер.

...