Я знаю, что вывод на консоль является дорогостоящей операцией. В интересах читабельности кода иногда полезно вызывать функцию для вывода текста дважды, а не иметь длинную строку текста в качестве аргумента.
Например, насколько менее эффективно иметь
System.out.println("Good morning.");
System.out.println("Please enter your name");
против
System.out.println("Good morning.\nPlease enter your name");
В примере разница только в одном вызове, println()
но что, если это больше?
Относительно примечания, заявления, связанные с печатью текста, могут выглядеть странно при просмотре исходного кода, если текст для печати длинный. Предполагая, что сам текст не может быть сокращен, что можно сделать? Должен ли это быть случай, когда println()
будет сделано несколько звонков? Кто-то однажды сказал мне, что строка кода не должна превышать 80 символов (IIRC), так что бы вы сделали с
System.out.println("Good morning everyone. I am here today to present you with a very, very lengthy sentence in order to prove a point about how it looks strange amongst other code.");
То же самое верно для языков, таких как C / C ++, поскольку каждый раз, когда данные записываются в выходной поток, должен выполняться системный вызов, и процесс должен переходить в режим ядра (который очень дорогой)?
Ответы:
Здесь есть две «силы» в напряжении: производительность против читабельности.
Давайте сначала рассмотрим третью проблему - длинные строки:
Лучший способ реализовать это и сохранить читабельность - использовать конкатенацию строк:
Конкатенация String-constant происходит во время компиляции и никак не влияет на производительность. Строки читабельны, и вы можете просто двигаться дальше.
Теперь о:
против
Второй вариант значительно быстрее. Я посоветую примерно в 2 раза быстрее .... почему?
Потому что 90% (с большим пределом погрешности) работы не связаны с выгрузкой символов в вывод, а являются накладными расходами, необходимыми для защиты вывода для записи в него.
синхронизация
System.out
являетсяPrintStream
. Все известные мне Java-реализации внутренне синхронизируют PrintStream: см. Код на GrepCode! ,Что это значит для вашего кода?
Это означает, что каждый раз, когда вы звоните,
System.out.println(...)
вы синхронизируете модель памяти, вы проверяете и ожидаете блокировки. Любые другие потоки, вызывающие System.out, также будут заблокированы.В однопоточных приложениях влияние
System.out.println()
часто ограничивается производительностью ввода-вывода вашей системы, как быстро вы можете записывать в файл. В многопоточных приложениях блокировка может быть более серьезной проблемой, чем IO.смывание
Каждый отпечаток сбрасывается . Это приводит к очистке буферов и запускает запись на уровне консоли в буферы. Количество предпринимаемых здесь усилий зависит от реализации, но, как правило, следует понимать, что производительность сброса лишь в небольшой части связана с размером очищаемого буфера. Существуют значительные издержки, связанные с сбросом, когда буферы памяти помечаются как грязные, виртуальная машина выполняет ввод-вывод и так далее. Возложение этих накладных расходов один раз, а не дважды, является очевидной оптимизацией.
Некоторые цифры
Я собрал следующий маленький тест:
Код относительно прост, он многократно печатает короткую или длинную строку для вывода. Длинная строка содержит несколько новых строк. Он измеряет, сколько времени требуется для печати 1000 итераций каждой.
Если я запускаю его в Unix (Linux) в командной строке, и перенаправить
STDOUT
к/dev/null
, и распечатать фактические результатыSTDERR
, я могу сделать следующее:Вывод (в errlog) выглядит так:
Что это значит? Позвольте мне повторить последнюю строфу:
Это означает, что для всех намерений и целей, даже несмотря на то, что «длинная» строка примерно в 5 раз длиннее и содержит несколько новых строк, для вывода требуется примерно столько же времени, сколько для короткой строки.
Количество символов в секунду в долгосрочной перспективе в 5 раз больше, а истекшее время примерно одинаково .....
Другими словами, ваша производительность масштабируется относительно количества ваших отпечатков, а не того , что они печатают.
Обновление: что произойдет, если вы перенаправите в файл, а не в / dev / null?
Это намного медленнее, но пропорции примерно одинаковы ....
источник
"\n"
не может быть правильным ограничителем строки.println
автоматически завершит строку правильным символом (ами), но вставка\n
строки в вашу строку может вызвать проблемы. Если вы хотите сделать это правильно, вам может потребоваться использовать форматирование строки илиline.separator
системное свойство .println
намного чищеЯ не думаю, что наличие множества
println
s - это проблема дизайна вообще. Я вижу это в том, что это можно сделать с помощью статического анализатора кода, если это действительно проблема.Но это не проблема, потому что большинство людей не делают IO, как это. Когда им действительно нужно сделать много операций ввода-вывода, они используют буферизованные (BufferedReader, BufferedWriter и т. Д.), Когда вход буферизируется, вы увидите, что производительность достаточно похожа, и вам не нужно беспокоиться о наличии куча
println
или малоprintln
.Итак, чтобы ответить на оригинальный вопрос. Я бы сказал, неплохо, если вы используете
println
для распечатки несколько вещей, которые используют большинство людейprintln
.источник
В языках более высокого уровня, таких как C и C ++, это меньше проблем, чем в Java.
Прежде всего, C и C ++ определяют конкатенацию строк во время компиляции, так что вы можете сделать что-то вроде:
В таком случае объединение строк - это не просто оптимизация, которую вы можете в значительной степени, обычно (и т. Д.), Зависит от компилятора. Скорее, это прямо требуется стандартами C и C ++ (фаза 6 перевода: «Литеральные токены смежных строк объединяются»).
Хотя это происходит за счет небольшой дополнительной сложности компилятора и реализации, C и C ++ делают немного больше, чтобы скрыть сложность эффективного вывода результатов от программиста. Java гораздо больше похожа на ассемблер - каждый вызов
System.out.println
гораздо более непосредственно переводится в вызов основной операции для записи данных в консоль. Если вы хотите буферизовать для повышения эффективности, это должно быть предоставлено отдельно.Это означает, например, что в C ++, переписывая предыдущий пример, что-то вроде этого:
... обычно не будет 1 практически не влияют на эффективность. Каждое использование
cout
просто помещает данные в буфер. Этот буфер будет сброшен в нижележащий поток, когда буфер заполнится, или код попытается прочитать входные данные из использования (например, с помощьюstd::cin
).iostream
У s также естьsync_with_stdio
свойство, которое определяет, синхронизирован ли вывод из iostreams с вводом в стиле C (например,getchar
). По умолчаниюsync_with_stdio
установлено значение true, поэтому, если, например, вы записываетеstd::cout
, а затем читаете черезgetchar
, записанные вами данныеcout
будут сбрасываться приgetchar
вызове. Вы можете установитьsync_with_stdio
значение false, чтобы отключить это (обычно это делается для повышения производительности).sync_with_stdio
также контролирует степень синхронизации между потоками. Если синхронизация включена (по умолчанию), запись в iostream из нескольких потоков может привести к тому, что данные из потоков будут чередоваться, но предотвратит любые условия гонки. Итак, ваша программа будет работать и производить вывод, но если одновременно в поток записывается более одного потока, произвольное перемешивание данных из разных потоков обычно делает вывод довольно бесполезным.Если вы отключите синхронизацию, то ответственность за синхронизацию доступа из нескольких потоков также полностью ложится на вас. Одновременная запись из нескольких потоков может / приведет к гонке данных, что означает, что код имеет неопределенное поведение.
Резюме
По умолчанию C ++ пытается сбалансировать скорость с безопасностью. Результат довольно успешен для однопоточного кода, но менее для многопоточного кода. Многопоточный код обычно должен гарантировать, что только один поток записывает в поток за раз, чтобы произвести полезный вывод.
1. Можно отключить буферизацию для потока, но на самом деле это довольно необычно, и когда / если кто-то делает это, это, вероятно, по довольно конкретной причине, например, гарантируя, что весь вывод захватывается немедленно, несмотря на влияние на производительность. , В любом случае это происходит только в том случае, если код делает это явно.
источник
"2^31 - 1 = " + Integer.MAX_VALUE
хранится в виде одной строки интернировано (ПСБ Sec 3.10.5 и 15.28 ).Хотя производительность здесь не является проблемой, плохая читаемость множества
println
операторов указывает на отсутствующий аспект дизайна.Почему мы пишем последовательность из многих
println
утверждений? Если бы это был только один фиксированный текстовый блок, такой как--help
текст в консольной команде, было бы намного лучше иметь его в качестве отдельного ресурса, прочитать его и записать на экран по запросу.Но обычно это смесь динамических и статических частей. Допустим, у нас есть несколько простых данных заказа, с одной стороны, и несколько фиксированных статических текстовых частей, с другой стороны, и эти вещи нужно смешать вместе, чтобы сформировать лист подтверждения заказа. Опять же, и в этом случае лучше иметь отдельный текстовый файл ресурсов: ресурс будет шаблоном, содержащим какие-то символы (заполнители), которые во время выполнения заменяются фактическими данными заказа.
Отделение языка программирования от естественного языка имеет много преимуществ, среди которых есть интернационализация: вам может потребоваться перевести текст, если вы хотите стать многоязычным с вашим программным обеспечением. Кроме того, зачем нужен шаг компиляции, если вы хотите иметь только текстовую коррекцию, скажем, исправьте несколько орфографических ошибок.
источник