개발

System.out.println에 관한 고찰

동고킴 2016. 4. 3. 20:05
반응형

자바 개발 시, 가장 먼저 배우는게 바로 System.out.println이다.

아마 System.out.prinln("Hello World!")를 안써본 사람은 없을 것이다.

그럼에도 불구하고 자바 개발 시 지양해야하는 것중 하나가 바로 System.out.prinln이다.

이유는 리소스를 많이 사용하여 성능 부하를 초래하기 때문이다. 대신 log4j 등의 유틸리티를 사용하라고 한다.


궁금하지 않은가. System.out.println이 어떤놈인지, 그리고 얼마나 느린지.

먼저 System.out.println의 정체를 살펴보자. 구글링도 좋지만 java doc을 먼저 뒤덕거려 보자.

허접한 영어 실력으로 System, out, println을 차례로 따라가면서 살펴보면 아래와 같다.


System은 Object 클래스를 상속받은 final 클래스이다. 인스턴스화 할수없다.

표준 입출력, 에러 출력 기능을 제종한다.

외부(externally로 표현되어 있는데 아마 OS일듯?)에 정의되어 있는 속성 및 환경변수에 접근이 가능하다.

파일 및 라이브러리 로딩이 가능하다. 배열 복사를 빠르게 할수 있는 메소드를 제공한다.

그리고 out은 PrintStream의 인스턴스다. 

마지막으로 println은 PrintStream의 메소드이다.넘겨받은 인자를 출력한다.


그리고 System 클래스는 아래처럼 이루어져있다.

1
2
3
4
5
6
7
8
9
10
11
12
public final class System {
    static PrintStream out;
    static PrintStream err;
    static InputStream in;
    ...
}
 
public class PrintStream extends FilterOutputStream {
    //out object is inherited from FilterOutputStream class
    public void println() {
    ...
}
 
cs



System.out.println의 정체를 알았으니 이제 실제로 사용해보자.

문자열 a를 백만번 찍는 간단한 코드로 테스트를 해보자.

백만번은 정확한 테스트를 위해서는 매우 적은 수치지만 내 성격상 오래 기다리는걸 싫어해서 백만으로 테스트했다.


먼저 System.out.println을 사용하지 않고 결과만 측정해보았다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Main {
    
    public static void main(String[] args) {
        
        long start = System.currentTimeMillis();
        
        int count = 0;
        for (int i=0; i<1000000; i++) {
            count++;
        }
        
        long finish = System.currentTimeMillis();
        
        System.out.println("==================");
        System.out.println("count : " + count);
        System.out.println("걸린시간(ms) : " + (finish - start));
        
    }
    
}
cs

결과

1
2
3
==================
count : 1000000
걸린시간(ms) : 0
cs


1ms도 걸리지 않는다. 가끔 15ms 내외 소요된다.


다음은 System.out.println을 사용해보았다. for문 안에 a를 출력해보았다.

1
2
3
4
5
6
// ..생략..
for (int i=0; i<1000000; i++) {
    num++;
    System.out.println("a");
}
// ..생략..
cs

결과

1
2
3
4
5
6
a
a
a
==================
count : 1000000
걸린시간(ms) : 4571
cs


약 4.5초 정도 소요된다. 굉장한 차이로 비교 자체가 불가하다.


왜 이렇게 차이가 많이 나는걸까?

println은 println -> print -> write() + newLine() 순서로 처리된다고 한다.

그리고 write()와 newLine()은 synchronized이라고 한다.

여기서 Sync가 큰 오버헤드는 아닐것같다. 혹시 몰라서 println대신 print를 사용해서 테스트를 해보았다.

1
2
3
4
5
6
// ..생략..
for (int i=0; i<1000000; i++) {
    num++;
    System.out.print("a");
}
// ..생략..
cs

결과

1
2
3
aaaaaaaaaaaa==================
count : 1000000
걸린시간(ms) : 2420
cs


줄어들었다. 하지만 썩 좋아보이진 않는다. print는 특별한 경우가 아니면 사용할일이 없기 때문이다.


그래도 난 변태니까 무조건 System.out.println을 쓰고싶다. 속도를 줄일수 있는 방법이 없을까?

out 객체를 아예 static으로 import를 한후에 써보았다.

1
2
3
4
5
6
7
8
9
10
11
// ..생략..
for (int i=0; i<1000000; i++) {
    count++;
    out.println("a");
}
 
long finish = System.currentTimeMillis();
 
out.println("==================");
out.println("count : " + count);
out.println("걸린시간(ms) : " + (finish - start));
cs

결과

1
2
3
4
5
6
a
a
a
==================
count : 1000000
걸린시간(ms) : 4591
cs


오.. 줄어들었다.

는 훼이크다. 별 차이가 없다. 굳이 가독성만 떨어뜨렸다.


그래도 죽어도 무조건 System.out.println을 써야겠다.

그렇다면 console에 출력하는걸 포기하고 파일에 출력하는 꼼수를 써보자.

위에서 System 클래스를 보면 알수 있듯이 out은 System 클래스내에 PrintStrem으로 static 정의되어 있기때문에 이부분만 살짝 바꿔주면 파일 출력으로 바꿀수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Main {
    
    public static void main(String[] args) throws Exception {
        
        System.setOut(new PrintStream(new FileOutputStream("log.txt")));
        
        long start = System.currentTimeMillis();
        
        int count = 0;
        for (int i=0; i<1000000; i++) {
            count++;
            System.out.println("a");
        }
        
        long finish = System.currentTimeMillis();
        
        System.out.println("==================");
        System.out.println("count : " + count);
        System.out.println("걸린시간(ms) : " + (finish - start));
        
    }
    
}

cs

결과

1
2
3
4
5
6
a
a
a
==================
count : 1000000
걸린시간(ms) : 15042
cs


15042ms나 걸린다. 더 오래 걸린다.


테스트는 끝났다. 포기할 타이밍이다.

System.out.println은 OS의 콘솔을 활성화 시키면서 CPU 리소스를 점유하게 된다.

시나리오에 따라 다르겠지만 웹을 생각해보자. 클릭을 했는데 페이지 이동이 너무 느리다. 근데 황당하게도 원인이 System.out.println일 수도 있다.


어쨋든 System.out.println은 느리다. 성능에 영향을 많이 준다. 고로 사용을 자제하자.


반응형

'개발' 카테고리의 다른 글

String에 대한 궁금증 4가지  (0) 2016.04.21
JVM 구조 가볍게 알아보기  (0) 2016.04.21
Cloud9 사용하기 (ci9)  (2) 2016.04.01
자바 for문을 이용하여 Map, Hash 값 꺼내기  (2) 2016.03.28
Git reset 명령어  (0) 2016.03.28