문자열을 함께 연결하고 한 번 호출하는 것보다 println ()을 호출하는 것이 얼마나 나쁜가요?


23

콘솔 출력은 비용이 많이 드는 작업이라는 것을 알고 있습니다. 코드 가독성을 위해 긴 텍스트 문자열을 인수로 사용하지 않고 텍스트를 두 번 출력하는 함수를 호출하는 것이 좋습니다.

예를 들어 얼마나 덜 효율적입니까

System.out.println("Good morning.");
System.out.println("Please enter your name");

vs.

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 ++와 같은 언어의 경우에도 마찬가지입니까?


이것은 매우 작은 코드이지만, 같은 것을 궁금해하고 있습니다. 이에 대한 해답을 결정하는 것이 좋을 것입니다
Simon Forsberg

@ SimonAndréForsberg 가상 머신에서 실행되기 때문에 Java에 적용 가능한지 확실하지 않지만 C / C ++와 같은 저수준 언어에서는 출력 스트림, 시스템 호출에 쓸 때마다 비용이 많이들 것이라고 상상할 수 있습니다. 해야합니다.

이것도 고려해야합니다 : stackoverflow.com/questions/21947452/…
hjk

1
나는 여기서 요점이 보이지 않는다고 말해야한다. 터미널을 통해 사용자와 상호 작용할 때 일반적으로 인쇄 할 것이 많지 않기 때문에 성능 문제를 상상할 수 없습니다. 또한 GUI 또는 webapp가있는 응용 프로그램은 로그 파일에 기록해야합니다 (일반적으로 프레임 워크 사용).
Andy

1
좋은 아침이라고 말하면 하루에 한두 번하십시오. 최적화는 문제가되지 않습니다. 다른 것이 있으면 문제가 있는지 프로파일 링해야합니다. 로깅에서 작업하는 코드는 여러 줄 버퍼를 작성하고 한 번의 호출로 텍스트를 덤프하지 않으면 코드를 사용할 수 없게 만듭니다.
mattnz

답변:


29

여기에는 두 가지 '힘'이 있습니다. 성능과 가독성.

세 번째 문제를 먼저 다루겠습니다.

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.");

이것을 구현하고 가독성을 유지하는 가장 좋은 방법은 문자열 연결을 사용하는 것입니다.

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.");

문자열 상수 연결은 컴파일 타임에 발생하며 성능에 전혀 영향을 미치지 않습니다. 행을 읽을 수 있으며 계속 진행할 수 있습니다.

이제,

System.out.println("Good morning.");
System.out.println("Please enter your name");

vs.

System.out.println("Good morning.\nPlease enter your name");

두 번째 옵션은 훨씬 빠릅니다. 나는 2 배 빠른 것에 대해 제안 할 것이다.… 왜?

작업의 90 % (넓은 오류 한계)는 문자를 출력으로 덤프하는 것과 관련이 없지만 출력을 쓰기 위해 보안을 설정하는 데 오버 헤드가 필요합니다.

동기화

System.out입니다 PrintStream. 내가 아는 모든 Java 구현은 PrintStream을 내부적으로 동기화합니다. GrepCode의 코드를 참조하십시오! .

이것이 코드에서 무엇을 의미합니까?

전화를 걸 때마다 System.out.println(...)메모리 모델을 동기화 할 때 잠금을 확인하고 기다리고 있음을 의미합니다. System.out을 호출하는 다른 스레드도 잠 깁니다.

단일 스레드 응용 프로그램의 영향 System.out.println()은 종종 시스템의 IO 성능, 파일에 얼마나 빨리 쓸 수 있는지에 의해 제한됩니다. 다중 스레드 응용 프로그램에서 잠금은 IO보다 더 큰 문제가 될 수 있습니다.

홍조

각 println이 플러시 됩니다. 버퍼가 지워지고 버퍼에 대한 콘솔 레벨 쓰기가 트리거됩니다. 여기서 수행되는 노력의 양은 구현에 의존하지만, 일반적으로 플러시의 성능은 플러시되는 버퍼의 크기와 관련이있는 것으로 이해된다. 메모리 버퍼가 더티로 표시되고 가상 시스템이 IO를 수행하는 등 플러시와 관련하여 상당한 오버 헤드가 있습니다. 이 오버 헤드가 두 번이 아니라 한 번만 발생하는 것이 확실한 최적화입니다.

일부 숫자

다음과 같은 작은 테스트를 구성했습니다.

public class ConsolePerf {

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            benchmark("Warm " + i);
        }
        benchmark("real");
    }

    private static void benchmark(String string) {
        benchString(string + "short", "This is a short String");
        benchString(string + "long", "This is a long String with a number of newlines\n"
                  + "in it, that should simulate\n"
                  + "printing some long sentences and log\n"
                  + "messages.");

    }

    private static final int REPS = 1000;

    private static void benchString(String name, String value) {
        long time = System.nanoTime();
        for (int i = 0; i < REPS; i++) {
            System.out.println(value);
        }
        double ms = (System.nanoTime() - time) / 1000000.0;
        System.err.printf("%s run in%n    %12.3fms%n    %12.3f lines per ms%n    %12.3f chars per ms%n",
                name, ms, REPS/ms, REPS * (value.length() + 1) / ms);

    }


}

코드는 비교적 단순하며 짧거나 긴 문자열을 반복적으로 출력하여 출력합니다. 긴 문자열에는 여러 줄 바꿈이 있습니다. 각각 1000 회 반복 인쇄하는 데 걸리는 시간을 측정합니다.

내가 명령 프롬프트 유닉스 (리눅스)에서 실행하고, 리디렉션 경우 STDOUT/dev/null, 그리고에 대한 실제 결과를 인쇄 STDERR, 나는 다음을 수행 할 수 있습니다 :

java -cp . ConsolePerf > /dev/null 2> ../errlog

출력 (errlog)은 다음과 같습니다.

Warm 0short run in
           7.264ms
         137.667 lines per ms
        3166.345 chars per ms
Warm 0long run in
           1.661ms
         602.051 lines per ms
       74654.317 chars per ms
Warm 1short run in
           1.615ms
         619.327 lines per ms
       14244.511 chars per ms
Warm 1long run in
           2.524ms
         396.238 lines per ms
       49133.487 chars per ms
.......
Warm 99short run in
           1.159ms
         862.569 lines per ms
       19839.079 chars per ms
Warm 99long run in
           1.213ms
         824.393 lines per ms
      102224.706 chars per ms
realshort run in
           1.204ms
         830.520 lines per ms
       19101.959 chars per ms
reallong run in
           1.215ms
         823.160 lines per ms
      102071.811 chars per ms

이것은 무엇을 의미 하는가? 마지막 'stanza'를 반복하겠습니다.

realshort run in
           1.204ms
         830.520 lines per ms
       19101.959 chars per ms
reallong run in
           1.215ms
         823.160 lines per ms
      102071.811 chars per ms

그것은 모든 의도와 목적을 위해, '긴'줄이 약 5 배 길고 여러 줄 바꿈을 포함하더라도 짧은 줄만큼 출력하는 데 시간이 오래 걸린다는 것을 의미합니다.

장기적으로 초당 문자 수는 5 배이며 경과 시간은 거의 같습니다 .....

즉, 성능이 상대적 확장 할 당신이 printlns의, 아니 어떤 그들은 인쇄 할 수 있습니다.

업데이트 : / dev / null 대신 파일로 리디렉션하면 어떻게됩니까?

realshort run in
           2.592ms
         385.815 lines per ms
        8873.755 chars per ms
reallong run in
           2.686ms
         372.306 lines per ms
       46165.955 chars per ms

훨씬 느리지 만 비율은 거의 같습니다 ....


성능 수치가 추가되었습니다.
rolfl

또한 "\n"올바른 회선 종결자가 아닐 수도 있는 문제를 고려해야합니다 . println올바른 문자로 줄을 자동으로 종료하지만 \n문자열에 직접 입력하면 문제가 발생할 수 있습니다. 올바르게 하려면 문자열 형식화 또는 line.separator시스템 속성사용해야합니다 . println훨씬 더 깨끗합니다.
user2357112는 Monica

3
이것은 모두 훌륭한 분석이므로 +1은 확실하지만 콘솔 출력에 최선을 다하면 이러한 작은 성능 차이가 발생합니다. 프로그램의 알고리즘이 결과를 출력하는 것 (이 작은 수준의 출력)보다 빠르게 실행되는 경우 각 문자를 하나씩 인쇄하여 차이를 알 수 없습니다.
David Harkness

이것이 출력이 동기화되는 Java와 C / C ++의 차이점이라고 생각합니다. 다른 스레드가 콘솔에 쓰려고하면 멀티 스레드 프로그램을 작성하고 깨진 출력에 문제가 있다고 생각하기 때문에 이것을 말합니다. 누구든지 이것을 확인할 수 있습니까?

6
또한 사용자 입력을 기다리는 기능 옆에 놓을 때 그 속도 중 어느 것도 중요 하지 않다는 것을 기억해야 합니다.
vmrob

2

나는 많은 printlns를 갖는 것이 디자인 문제 라고 생각하지 않습니다 . 내가 보는 방식은 이것이 실제로 문제가된다면 정적 코드 분석기로 명확하게 수행 할 수 있다는 것입니다.

그러나 대부분의 사람들이 이와 같은 IO를 수행하지 않기 때문에 문제가되지 않습니다. 실제로 많은 IO를 수행 해야하는 경우 입력이 버퍼링 될 때 버퍼링 된 버퍼 (BufferedReader, BufferedWriter 등)를 사용합니다. 성능이 충분히 비슷하다는 것을 알 수 있습니다. 잔뜩 println또는 몇 println.

원래 질문에 대답합니다. println대부분의 사람들이 사용하는 것처럼 몇 가지를 인쇄하는 데 사용한다면 나쁘지 않습니다 println.


1

C 및 C ++와 같은 고급 언어에서는 Java보다 문제가 적습니다.

우선, C와 C ++는 컴파일 타임 문자열 연결을 정의하므로 다음과 같이 할 수 있습니다.

std::cout << "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 ++ 표준에서 직접 요구됩니다 (번역의 6 단계 : "인접한 문자열 리터럴 토큰이 연결되어 있습니다").

C와 C ++는 컴파일러와 구현에서 약간의 추가 복잡성을 희생하지만 프로그래머로부터 효율적으로 출력을 생성하는 복잡성을 감추기 위해 조금 더 노력합니다. Java는 어셈블리 언어와 매우 유사합니다. 각 호출 System.out.println은 기본 운영에 대한 호출로 훨씬 더 직접 변환되어 콘솔에 데이터를 씁니다. 버퍼링을 통해 효율성을 높이려면 별도로 제공해야합니다.

예를 들어 C ++에서 이전 예제를 다음과 같이 다시 작성한다는 것을 의미합니다.

std::cout << "Good morning everyone. I am here today to present you with a very, ";
std::cout << "very lengthy sentence in order to prove a point about how it looks ";       
std::cout << "strange amongst other code.";

... 일반적으로 1 은 효율성에 거의 영향을 미치지 않습니다. 각각의 사용 cout은 단순히 데이터를 버퍼에 저장합니다. 해당 버퍼는 버퍼가 가득 찼을 때 또는 코드가와 같은 사용에서 입력을 읽으려고 할 때 기본 스트림으로 플러시됩니다 std::cin.

iostream또한 sync_with_stdioiostream의 출력이 C 스타일 입력 (예 :)과 동기화되는지 여부를 결정 하는 속성이 getchar있습니다. 기본적 sync_with_stdio으로 true로 설정되어 있으므로 예를 들어에 std::cout쓰고을 통해 읽는 경우 getchar쓴 데이터 가 호출 cout될 때 플러시됩니다 getchar. sync_with_stdio사용하지 않도록 false로 설정할 수 있습니다 (일반적으로 성능 향상을 위해 수행됨).

sync_with_stdio또한 스레드 간의 동기화 정도를 제어합니다. 동기화가 켜져 있으면 (기본값) 여러 스레드에서 iostream에 쓰면 스레드의 데이터가 인터리브 될 수 있지만 경쟁 조건은 방지 할 수 있습니다. IOW에서는 프로그램이 실행되어 출력을 생성하지만 한 번에 둘 이상의 스레드가 스트림에 쓰면 다른 스레드의 데이터를 임의로 혼합하여 출력을 꽤 쓸모 없게 만듭니다.

동기화 를 끄면 여러 스레드에서 액세스를 동기화하는 것도 전적으로 귀하의 책임입니다. 여러 스레드에서 동시 쓰기를 수행하면 데이터 경쟁이 발생할 수 있으며 이는 코드에 정의되지 않은 동작이 있음을 의미합니다.

개요

C ++은 기본적으로 속도와 안전의 균형을 유지하려고 시도합니다. 결과는 단일 스레드 코드에서는 상당히 성공적이지만 다중 스레드 코드에서는 그렇지 않습니다. 멀티 스레드 코드는 일반적으로 유용한 출력을 생성하기 위해 한 번에 하나의 스레드 만 스트림에 쓰도록해야합니다.


1. 스트림에 대한 버퍼링을 해제 할 수 있지만 실제로 그렇게하는 것은 매우 드문 일이며 누군가 그렇게 할 경우 성능에 영향을 미치지 않으면 서 모든 출력을 즉시 캡처하는 것과 같은 매우 구체적인 이유 일 수 있습니다. . 어쨌든 이것은 코드에서 명시 적으로 수행하는 경우에만 발생합니다.


13
" C 및 C ++와 같은 고급 언어에서는 Java보다 문제가 적습니다. "-무엇? C 및 C ++는 Java보다 하위 언어입니다. 또한 라인 종결자를 잊어 버렸습니다.
user2357112는 Monica

1
나는 자바가 저수준 언어라는 객관적인 기초를 지적한다. 무슨 라인 터미네이터를 말하는지 확실하지 않습니다.
Jerry Coffin

2
Java는 컴파일 타임 연결도 수행합니다. 예를 들어, "2^31 - 1 = " + Integer.MAX_VALUE단일 내부 문자열로 저장됩니다 (JLS Sec 3.10.515.28 ).
200_success

2
@ 200_success : 컴파일 타임에 문자열 연결을 수행하는 Java가 §15.18.1로 내려간 것 같습니다. "표현식이 컴파일 타임 상수 표현식 (§15.28)이 아니면 String 객체가 새로 생성됩니다 (§12.5)." 이것은 컴파일 타임에 연결을 수행하도록 허용하지만 요구하지는 않습니다. 즉, 입력이 컴파일 타임 상수가 아닌 경우 결과를 새로 작성해야하지만 컴파일 타임 상수 인 경우 어느 방향으로도 요구 사항이 없습니다. 컴파일 타임 연결을 요구하려면 "if"와 "if and only if"를 의미하는 "if"를 읽어야합니다.
Jerry Coffin

2
@Phoshi : 리소스를 사용해 보는 것도 RAII와 모호하지 않습니다. RAII에서는 클래스가 리소스를 관리 할 수 ​​있지만 리소스를 사용하려면 리소스를 관리하기 위해 클라이언트 코드가 필요합니다. 하나의 특징과 다른 부분의 부족한 특징 (추상, 더 정확하게)은 전적으로 관련이 있습니다. 사실, 한 언어가 다른 언어보다 더 높은 수준을 만드는 것입니다.
Jerry Coffin

1

여기서 성능은 실제로 문제가되지 않지만, 많은 문장의 가독성은 println디자인 측면이 빠져 있음을 나타냅니다.

왜 우리는 많은 println진술 의 순서를 작성합니까? --help콘솔 명령 의 텍스트 와 같이 하나의 고정 된 텍스트 블록 인 경우 별도의 리소스로 사용하여 요청에 따라 화면에 읽고 쓰는 것이 훨씬 좋습니다.

그러나 일반적으로 동적 부품과 정적 부품이 혼합되어 있습니다. 한편으로 일부 주문 데이터와 고정 된 정적 텍스트 부분이 있고 주문 확인 시트를 구성하기 위해 이들을 혼합해야한다고 가정 해 봅시다. 또한이 경우에도 별도의 리소스 텍스트 파일을 사용하는 것이 좋습니다. 리소스는 런타임에 실제 주문 데이터로 대체되는 일종의 기호 (자리 표시 자)를 포함하는 템플릿입니다.

프로그래밍 언어와 자연 언어를 분리하면 많은 장점이 있습니다. 그 중 국제화가 있습니다. 소프트웨어를 다국어로 사용하려면 텍스트를 번역해야 할 수도 있습니다. 또한 텍스트를 수정하고 싶을 때 컴파일 단계가 필요한 이유는 무엇입니까?

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.