Почему System.out.println такой медленный? - PullRequest
12 голосов
/ 14 декабря 2010

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

РЕДАКТИРОВАТЬ: Например, Java может найти все простые числа до 1 миллиона меньше, чем за секунду - но печать каждого из них в отдельности может занять минуты!До 10 миллиардов баночных часов для печати!

EX:

package sieveoferatosthenes;
public class Main {
    public static void main(String[] args) {
        int upTo = 10000000;
        boolean primes[] = new boolean[upTo];
        for( int b = 0; b < upTo; b++ ){
            primes[b] = true;
        }
        primes[0] = false;
        primes[1] = false;

        int testing = 1;

        while( testing <= Math.sqrt(upTo)){
            testing ++;
            int testingWith = testing;
            if( primes[testing] ){
                while( testingWith < upTo ){
                    testingWith = testingWith + testing;
                    if ( testingWith >= upTo){
                    }
                    else{
                        primes[testingWith] = false;
                    }

                }
            }
        }
        for( int b = 2; b < upTo; b++){
            if( primes[b] ){
                System.out.println( b );
            }
        }
    }
}

Ответы [ 7 ]

24 голосов
/ 14 декабря 2010

println не медленный, это базовый PrintStream, который связан с консолью, предоставляемой операционной системой хостинга.

Вы можете проверить это сами: сравните дамп большого текстового файла сконсоль с передачей того же текстового файла в другой файл:

cat largeTextFile.txt
cat largeTextFile.txt > temp.txt

Чтение и запись похожи и пропорциональны размеру файла (O (n)), единственное отличие состоит в том, что назначение отличается (консоль по сравнению с файлом).И это в основном то же самое с System.out.


Базовая операция ОС (отображение символов в окне консоли) идет медленно, потому что

  1. Байты должны быть отправленыконсольное приложение (должно быть довольно быстрым)
  2. Каждый символ должен отображаться с использованием (обычно) шрифта истинного типа (это довольно медленно, отключение сглаживания может повысить производительность, кстати)
  3. Возможно, придется прокрутить отображаемую область, чтобы добавить новую строку в видимую область (лучший вариант: операция передачи битового блока, наихудший случай: повторное отображение всей текстовой области)
5 голосов
/ 14 декабря 2010

System.out является статическим PrintStream классом.PrintStream имеет, помимо прочего, те методы, с которыми вы, вероятно, достаточно хорошо знакомы, например print() и println() и т.,"долго."печать или запись в PrintStream занимает доли секунды, но более 10 миллиардов экземпляров этого отпечатка могут составить довольно много!

Вот почему ваше «перемещение всего в строку» - этобыстрый.Ваша огромная строка построена, но вы печатаете ее только один раз .Конечно, это огромная печать, но вы тратите время на фактическую печать, а не на накладные расходы, связанные с print() или println().

Как упоминал Dvd Prd, строки неизменяемы.Это означает, что всякий раз, когда вы присваиваете новую строку старой, но повторно используете ссылки, вы фактически уничтожаете ссылку на старую строку и создаете ссылку на новую.Таким образом, вы можете сделать всю эту операцию еще быстрее, используя класс StringBuilder, который является изменяемым.Это уменьшит накладные расходы, связанные со сборкой той строки, которую вы в конечном итоге напечатаете.

4 голосов
/ 14 декабря 2010

Я полагаю, это из-за буферизации . Цитата из статьи:

Еще один аспект проблем с буферизацией вывод текста в окно терминала. От по умолчанию System.out (PrintStream) равен строка буферизована, что означает, что вывод буфер очищается при переводе строки персонаж встречается Это важно для интерактивности, где Вы хотели бы иметь приглашение ввода отображается перед вводом любого вход.

Цитата, объясняющая буферы из Википедии:

В информатике буфер является область памяти используется для временного удерживать данные во время их перемещения из одно место в другое. Как правило, данные хранятся в буфере как есть извлекается из устройства ввода (например, как мышь) или просто перед отправкой на устройство вывода (например, динамики)

public void println()

Завершить текущую строку, написав строка разделителя строк. Линия Строка разделителя определяется системное свойство line.separator и является не обязательно один перевод строки символ ('\ n').

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

3 голосов
/ 14 декабря 2010

Взгляните на мою замену System.out.println .

По умолчанию System.out.print () имеет только линейный буфер и выполняет большую работу, связанную с Unicode.обработки.Из-за своего небольшого размера буфера System.out.println () не очень подходит для обработки множества повторяющихся выходных данных в пакетном режиме.Каждая строка очищается сразу.Если ваши выходные данные в основном основаны на ASCII, то после удаления операций, связанных с Unicode, общее время выполнения будет лучше.

1 голос
/ 14 декабря 2010

Если вы печатаете в окне консоли, а не в файл, это будет убийцей.

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

Это займет гораздо больше циклов, чем ваша программа.

Обычно это неплохая ценаплатите, так как вывод на консоль должен быть для вашего удовольствия от чтения:)

1 голос
/ 14 декабря 2010

Проблема, с которой вы столкнулись, заключается в том, что отображение на экране очень дорого, особенно если у вас графическая среда windows / X-windows (а не просто текстовый терминал). Просто рендерить одну цифру в шрифте намного дороже, чемрасчеты, которые вы делаете.Когда вы отправляете данные на экран быстрее, чем он может их отобразить, они буферизуют данные и быстро блокируют.Даже запись в файл значительна по сравнению с вычислениями, но она в 10–100 раз быстрее, чем отображение на экране.

Кстати: math.sqrt () очень дорогой, а использование цикла намного медленнее, чем использованиемодуль т.е.%, чтобы определить, является ли число кратным.BitSet может быть в 8 раз эффективнее, чем логическое значение []

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

Took 289 ms to examine 10,000,000 numbers.
Took 149 ms to toString primes up to 10,000,000.
Took 306 ms to write to a file primes up to 10,000,000.
Took 61,082 ms to write to a System.out primes up to 10,000,000.

time cat primes.txt

real    1m24.916s
user    0m3.619s
sys     0m12.058s

Код

int upTo = 10*1000*1000;
long start = System.nanoTime();
BitSet nonprimes = new BitSet(upTo);
for (int t = 2; t * t < upTo; t++) {
    if (nonprimes.get(t)) continue;
    for (int i = 2 * t; i <= upTo; i += t)
        nonprimes.set(i);
}
PrintWriter report = new PrintWriter("report.txt");
long time = System.nanoTime() - start;
report.printf("Took %,d ms to examine %,d numbers.%n", time / 1000 / 1000, upTo);

long start2 = System.nanoTime();
for (int i = 2; i < upTo; i++) {
    if (!nonprimes.get(i))
        Integer.toString(i);
}
long time2 = System.nanoTime() - start2;
report.printf("Took %,d ms to toString primes up to %,d.%n", time2 / 1000 / 1000, upTo);

long start3 = System.nanoTime();
PrintWriter pw = new PrintWriter(new BufferedOutputStream(new FileOutputStream("primes.txt"), 64*1024));
for (int i = 2; i < upTo; i++) {
    if (!nonprimes.get(i))
        pw.println(i);
}
pw.close();
long time3 = System.nanoTime() - start3;
report.printf("Took %,d ms to write to a file primes up to %,d.%n", time3 / 1000 / 1000, upTo);

long start4 = System.nanoTime();
for (int i = 2; i < upTo; i++) {
    if (!nonprimes.get(i))
        System.out.println(i);
}
long time4 = System.nanoTime() - start4;
report.printf("Took %,d ms to write to a System.out primes up to %,d.%n", time4 / 1000 / 1000, upTo);
report.close();
0 голосов
/ 29 октября 2018

Большинство ответов здесь правильные, но они не охватывают самый важный момент: системные вызовы. Это операция, которая вызывает больше накладных расходов.

Когда вашему программному обеспечению требуется доступ к какому-либо аппаратному ресурсу (например, к вашему экрану), ему необходимо запросить у ОС (или гипервизора) доступ к оборудованию. Это стоит много:

Вот интересные блоги о системных вызовах, последний из которых посвящен системным вызовам и Java

http://arkanis.de/weblog/2017-01-05-measurements-of-system-call-performance-and-overhead http://www.brendangregg.com/blog/2014-05-11/strace-wow-much-syscall.html https://blog.packagecloud.io/eng/2017/03/14/using-strace-to-understand-java-performance-improvement/

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...