Производительность ввода С++

Я пытался решить проблему на InterviewStreet. Через некоторое время я определяю, что на самом деле я тратил большую часть своего времени на чтение ввода. У этого конкретного вопроса было много входных данных, так что это имеет некоторый смысл. Что не имеет смысла, так это то, почему различные методы ввода имели такие разные характеристики:

Изначально у меня было:

std::string command;
std::cin >> command;

Его замена сделала его заметно быстрее:

char command[5];
cin.ignore();
cin.read(command, 5);

Переписав все, чтобы использовать scanf, стало еще быстрее

char command;
scanf("get_%c", &command);

В общем, я сократил время чтения ввода примерно на 1/3.

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


person Winston Ewert    schedule 01.02.2012    source источник


Ответы (4)


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

И там, где это происходит (оболочка Unix), код написан на C, читается непосредственно с устройства stdin и эффективен.

person Martin Beckett    schedule 01.02.2012
comment
Что я нахожу особенно забавным, так это то, как я прочитал в одной из книг по C++ (это была Библия C++?), что теоретически iostream может быть быстрее, чем функции stdio, потому что он использует виртуальные функции вместо разбора текста, такого как %s %s %d, но на практике это в 10-20 раз медленнее. Я причисляю это к теоретической возможности того, что Java на самом деле быстрее C, потому что виртуальная машина может профилировать код на лету. - person sashoalm; 01.02.2012
comment
@satuon: лично я нахожу iostream ужасным. Различные уровни косвенности и неинтуитивный интерфейс просто ошеломляют. Проблема iostream заключается в том, что его удержание восходит к ранней эпохе C++, когда шаблоны и исключения все еще находились в стадии изучения. Итак, у вас есть какое-то полусырое решение, которое заморожено из соображений обратной совместимости. вздыхает. - person Matthieu M.; 01.02.2012
comment
Получается, что iostream плохо реализован, потому что никто не использует его для высокопроизводительного ввода-вывода? - person Winston Ewert; 01.02.2012
comment
@WinstonEwert - предположительно он оптимизирован для гибкости и элегантности дизайна. Хотя на практике он, наверное, ни на что не оптимизирован, так как им никто не пользуется! - person Martin Beckett; 01.02.2012

Потоки ввода-вывода, которые рискуют быть отвергнутыми, в целом медленнее и громоздче, чем их аналоги C. Это не причина избегать их использования во многих целях, поскольку они более безопасны (когда-нибудь сталкивались с ошибкой scanf или printf? Не очень приятно) и более общие (например, перегруженный оператор вставки, позволяющий выводить пользовательские типы). Но я бы также сказал, что это не причина использовать их догматически в очень критичном по производительности коде.

Хотя я нахожу результаты немного удивительными. Из трех перечисленных вами, я бы подозревал, что это будет самым быстрым:

char command[5];
cin.ignore();
cin.read(command, 5);

Причина: не требуется выделение памяти и простое чтение символьного буфера. Это также верно для вашего примера C ниже, но вызов scanf для многократного чтения одного символа не является оптимальным даже на концептуальном уровне, поскольку scanf должен каждый раз анализировать строку формата, которую вы передали. Мне были бы интересны подробности вашего кода ввода-вывода, поскольку кажется, что существует разумная вероятность того, что что-то не так произойдет, когда вызовы scanf для чтения одного символа оказываются самыми быстрыми. Я просто должен спросить и не хотел обидеть, а действительно ли код скомпилирован и связан с включенными оптимизациями?

Теперь что касается вашего первого примера:

std::string command;
std::cin >> command;

Мы можем ожидать, что это будет немного медленнее, чем оптимально, по той причине, что вы работаете с контейнером переменного размера (std::string), который должен будет задействовать некоторые выделения кучи для чтения в желаемом буфере. Когда дело доходит до проблем стека и кучи, стек всегда значительно быстрее, поэтому, если вы можете предвидеть максимальный размер буфера, необходимый в конкретном случае, простой символьный буфер в стеке будет лучше, чем std::string для ввода (даже если вы использовали резерв). Это также верно для массива в стеке, в отличие от std::vector, но эти контейнеры лучше всего использовать в случаях, когда вы не можете заранее предсказать размер. Где std::string может быть быстрее, так это в тех случаях, когда у людей может возникнуть соблазн многократно вызывать strlen, где лучше хранить и поддерживать переменную размера.

Что касается деталей gprof, то он должен освещать эти проблемы. Вы смотрите на полный график вызовов, а не на плоский профиль? Естественно, в этом случае плоский профиль может ввести в заблуждение. Мне нужно знать некоторые дополнительные подробности о том, как вы используете gprof, чтобы дать лучший ответ.

person stinky472    schedule 01.02.2012
comment
gprof хорош (но не велик) для выявления узких мест в процессах, связанных с процессором. Это плохо (очень, очень плохо!) для выявления узких мест в процессах, связанных с вводом-выводом. - person David Hammen; 01.02.2012
comment
@David: авторы gprof написали статью об этом, но конечно, кто такие вещи читает? Все думают, что это инструмент для поиска узких мест, но они не заявляют об этом. - person Mike Dunlavey; 02.02.2012
comment
Подпрограммы stdio имеют много оптимизаций в своей истории и вспомогательных вызовах оптимизации производительности, таких как setvbuf(). Даже если концептуально scanf() кажется сложным, посмотрите на любую реализацию: этот код синтаксического анализа строк работает очень быстро. - person wallyk; 02.02.2012
comment
@wallyk: правда, но даже при самом быстром разборе строки в его примере это делается для каждого символа! Довольно неожиданно, что char buf[n]; cin.read(buf, n); будет намного медленнее (на самом деле удивительно, что это не быстрее, но iostreams - очень громоздкая и старая часть C++). - person stinky472; 02.02.2012

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

Почему-то это малоизвестно.

person Mike Dunlavey    schedule 01.02.2012

По умолчанию стандартные потоки ввода-вывода настроены для совместной работы, и на практике с библиотекой C stdio это означает, что использование cin и cout для вещей, отличных от интерактивного ввода и вывода, имеет тенденцию быть медленным.

Чтобы получить хорошую производительность при использовании cin и cout, нужно отключить синхронизацию со stdio. Для высокопроизводительного ввода вы можете даже развязать потоки.

Дополнительные сведения см. в следующем вопросе о стеке.

Как повысить производительность IOStream?

person Community    schedule 12.06.2016