문자열을 함께 연결하고 한 번 호출하는 것보다 println ()을 호출하는 것이 얼마나 나쁜가요? 년 전에 마이그레이션 되었습니다

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

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

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



답변

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

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

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

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


답변

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

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

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


답변

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


답변

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

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

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

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