Как я могу ускорить чтение файла ASCII по строкам? (С++)

вот немного кода, который является значительным узким местом после выполнения некоторых измерений:

//-----------------------------------------------------------------------------
//  Construct dictionary hash set from dictionary file
//-----------------------------------------------------------------------------
void constructDictionary(unordered_set<string> &dict)
{
    ifstream wordListFile;
    wordListFile.open("dictionary.txt");

    std::string word;
    while( wordListFile >> word )
    {
        if( !word.empty() )
        {
            dict.insert(word);
        }
    }

    wordListFile.close();
}

Я читаю в ~200 000 слов и это занимает около 240 МС на моей машине. Является ли использование ifstream здесь эффективно? Могу я сделать лучше? Я читаю о mmap() реализаций, но я не понимаю их на 100%. Входной файл-это просто текстовые строки с окончаниями строк *nix.

EDIT: последующий вопрос для альтернатив предложенный: Бы любой alternative (минус увеличение размеров буфера потока) означает, что я пишу парсер, который проверяет каждый символ для новых строк? Мне вроде как нравится простой синтаксис потоков, но я могу переписать что-то более мелкое, если мне нужно для скорости. Чтение всего файла в память является жизнеспособным вариантом, это всего лишь около 2 Мб.

EDIT #2: я обнаружил, что замедление для меня было связано с установленной вставкой, но для тех, кто все еще заинтересованы в ускорении строки по строке файла IO, пожалуйста, прочитайте ответы здесь и проверить продолжение Матье м. На эту тему.

9 ответов


быстрое профилирование в моей системе (linux-2.6.37, gcc-4.5.2, скомпилированное с-O3) показывает, что ввод-вывод не является узким местом. Ли используя fscanf в массив символов, за которым следует dict.вставить () или operator>> как и в вашем точном коде, требуется примерно столько же времени (155 - 160 мс для чтения файла 240k word).

замена gcc std::unordered_set С std::vector<std::string> в вашем коде время выполнения падает до 45 мс (fscanf) - 55 МС (operator>>) для меня. Попробуйте профилировать IO и установить вставку отдельно.


вы могли бы получить лучшую производительность, как правило, путем увеличения размера буфера.

сразу после строительства ifstream, вы можете установить свой внутренний буфер, используя:

char LocalBuffer[4096]; // buffer

std::ifstream wordListFile("dictionary.txt");

wordListFile.rdbuf()->pubsetbuf(LocalBuffer, 4096);

Примечание: rdbufрезультат гарантированно не будет нулевым, если конструкция ifstream удалось

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

Я выполнил несколько простых измерений, используя небольшой собственный бенчмарк, вы можете найти код ниже (и меня интересуют критики):

gcc 3.4.2 на SLES 10 (sp 3)
C: 9.52725 e+06
C++: 1.11238 e+07
разница: 1.59655 e+06

что дает замедление криков 17%.

это происходит в учетная запись:

  • автоматическое управление памятью (без переполнения буфера)
  • автоматическое управление ресурсами (нет риска забыть закрыть файл)
  • обработка locale

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


соответствующий код, где benchmark немного полезности моей собственной, которая измеряет время повторного выполнения (здесь запущено для 50 итераций) с использованием gettimeofday.

#include <fstream>
#include <iostream>
#include <iomanip>

#include <cmath>
#include <cstdio>

#include "benchmark.h"

struct CRead
{
  CRead(char const* filename): _filename(filename) {}

  void operator()()
  {
    FILE* file = fopen(_filename, "r");

    int count = 0;
    while ( fscanf(file,"%s", _buffer) == 1 ) { ++count; }

    fclose(file);
  }

  char const* _filename;
  char _buffer[1024];
};

struct CppRead
{
  CppRead(char const* filename): _filename(filename), _buffer() {}

  enum { BufferSize = 16184 };

  void operator()()
  {
    std::ifstream file(_filename);
    file.rdbuf()->pubsetbuf(_buffer, BufferSize);

    int count = 0;
    std::string s;
    while ( file >> s ) { ++count; }
  }

  char const* _filename;
  char _buffer[BufferSize];
};


int main(int argc, char* argv[])
{
  size_t iterations = 1;
  if (argc > 1) { iterations = atoi(argv[1]); }

  char const* filename = "largefile.txt";

  CRead cread(filename);
  CppRead cppread(filename);

  double ctime = benchmark(cread, iterations);
  double cpptime = benchmark(cppread, iterations);

  std::cout << "C  : " << ctime << "\n"
               "C++: " << cpptime << "\n";

  return 0;
}

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

является ли 0.25 s на самом деле проблемой? Если вы не собираетесь загружать гораздо большие файлы, нужно ли делать это быстрее, если это делает его менее читаемым?


моя система (3.2.0-52-generic, g++-4.7 (Ubuntu / Linaro 4.7.3-2ubuntu1~12.04) 4.7.3, скомпилировано с -O2 если не указано, CPU: i3-2125)

в моих тестовых случаях я использовал словарь 295068 слов (так, есть на 100k больше слов, чем в вашем):http://dl.dropboxusercontent.com/u/4076606/words.txt

С точки зрения временной сложности:

  • в худшем случае ваша сложность программы: O(n*n)=O (n[читать данные]*n[вставить в данная])
  • средний случай сложность вашей программы: O(n)=O (n[читать данные]*1[вставить в unordered_set])

практические советы:

  • большинств простая структура данных имеет меньше накладных расходов времени. Простой массив быстрее, чем вектор. массив char быстрее строки. В интернете много информации об этом.

мои измерения:

примечание: Я не очистил кэш ОС и кэш HDD. Последний, которого я не могу. управление, но сначала можно управлять с помощью:

sync; sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches'

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

мой код:


14-16 МС для чтения из файла и вставки данных в 2D массив символов (чтение и вставка) n раз

65-75 МС поиск бинарный поиск все слова (поиск N раз):

Total=79-91 МС


61-78 МС для чтения из файла и вставки данных в unordered_set массив символов (чтение и вставка) n раз

7-9 МС to поиск по хэшу n раз

Total=68-87 МС


Если вы ищете больше раз, чем вставляете, выберите хэш-таблицу (unordered_set) в противном случае двоичный поиск (с простым матрица.)


ваш исходный код (поиск и вставка):

скомпилировано с -O2:157-182 ms

скомпилировано с-O0 (если опустить флаг-O, уровень"- O " по умолчанию также равен 0):223-248 МС

Итак, параметры компилятора также имеют значение, в этом случае это означает 66 МС повышение скорости. Вы не указали ни одного из них. Так что, думаю, ты его не использовал. Я пытаюсь ответить на твой главный вопрос. вопрос.


что вы можете сделать самое простое, но лучше с вашим текущим кодом?

  1. [лучшее использование API высокого уровня] используйте " getline (wordListFile, word)" Вместо "wordListFile >> word". Также я думаю, что "getline" более читаем, чем оператор">>".

скомпилировано с -O2:142-170 МС. ~ 12-15 МС ускорение по сравнению с исходным кодом.

скомпилировано с-O0 (если вы опустите флаг-O, уровень " - O по умолчанию тоже 0): 213-235 МС. ~ 10-13 МС ускорение по сравнению с исходным кодом.

  1. [лучшее использование API высокого уровня] избегайте перефразирования с помощью " dict.резерв (XXXXXX);", @David Rodríguez - dribeas также упоминал об этом. Если ваш словарь статичен или вы можете угадать размер словаря (например, по размеру файла, деленному на среднюю длину слова). Сначала запустите без " reserve "и выведите bucket_count (cout

скомпилировано с -O2:99-121 - [137] ms. ~ 33-43-[49] ms speed boost по сравнению с исходным кодом.

что вы можете сделать более продвинутым, чтобы ускорить его?

реализуйте свою собственную хэш-функцию для вашего конкретного ввода данных. Используйте массив char вместо строки STL. После того, как вы это сделали, только тогда напишите код с прямым OS I / O. Как вы заметили (из моих измерений также видно), что структура данных является узким местом. Если носитель очень медленный, но процессор очень быстрый, сжать файл распаковать его в вашей программе.


мой код не идеален, но все же он лучше, чем что-либо можно увидеть выше:http://pastebin.com/gQdkmqt8 (хэш-функция из интернета, также может быть выполнена лучше)


не могли бы вы предоставить более подробную информацию о том, для какой системы (одного или серии) вы планируете оптимизировать?

информация о временных сложностях: Должны быть ссылки... Но у меня не так много репутации, как у новичка в stackoverflow.

мой ответ все еще имеет отношение к чему-либо? Пожалуйста, добавьте комментарий или проголосуйте, поскольку нет PM, как я вижу.


библиотеки C++ и C читают материал с диска одинаково быстро и уже буферизованы, чтобы компенсировать задержку ввода-вывода диска. Вы не сделаете это быстрее, добавив больше буферизации.

самая большая разница заключается в том, что потоки C++ выполняют нагрузку манипуляций на основе локали. Преобразования символов/Punctuational и т. д./и т. д.

в результате библиотеки C будут быстрее.

Заменена Мертвая Ссылка

по какой-то причине связанный вопрос удаляемый. Поэтому я перемещаю соответствующую информацию сюда. Связанный вопрос касался скрытых функций в C++.


хотя не techncially части стл.
Библиотека streams является частью стандартных библиотек C++.

для потоков:

районов.

мало кто удосужился узнать, как правильно установить и/или манипулировать локаль потока.

вторая самая крутая вещь-итератор шаблоны.
В частности, для меня это итераторы потока, которые в основном превращают потоки в очень простые контейнеры, которые затем могут использоваться в сочетании со стандартными алгоритмами.

примеры:

  • знаете ли вы, что locales изменит '.- в десятичном числе к любому другому символу автоматически.
  • знаете ли вы, что локали будут добавлять", " каждая третья цифра, чтобы сделать его легко читать.
  • вы знали, что локали могут использоваться для управления текстом по пути (т. е. преобразование из UTF-16 в UTF-8 (при записи в файл).

etc.

примеры:


правильная реализация библиотеки ввода-вывода будет кэшировать данные для вас, избегая чрезмерных обращений к диску и системные вызовы. Я рекомендую вам использовать инструмент уровня системного вызова (например, strace, если вы находитесь под Linux), чтобы проверить, что на самом деле происходит с вашим IO.

очевидно, dict.insert(xxx) также может быть неприятностью, если он не позволяет вставлять O(1).


к сожалению, вы мало что можете сделать для повышения производительности при использовании fstream.

вы можете получить очень небольшое улучшение скорости, прочитав большие куски файла, а затем проанализировав отдельные слова, но это зависит от того, как ваша реализация fstream выполняет буферизацию.

единственный способ получить большое улучшение-использовать функции ввода-вывода вашей ОС. Например, в Windows открытие файла с флагом FILE_FLAG_SEQUENTIAL_SCAN может ускорить чтение вверх, а также использование асинхронного чтения для захвата данных с диска и параллельного их анализа.


Если вы действительно хотите быстро, ditch istream и string и создать тривиальный класс Read_Only_Text вокруг const char* & size, затем карта памяти файла и вставить в unordered_set<Read_Only_Text> со ссылками на встроенные строки. Это будет означать, что вы без необходимости сохраните файл 2mb, даже если ваше количество уникальных ключей может быть намного меньше, но он будет очень, очень быстро заполняться. Я знаю, что это боль, но я сделал это несколько раз для различных задач и результаты очень хорошие.


Верьте или нет, производительность потока stdlib при чтении данных намного ниже, чем у подпрограмм библиотеки C. Если вам нужна максимальная производительность чтения IO, не используйте потоки c++. Я обнаружил это на сайтах конкуренции алгоритмов-мой код попал бы в тайм-аут теста, используя потоки c++ для чтения stdin,но закончил бы много времени, используя простые операции с файлами C.

Edit: просто попробуйте эти две программы На некоторых образцах данных. Я запустил их на Mac OS X 10.6.6 с помощью G i686 в-яблоко-darwin10-г++++-4.2.1 (ССЗ) 4.2.1 (яблоком Inc. постройте 5664) на файле с 1 миллионом строк "howdythere", и версия scanf работает последовательно в 5 раз быстрее, чем версия cin:

#include <stdio.h>

int main()
{
    int count = 0;
    char buf[1024];
    while ( scanf("%s", buf) == 1 )
        ++ count;

    printf( "%d lines\n", count );
}

и

#include <iostream>

int main()
{
    char buf[1024];
    int count = 0;

    while ( ! std::cin.eof() )
    {
        std::cin.getline( buf, 1023 );
        if ( ! std::cin.eof() )
            ++count;
    }
   std::cout << count << " lines" << std::endl;
}

Edit: изменил файл данных на "howdythere", чтобы устранить разницу между двумя случаями. Результаты не изменились.

Edit: я думаю, что сумма процентов (и downvotes) в этом ответе показывает, насколько наоборот для общественного мнения реальность такова. Люди просто не могут поверить, что простой случай чтения ввода как в C, так и в потоках может быть настолько разным. Прежде чем вы downvote: идите измерить его самостоятельно. Дело не в том, чтобы установить тонны состояния (которые обычно никто не устанавливает), а только код, который люди чаще всего пишут. Мнение ничего не значит в исполнении: мера, мера, мера-это все, что имеет значение.