Я знаю, что многие оптимизации компилятора могут быть довольно эзотерическими, но мой пример настолько прост, что я хотел бы посмотреть, смогу ли я понять, если у кого-нибудь есть идея, что он может делать.
У меня есть текстовый файл 500 МБ. Я объявляю и инициализирую fstream:
std::fstream file(path,std::ios::in)
Мне нужно прочитать файл последовательно. Он разделен табуляцией, но длины полей неизвестны и варьируются от строки к строке. Фактический анализ, который мне нужно сделать для каждой строки, добавил очень мало времени к итогу (что меня очень удивило, так как я выполнял string :: find для каждой строки из getline. Я думал, что это будет медленно).
В общем, я хочу найти в каждой строке строку и прервать цикл, когда найду ее. У меня также есть увеличение и выплевывание номеров строк для моего собственного любопытства, я подтвердил, что это добавляет немного времени (5 секунд или около того) и позволяет мне увидеть, как оно проходит мимо коротких линий и замедляется на длинных.
У меня есть текст, который нужно найти как уникальную строку, помечающую eof, поэтому он должен искать каждую строку. Я делаю это на своем телефоне, поэтому я прошу прощения за проблемы с форматированием, но это довольно просто. У меня есть функция, принимающая мой fstream в качестве ссылки, а текст, который нужно найти как строку, и возвращающий std :: size_t.
long long int lineNum = 0;
while (std::getline (file, line))
{
pos = line.find(text);
lineNum += 1;
std::cout << std::to_string(lineNum) << std::endl;
if (pos != -1)
return file.tellg():
}
return std::string::npos;
Редактировать: Линси указал, что строка to_string здесь не нужна, спасибо. Как уже упоминалось, полное отсутствие вычисления и вывода номера строки экономит несколько секунд, что в моем предварительно оптимизированном примере составляет небольшой процент от общего числа.
Это успешно проходит через каждую строку и возвращает конечную позицию через 408 секунд. У меня было минимальное улучшение, когда я пытался поместить файл в поток строк или пропускал все во всем цикле (просто getline до конца, никаких проверок, поисков или отображений). Также не помогло предварительное резервирование огромного пространства для строки.
Похоже, что getline является полностью драйвером. Однако … если я скомпилирую с флагом / O2 (MSVC ++), я получу комически быстрее 26 секунд. Кроме того, нет видимого замедления на длинных линиях против коротких. Очевидно, что компилятор делает что-то совсем другое. Никаких претензий от меня, но есть мысли о том, как это достигается? В качестве упражнения я хотел бы попытаться заставить мой код выполняться быстрее перед оптимизацией компилятора.
Бьюсь об заклад, это как-то связано с тем, как getline манипулирует строкой. Было бы быстрее (увы, не могу проверить какое-то время) просто зарезервировать весь размер файла для строки и читать символ за символом, увеличивая мой номер строки, когда я передаю / n? Кроме того, будет ли компилятор использовать такие вещи, как mmap?
ОБНОВЛЕНИЕ: я отправлю код, когда вернусь домой этим вечером. Похоже, что просто отключение проверок во время выполнения снизило выполнение с 400 секунд до 50! Я попытался выполнить ту же функциональность, используя необработанные массивы в стиле c. Я не очень опытный, но это было достаточно легко, чтобы выгрузить данные в массив символов, и перебрать их в поисках новых строк или первой буквы моей целевой строки.
Даже в режиме полной отладки он доходит до конца и правильно находит строку за 54 секунды. 26 секунд с выключенными проверками и 20 секунд оптимизированы. Так что из моих неформальных, специальных экспериментов похоже, что строковые и потоковые функции становятся жертвами проверок во время выполнения? Я снова проверю, когда вернусь домой.
Причина такого резкого ускорения заключается в том, что иерархия классов iostream основана на шаблонах (std::ostream
на самом деле является typedef шаблона с именем std::basic_ostream
), и большая часть его кода находится в заголовках. C ++ iostreams принимают несколько вызовов функций для обработки каждого байта в потоке. Однако большинство из этих функций довольно тривиальны. При включении оптимизации большинство этих вызовов являются встроенными, предоставляя компилятору тот факт, что std::getline
по сути, копирует символы из одного буфера в другой, пока не найдет новую строку — обычно это «скрыто» под несколькими уровнями вызовов функций. Это может быть дополнительно оптимизировано, уменьшая накладные расходы на байт на порядки.
Поведение буфера на самом деле не меняется между оптимизированной и неоптимизированной версией, в противном случае ускорение будет еще выше.