Что быстрее printf или cout
Что быстрее, а что гибче: printf или cout?
Я всегда задавался вопросом о printf и cout. какой из них в конечном итоге быстрее, и является ли он также наиболее гибким (т.е. может печатать диапазон переменных, и вывод может быть отформатирован)?
PS Я знаю, что это похоже на «printf» против «cout» в C++, но на самом деле я не спрашиваю об этом.
2 ответа
Короткий ответ
Длинный ответ
По сравнению с семейством sprintf потоки C++ должны быть медленнее (в 6 раз, если я вспоминаю элемент Exceptional C++, написанный Хербом Саттером). Тем не менее, в большинстве случаев вам не понадобится эта скорость, но вы должны быть уверены, что ваш код не будет прослушиваться.
И легко сделать что-то не так с семейством функций printf, будь то неправильное количество аргументов, неправильные типы или даже потенциальная уязвимость безопасности (на ум приходит спецификатор%n) в вашем коде.
Если вы действительно не хотите этого (а потом это называется саботажем), почти невозможно ошибиться с потоками C++. Они легко обрабатывают все известные типы (встроенные модули, std::strings и т. Д.), И их легко расширить. Например, допустим, у меня есть объект «Coordinate3D», и я хочу распечатать его данные:
Проблема с потоком заключается в том, что с ними довольно трудно правильно обращаться, когда требуется указать формат некоторых данных (например, пробел для чисел), и что иногда вам действительно нужно идти быстро. Затем вернитесь к printf или попробуйте некоторые высокоскоростные альтернативы C++ (на ум приходит FastFormat).
Изменить: Обратите внимание, что серия тестов Томаса показывают интересные результаты (которые я воспроизвел прямо сейчас на моем компьютере), а именно: cout а также printf иметь аналогичные характеристики, когда кто-то избегает использования std::endl (который сбрасывает вывод в дополнение к выводу \n ).
Что лучше использовать: cout / wcout или printf? [закрыт]
Хотите улучшить этот вопрос? Переформулируйте вопрос так, чтобы он был сосредоточен только на одной проблеме.
Собственно, вопрос в заголовке. Пожалуйста, дайте подробный ответ.
1 ответ 1
Как завещал нам Страуструп, лучше потоки чем printf. Ибо потоки, как минимум, типобезопасны.
Представим, что в процессе работы пришлось сменить тип переменной (это встречается сплошь и рядом):
Как видно, при использовании потока ничего не поменялось. А при использовании printf пришлось править форматную строку. Если printf много в коде, то везде править форматную строку во всех printf очень утомительно и черевато ошибками.
Уж не знаю насколько потоки следствие идеи перегрузки операций. Может быть это и так. В любом случае, в варианте с printf тип операнда указывается два раза. Один раз в форматной строке, а второй раз собственно имя переменной говорит компилятору какой это тип. А два раза повторять одну и ту же информацию не есть хорошо. Потому что править приходится в двух местах, а не в одном месте. Так что идея с потоками (независимо от источника ее появления, будь то перегрузка операций или просто желание избавится от форматной строки) еще позволила избавится от ненужного дублирования информации.
Кстати, перегрузить операции можно было бы и с printf. Или в потоках оставить форматную строку. Ну если все делать через одно место конечно.
Что касается квалификации персонала, то квалификация постоянно растет. Старая школа может долбать любимый printf до посинения, пока самим не надоест вылавливать ошибки форматов. Новое поколение не знает таких ужасов и весело применяет потоки.
Кстати, вывод через потоки в общем случае быстрее, чем вывод через printf. Объясняется это тем, что при выводе через потоки разбор типов происходит на этапе компиляции и в рантайме разбора типов нет, а там есть только вывод значения. В случае же с printf происходит разбор форматной строки в рантайме, то есть компилированный код передает управление интерпретатору форматной строки. А, как известно, интерпретатор работает в 100-1000 раз медленнее, чем компилированный код.
Ранние трансляторы С++ создавали потоковый вывод как надстройку над выводом printf и разницы в скорости не было (но все равно была разница в типобезопасности, так как форматные строки формировались автоматически). Современные трансляторы С++ делают потоковый вывод как надо, без использования printf. И это (теоретически) повышает быстродействие вывода.
Так что скорость вывода это еще один аргумент, который заставляет программистов выбирать потоки вместо старого подхода с printf.
Но и объем скомпилированного кода больше.
Объем скомпилированного кода при выводе в поток не больше, чем при использовании printf.
В этом примере строки вывода
разворачиваются в обычные вызовы подпрограмм вывода, а вовсе не в последовательные повторения кода.
скорость встроенных при компиляции (как следствие реализации templates) преобразований выше.
Скорость вывода при использовании printf меньше именно из-за того, что разбор форматной строки происходит в рантайме. В случае использования потоков нет разбора форматной строки в рантайме и также нет оверхеда из-за шаблонов потому что там вставляется не шаблон, а обычный вызов подпрограммы вывода для соответствующего типа. Это легко проверить, если посмотреть ассемблерный код простого примера, приведенного выше. Транслятор Visual Studio 2017.
разворачивается в обычный вызов подпрограммы
и никаких шаблонов тут нет.
мы пишем программы не для компьютера, а для других программистов
Довольно спорное утверждение. Проще переписать весь код, чем править чужое. Ибо невозможно до конца понять, какие мысли (часто неверные) были у человека, когда он писал код.
Поэтому, используйте те конструкции, что вам представляются наиболее подходящими для понимания конкретного кода.
Нет, надо не только использовать то, чем владеешь, но и осваивать новые, более продвинутые инструменты вроде потоков, которые для нас придумывают разные головастые страуструпы. Если бы мы не осваивали новые инструменты, то мы бы до сих пор программировали в машинных кодах.
cout vs printf
printf > cout
Помогите переделать printf(«| %3.2f | %3.1f | %7.5f |\n»,a,b,y); на cout.
Cout в printf
Здравствуйте, может кто-нибудь помочь переделать cout в printf на 87-88 строках в данной программе.
В C++ и всё будет отлично работать.
Не печатает ‘\0’ А кому надо это печатать?! Если вы воспользуетесь функцией putchar, то она вам его напечатает.
Тоже должна его напечатать.(я не проверял, но думаю должна). Хотя что у вас будет на терминале не знаю. Потому что первые 32 символа непечатные, некоторые управляющие, но не этот. Если их начать печатать с терминалом начнут происходить всякие странные вещи. иногда оч полезное. Например если попробовать напечатать «\033[1mPiter\033[0m» то вы увидите слово Piter напчатаное жирным шрифтом. Можно сделать инверсию, мигание, расцечивать всякими цветами вплоть до rgb и всё с помощью печати esc последовательностей. Есть лр последовательности переключающие режимы терминала но ни в одну из них не входит символ ‘\0’
можно написать ненужное кол-во аргументов и их типы
да можно, но на каком нибудь оч древнем компиляторе потому что любой
не оч старый компилятор всегда не только ругнётся, но и скажет что именно не так. Если вы конечно не отключили предупреждения. но это уже вы сами виноваты.
Любой аргумент против можно разбить в пух и прах если только подумать, а не слушать всяких непонятных личностей ссылающихся на америкосов о том, что всё не так. своя голова на плечах должна быть! В этом всё дело.
printf работает в среднем раз в 10 быстрее чем cout. это известный факт. По сути cout это оч тормознутая штука.
printf гораздо старше чем cout итам уже всё давно исправлено и доведено до совершенства. а cout иногда глючит.
printf гораздо удобнее чем cout, потому что все действия пишутся водном формате где есть всё. и нчиего больше. Если вам надо выводить элементы в cout не по default режиму это заставвит вас применять многочисленные манипуляторы, что оч неудобно. а иногда даже не работает. Приходится выяснять почему.
printf есть вещи, недоступные в cout, например нумерация аргументов вывода с помощью %$1-$n вместо %. И это позволяет вам печатать аргументы не по порядку, а по номерам. некоторые можно печатаь по 2 и более раз если указать в формате один и тот же номер аргумента. Можно печатать число в локальном представлении, если указать флаг ‘ или локальный набор цифр с помощью флага I
ну и конечно вы можете динамически управлять точностью задав в формате *. В cout это тоже можно, но в printf удобнее
Не печатает объекты. Представьте себе cout их тоже не печатает. Если конечно вы не перегрузите специальный оператор 0
printf vs. std::cout [duplicate]
If I just want to print a string on screen, I can do that using those two ways:
7 Answers 7
Here’s an interesting example that I found here.
printf and its associated friends are C functions. They work in C++, but do not have the type safety of C++ std::ostream s. Problems can arise in programs that use printf functions to format output based on user input (or even input from a file). For example:
C++ has much stronger type safety (and a std::string class) to help prevent problems like these.
I struggle with this very question myself. printf is in general easier to use for formatted printing, but the iostreams facility in C++ has the big advantage that you can create custom formatters for objects. I end up using both of them in my code as necessary.
The problem with using both and intermixing them is that the output buffers used by printf and cout are not the same, so unless you run unbuffered or explicitly flush output you can end up with corrupted output.
My main objection to C++ is that there is no fast output formatting facility similar to printf, so there is no way to easily control output for integer, hex, and floating point formatting.
Java had this same problem; the language ended up getting printf.
Actually for your particular example, you should have asked which is preferable, puts or cout. printf prints formatted text but you are just outputting plain text to the console.
For general use, streams (iostream, of which cout is a part) are more extensible (you can print your own types with them), and are more generic in that you can generate functions to print to any type of stream, not just the console (or redirected output). You can create generic stream behaviour with printf too using fprintf which take a FILE* as a FILE* is often not a real file, but this is more tricky.
Streams are «typesafe» in that you overload with the type you are printing. printf is not typesafe with its use of ellipses so you could get undefined results if you put the wrong parameter types in that do not match the format string, but the compiler will not complain. You may even get a seg-fault / undefined behaviour (but you could with cout if used incorrectly) if you miss a parameter or pass in a bad one (eg a number for %s and it treats it as a pointer anyway).
printf does have some advantages though: you can template a format string then reuse that format string for different data, even if that data is not in a struct, and using formatting manipulations for one variable does not «stick» that format for further use because you specify the format for each variable. printf is also known to be threadsafe whereas cout actually is not.
boost has combined the advantages of each with their boost::format library.
The printf has been borrowed from C and has some limitations. The most common mentioned limitation of printf is type safety, as it relies on the programmer to correctly match the format string with the arguments. The second limitation that comes again from the varargs environment is that you cannot extend the behavior with user defined types. The printf knows how to print a set of types, and that’s all that you will get out of it. Still, it for the few things that it can be used for, it is faster and simpler to format strings with printf than with c++ streams.
So what should you use? In general I prefer using streams, as overloading operator for my own types is simple and they can be used uniformly with all types.
Использование printf с современным C++
Что понадобилось бы для модернизации printf? Этот вопрос может показаться странным многим разработчикам, считающим, что C++ уже предоставляет современную замену printf. Хотя C++ Standard Library, несомненно, есть чем похвалиться, в том числе превосходной Standard Template Library (STL), она также включает библиотеку ввода-вывода на основе потоков данных (streams), которая совершенно непохожа на STL и не реализует ни одного из принципов STL, относящихся к эффективности.
Согласно определению Александра Степанова и Дэниэля Роуза (Daniel Rose) в их книге «From Mathematics to Generic Programming» (Addison-Wesley Professional, 2015), «обобщенное программирование — это подход к разработке, который фокусируется на проектировании алгоритмов и структур данных, способных работать в наиболее универсальных условиях без потери эффективности».
Если честно, ни printf, ни cout ни в коей мере не отражают современный C++. Функция printf является примером вариативной функции (т. е. функции с переменным количеством аргументов) (variadic function) и одним из немногих хороших применений этой весьма хрупкой функциональности, унаследованной от языка программирования C. Такие функции предшествовали появлению вариативных шаблонов (variadic templates). Последние предлагают по-настоящему современный и надежный механизм для поддержки варьируемого количества типов или аргументов. В противоположность этому в cout вообще не применяется такая вариативность, но интенсивно используются вызовы виртуальных функций, с которыми компилятор не может сделать ничего особенного для их оптимизации. Развитие архитектур процессоров пошло так, что printf явно получает преимущества и мало что делается для повышения производительности полиморфического подхода, заложенного в cout. Поэтому, если вам нужны производительность и эффективность, лучше выбрать printf. Кроме того, ее применение дает более четкий код. Вот пример:
Спецификатор преобразования %f сообщает printf, что здесь ожидается число с плавающей точкой, которое следует преобразовать в десятичную нотацию. Символ \n — обычный символ новой строки, который можно дополнить символом перевода строки. Преобразование значений с плавающей точкой предполагает точность до шести знаков после десятичной точки. Таким образом, в этом примере будут выведены следующие знаки с последующей новой строкой:
Достижение того же результата с помощью cout поначалу кажется сравнительно простым:
Здесь cout полагается на оператор перегрузки, чтобы направить или послать число с плавающей точкой в поток вывода. Мне не нравится такое использование оператора перегрузки, но соглашусь, что это дело персонального стиля программирования. Наконец, endl завершает вставкой новой строки в поток вывода. Однако это не совсем то, что в примере с printf, и дает вывод с иной точностью после десятичной точки:
Тут возникает очевидный вопрос: как можно изменить точность соответствующих абстракций? Ну, если мне нужны лишь два знака после десятичной точки, можно просто указать это прямо в спецификаторе printf:
Теперь printf будет округлять число до двух знаков после десятичной точки:
Чтобы получить тот же результат в случае cout, потребуется набрать несколько больше кода:
Даже если вы не против многословия всего этого и вам нравится гибкость или выразительность такого варианта, учитывайте, что эта абстракция имеет свою цену. Прежде всего манипуляторы fixed и setprecision сохраняют состояние (stateful), а значит, их влияние остается до тех пор, пока вы не обратите их или не сбросите. В противоположность этому спецификатор преобразования в printf включает все, что нужно для одного конкретного преобразования, не влияя ни на какой другой код. Другие издержки могут не иметь никакого значения в большинстве случаев вывода, но в один прекрасный день вы вдруг заметите, что чужие программы способны выводить данные многократно быстрее, чем ваши. Помимо издержек от вызовов виртуальных функций, endl делает больше, чем вы, возможно, ожидали. Он не только отправляет знак новой строки в вывод, но и заставляет нижележащий поток сбрасывать свой вывод. При написании кода для любой разновидности ввода-вывода, будь то в консоль, файл на диске, сетевое соединение или даже в графическом конвейере, сброс обычно обходится очень дорого, а повторяющиеся сбросы, несомненно, приведут к падению производительности.
Теперь, когда я немного исследовал и сравнил printf и cout, пора вернуться к изначальному вопросу: что понадобилось бы для модернизации printf? Разумеется, с появлением современного C++, примером чего является C++11 и более поздние стандарты, я могу повысить продуктивность труда и надежность printf, не жертвуя производительностью. Другой отчасти посторонний член C++ Standard Library — официальный класс string языка. Хотя репутация этого класса за прошедшие годы тоже была опорочена, он все же обеспечивает превосходную производительность. Хоть и не безгрешный, он предоставляет очень полезный способ обработки строк в C++. Поэтому любая модернизация printf должна на практике хорошо уживаться со string и wstring. Давайте посмотрим, что можно сделать. Сначала позвольте мне устранить то, что я считаю самой досадной проблемой printf:
На самом деле это должно было бы работать, но, уверен, вы отчетливо понимаете, что вместо этого результатом будет то, что ласково называют «неопределенным поведением». Как вам известно, вся суть printf в выводе текста, а C++-класс string является главным воплощением текста в языке C++. Мне нужно обернуть printf так, чтобы это просто работало. Я не хочу постоянно выдергивать завершаемый нулем символьный массив строки таким образом:
Это просто утомительно, поэтому я намерен исправить это обертыванием printf. Традиционно при этом писали другую вариативную функцию. Например, нечто в таком духе:
Увы, это ничего не дает мне. Возможно, было бы удобно обернуть некую разновидность printf, чтобы записывать в какой-то другой буфер, но в данном случае я не получил бы ничего полезного. Я не хочу возвращаться к вариативным функциям в стиле C. Вместо этого я стремлюсь к использованию возможностей современного C++. К счастью, благодаря вариативным шаблонам в C++11 мне никогда больше не придется писать вариативную функцию. Вместо обертывания printf в другую вариативную функцию ее можно обернуть в вариативный шаблон:
Поначалу может показаться, что я выиграл не так уж много. Если бы я должен был вызывать функцию Print так:
это привело бы к раскрытию пакета аргументов args, состоящего из 123 и 456, внутри тела вариативного шаблона, словно я написал просто:
Так что же я выиграл? Конечно, я вызываю printf, а не vprintf, и мне не нужно управлять va_list и связанными макросами, крутящими стек, но, тем не менее, я просто пересылаю аргументы. Однако пусть вас не обманывает простота этого решения. Компилятор вновь будет распаковывать аргументы шаблона функции так, будто я напрямую вызвал printf, а значит, в обертывании printf таким способом не будет никаких издержек. Это также означает, что она по-прежнему остается полноправным элементом C++ и что я могу задействовать мощные методы метапрограммирования в этом языке для встраивания любого необходимого кода, причем с полным обобщением. Вместо простого раскрытия пакета параметров args можно обернуть каждый аргумент, чтобы добавить к нему любую настроечную информацию, необходимую printf. Рассмотрим следующий шаблон функции:
Этот код выглядит не делающим ничего особенного, и это действительно так, но теперь я могу раскрыть пакет параметров, чтобы обернуть каждый аргумент в одну из этих функций:
Функцию Print можно вызывать прежним образом:
Но теперь это приводит к следующему раскрытию:
Это очень интересно. Конечно, для этих целочисленных аргументов никакой разницы нет, но теперь можно перегружать функцию Argument для обработки строковых классов C++:
Далее я просто вызываю функцию Print с какими-нибудь строками:
Компилятор в конечном счете раскроет внутреннюю функцию printf следующим образом:
Это гарантирует, что завершаемый нулем массив символов каждой строки передается в printf и обеспечивает четко определенное поведение:
Наряду с шаблоном функции Print я также использую ряд перегруженных версий для неформатированного вывода. Это обычно безопаснее и избавляет printf от случайной интерпретации произвольных строк как содержащих спецификаторы преобразования. Эти функции перечислены на рис. 1.
Рис. 1. Отображение неформатированного вывода
Первые две перегруженные версии просто форматируют обычный и «широкосимвольный» массивы соответственно. Заключительный шаблон функции перенаправляет в соответствующую перегруженную версию в зависимости от типа аргумента: string или wstring. Благодаря этим функциям можно безопасно выводить некоторые спецификаторы преобразования в буквальном виде:
Это снимает самую распространенную проблему с printf, безопасно и прозрачно обрабатывая строковый вывод. А как насчет форматирования самих строк? C++ Standard Library предоставляет различные вариации printf для записи в буферы строк символов. Из них я нахожу наиболее эффективными snprintf и swprintf. Эти две функции обрабатывают символьный и широкосимвольный вывод соответственно. Они позволяют указывать максимальное количество символов, которое может быть записано, и возвращают значение, с помощью которого можно вычислить, какое пространство понадобится, если исходный буфер окажется недостаточно большим. Тем не менее, сами по себе они подвержены ошибкам и весьма утомительны в использовании. Пора внести в них кое-что из современного C++.
C не поддерживает перегрузку функций, но в C++ использовать ее весьма удобно, и это открывает дверь в обобщенное программирование, поэтому я начну с обертывания snprintf и swprintf как функций, вызываемых StringPrint. Кроме того, я задействую шаблоны вариативных функций, чтобы использовать преимущества безопасного раскрытия аргументов, ранее примененного для функции Print. На рис. 2 приведен код для обеих функций. Эти функции также проверяют, что результат не равен –1, а именно такое значение возвращают нижележащие функции, когда возникает какая-либо поправимая проблема при разборе форматирующей строки. Я использую контрольное выражение (assertion), так как просто предполагаю, что это ошибка и что ее надо исправить до распространения производственного кода. Возможно, вы захотите заменить его на исключение, но учитывайте, что надежного способа преобразования всех ошибок в исключения нет, поскольку все равно можно передать недопустимые аргументы, которые приведут к неопределенному поведению. Современный C++ не является защищенным от дураков C++.
Рис. 2. Низкоуровневые функции форматирования строки
Функции StringPrint обеспечивают обобщенный способ операций с форматированием строк. Теперь можно сосредоточиться на специфике класса string, и работа с ним по большей части требует управления памятью. Я хотел бы писать код примерно так:
Здесь нет видимого управления буферами. Мне не нужно выяснять, насколько большой буфер требуется создать. Я лишь прошу функцию Format логически присвоить отформатированный вывод объекту string. Как обычно, Format может быть шаблоном функции, а именно вариативным:
Реализовать эту функцию можно самыми разными способами. Немного экспериментов и хорошей дозы профилирования вполне достаточно. Простой, но наивный подход — предположить, что строка либо пуста, либо слишком мала, чтобы хранить форматированный вывод. В этом случае я начал бы с определения необходимого размера с помощью StringPrint и подгонки буфера под этот размер, а затем снова вызвал бы StringPrint с правильным буфером. Нечто вроде этого:
Приращение на 1 необходимо, так как и snprintf, и swprintf предполагают, что сообщаемый размер буфера включает место для завершающего нулевого символа. Это работает достаточно хорошо, но должно быть очевидно, что с производительностью здесь все очень плохо. Подход, обеспечивающий гораздо более высокую производительность в большинстве случаев, — предполагать, что строка достаточно велика, чтобы хранить форматированный вывод и выполнять подгонку размера буфера только при необходимости. Это почти выворачивает предыдущий код наизнанку, но в этом случае код весьма надежен. Я начинаю с попытки отформатировать строку непосредственно в буфере:
Если строка изначально пуста или недостаточно велика, полученный размер окажется больше размера строки, и я буду знать, что размер строки надо изменить до повторного вызова StringPrint:
Если полученный размер меньше размера строки, значит, форматирование успешно выполнено, но буфер нужно отсечь под этот размер:
Наконец, если размеры совпадают, ничего делать не надо и функция Format может просто вернуть управление. Полный шаблон функции Format представлен на рис. 3. Если вы знакомы с классом string, то, возможно, вспомните, что он также сообщает свою вместимость (capacity), и у вас может возникнуть соблазн присвоить размеру строки его вместимость до первого вызова StringPrint, полагая, что это улучшит ваши шансы на корректное форматирование строки с первого захода. Вопрос в том, а можно ли изменить размер объекта string быстрее, чем printf сумеет разобрать его форматирующую строку и вычислить необходимый размер буфера. Основываясь на своих неформальных тестах, могу дать ответ: когда как. Видите ли, изменение размера string для соответствия его вместимости требует несколько большего простого изменения сообщаемого размера. Нужно очистить любые дополнительные символы, а это требует времени. Окажется ли это быстрее, чем printf разберет форматирующую строку, зависит от того, сколько символов придется очистить и насколько сложным должно быть форматирование. Я применяю даже более скоростной алгоритм для вывода больших объемов строковых данных, но нахожу, что функция Format на рис. 3 обеспечивает хорошую производительность в большинстве сценариев.
Рис. 3. Форматирование строк
Располагая функцией Format, становится очень легко писать различные вспомогательные функции для распространенных операций форматирования строк. Возможно, вам нужно преобразовать широкосимвольную строку в обычную:
Или отформатировать числа с плавающей точкой:
Для одержимых производительностью такие функции специализированных преобразований легко поддаются оптимизации, поскольку требуемые размеры буферов вполне предсказуемы, но это я оставлю вам для самостоятельного исследования.
Это просто набор полезных функций из моей библиотеки вывода для современного C++. Надеюсь, они в какой-то мере подскажут вам, как использовать современный C++ для обновления старых методов программирования на C и C++. Кстати, в моей библиотеке вывода определены функции Argument, а также низкоуровневые функции StringPrint во вложенном пространстве имен Internal. Это помогает держать библиотеку легко читаемой и простой для понимания, но вы можете упорядочить свою реализацию как пожелаете.