저작권 안내: 저작권자표시 Yes 상업적이용 No 컨텐츠변경 No

스프링5 입문

JSP 2.3

JPA 입문

DDD Start

인프런 객체 지향 입문 강의

'IO 스트림'에 해당되는 글 2건

  1. 2001.07.18 IO 스트림에서 출력의 성능 향상 #2
  2. 2001.07.03 IO 스트림에서 출력의 성능 향상 #1 (1)
IO 스트림에서 출력 버퍼의 크기에 따른 자원 소모와 성능에 미치는 영향을 살펴본다.

출력 버퍼

출력 버퍼의 적당한 크기를 정하는데 생각해 볼 요소에는 다음과 같은 것들을 들 수 있다.

  1. 출력되는 데이터의 평균적인 크기
  2. 출력 버퍼의 보관된 데이터를 내보내는 flush의 타이밍
  3. 출력 버퍼를 위한 메모리 소모
즉, 출력 버퍼의 크기를 구하는데 가장 중요한 요소들은 경우에 따라 다르며, 주어진 조건에 따라 그 차이가 클 수도 있다.

그러나, 여기서는 20글자 (자바는 유니코드를 사용하므로 40 byte) String 객체를 10만번 파일 스트림으로 출력할 때 소모되는 시간과 메모리를 측정하여 보았다. 결과적으로 4메가 바이트의 데이터가 출력 스트림으로 보내진다. flush() 코드를 따로 추가하지 않고, 버퍼가 가득 찼을 때 자동으로 비워지도록 했을 경우 다음과 같은 결과를 얻을 수 있다.


위의 그래프는 버퍼의 크기가 상대적으로 작은 경우에는 버퍼 크기가 메모리나 시간에 큰 영향을 미치는 것을 보여준다. 따라서 지나치게 작은 출력 버퍼는 성능에 나쁜 영향을 줄 수 있다는 것을 보여준다.

그러나, 일정량 이상의 출력 버퍼를 가진 경우의 테스트 결과를 보면 다음과 같은 결과를 보여준다. 그래프에서 메모리 사용량은 거의 0에 가까운 바닥에 붙어서 보이지 않는데, 위의 그래프와 비교를 위해서 Y축의 비율을 그대로 유지하였다.


출력 버퍼의 크기가 어느 정도 충분한 크기에 이르면 자원 소모나 성능에 큰 영향을 미치지 못하는 것을 볼 수 있다. 대체로 512 char (즉, 1024 byte) 이상의 출력 버퍼는 별다른 성능의 차이를 보이지 않는다.

자바의 출력 버퍼의 기본값

자바의 BufferedWriter 클래스는 생성할 때, 버퍼의 크기를 지정해 줄 수 있지만, 버퍼 크기를 지정하지 않을 경우 기본값으로 버퍼가 생성된다. 기본 버퍼의 크기는 규정된 것은 아니지만 8192 char (즉, 16384 byte)이며 BufferedReader 클래스도 같은 크기의 입력 버퍼를 가지고 있다.

바이트 스트림의 입출력 버퍼 역할을 해주는 클래스인 BufferedInputStream 클래스와 BufferedOutputStream 클래스는 조금 다른 크기의 버퍼를 기본 크기로 가지고 있다.

버퍼 크기를 지정해주지 않을 경우 BufferedOutputStream 클래스의 출력 버퍼는 512 byte로 기본 버퍼크기가 규정되어 있으며, BufferedInputStream 클래스의 입력 버퍼는 지정되어 있지는 않지만 보통 2048 byte의 크기를 가지고 있다.

보통 이런 기본 크기의 버퍼들은 일반적인 경우 가장 합리적인 크기이며, 특별히 버퍼의 크기를 조절할 필요가 없다. 또한, 기본 크기의 버퍼들이 테스트 결과와도 잘 일치한다.

입출력 스트림의 성능은 자바가 아닌 운영체계와 밀접한 관련이 있으며, 자바 뿐만이 아니라, C/C++나 그 이외의 언어에서도 512 byte에서 8192 byte 정도의 입출력 버퍼가 유효한 것으로 알려져 있다.

결론

출력 스트림에서 출력 버퍼의 중요성과 버퍼 크기에 따른 자원 소모량과 성능 향상에 미치는 영향에 대하여 1,2부로 나누어 살펴보았다. 1부에서는 출력 버퍼의 유무에 따른 영향과 빈번한 flush 호출에 따른 효과를 살펴보았으며, 2부에서는 출력 버퍼의 크기에 따른 최적화에 대하여 살펴보았다.



본 글의 저작권은 이동훈에 있으며 저작권자의 허락없이 온라인/오프라인으로 본 글을 유보/복사하는 것을 금합니다.
Posted by 최범균 madvirus

댓글을 달아 주세요

IO 스트림에서 출력 버퍼가 성능에 미치는 영향에 대해서 살펴본다.

IO 스트림의 출력 버퍼

IO 스트림은 파일시스템이나 다른 주변장치들과의 데이터 통신뿐만이 아니라 프로세스간 파이프 같은 데이터 통신 및 네트워크 상의 다른 컴퓨터의 프로세스 간의 데이터 통신을 표준화된 방법으로 처리할 수 있도록 규정한 방법이다. 이런 IO 스트림은 OS 레벨에서 지원하게 되며, 고급화된 표준 IO 데이터 통신 방법이다.

IO 스트림에 데이터를 출력할 때 자바 언어의 스트림에서 제공하는 출력 스트림에 데이터를 내보내면 내부적으로 OS 차원에서 제공하는 고수준 프로시져를 호출하고, 다시 OS 커널의 저수준 프로시져를 거치게 된다. 이 때 데이터는 하나의 덩어리로 보내지게 된다.

이런 이유로, 1바이트의 데이터를 1000번에 걸쳐 보내는 것과, 1000바이트의 데이터를 1번에 보내는 것은 엄청난 속도 차이를 불러오게 된다. 이것은 언어에 상관없이 OS 차원에서 지원하는 것이므로, 자바만의 특징이 아니라 IO 스트림의 특징이다.

데이터를 스트림을 통하여 출력할 때, 큰 덩어리로 묶어서 보낼 수 있다면, 작은 단위로 나누어 출력하는 것보다 성능을 향상시킬 수 있다. 만일 데이터의 특성상 묶을 수가 없고, 작은 단위로 반복해서 보낼 수 밖에 없는 경우에는 출력 버퍼를 사용하는 것이 좋다.

출력 버퍼는 스트림으로 출력하는 데이터를 메모리상에서 임시로 보관하고 있다가 일정량이상 모이면 한번에 출력하는 방법이다.

파일 시스템에 데이터를 출력하는 경우를 생각해보자. 짧은 문자열을 수만번에 걸쳐서 저장하는 경우에 출력 버퍼를 사용하는 경우와 사용하지않는 경우를 비교해 본다. str5는 짧은 문자열을 가지고 있는 String 객체이다. test1() 메소드는 오직 FileWriter 클래스만을 이용하여 텍스트를 파일에 저장하는 메소드이다. test3() 메소드는 FileWriter 클래스에 PrintWriter 클래스를 붙여 println() 메소드를 포함한 편리한 메소드를 사용할 수 있게 된 메소드이다.

   public void test1() throws IOException {
      FileWriter wr = new FileWriter("test1.txt");
      for (int i = 0; i <= count; i++) {
         for (int j=0; j < size; j++) 
            wr.write(str5,0,str5.length());
      }
      wr.close();
   }

   public void test3() throws IOException {
      PrintWriter wr = new PrintWriter(new BufferedWriter(new FileWriter("test3.txt")));
      for (int i = 0; i <= count; i++) {
         for (int j=0; j < size; j++) 
            wr.println(str1);
      }
      wr.close();
   }

test2() 메소드는 BufferedWriter 클래스를 이용하여 test1() 메소드에 출력버퍼를 추가한 경우이다. test4() 메소드는 test3() 메소드에 BufferedWriter 클래스를 추가하여 FileWriter 클래스를 BufferedWriter 클래스와 PrintWriter 클래스로 감싼 경우이다.

   public void test2() throws IOException {
      BufferedWriter wr = new BufferedWriter(new FileWriter("test2.txt"));
      for (int i = 0; i <= count; i++) {
         for (int j=0; j < size; j++) 
            wr.write(str5,0,str5.length());
      }
      wr.close();
   }

   public void test4() throws IOException {
      PrintWriter wr = new PrintWriter(new FileWriter("test4.txt"));
      for (int i = 0; i <= count; i++) {
         for (int j=0; j < size; j++) 
            wr.println(str1);
      }
      wr.close();
   }

네가지 메소드를 6만4천회 반복(64번씩 천번 반복)하였을 때의 소요 시간과 남은 자유 메모리의 양을 비교해보면 다음과 같다.

- x 축은 반복횟수이며 64회 반복당 1씩 증가하며 총 6만4천번을 의미한다.
- y 축은 소요시간은 밀리초, 남은 자유 메모리는 MB이며 처음 힙 메모리의 크기는 64MB이다.


소요시간을 살펴보면 출력버퍼를 사용하지 않은 경우가 대략 50%정도의 시간이 더 걸린 것을 알 수 있다. PrinterWriter 클래스로 인한 소요시간의 증가는 println() 메소드를 사용하였기 때문이다. println() 메소드는 시스템에 맞는 줄바꿈기호를 추가하기 위하여 시간을 더 소모한다.

남은 자유 메모리의 양을 보면 출력버퍼가 메모리 소모를 극단적으로 줄이는 것을 알 수 있다. 출력버퍼를 사용하지 않으므로 빈번한 출력은 메모리의 소모를 증가시킨다. 이것은 나중에 garbage collection 등에 의한 시스템 지연효과도 불러올 수 있다.

PrintWriter 클래스의 autoFlush

출력버퍼를 얼마나 자주 flush 해야 할까? 출력버퍼에 임시로 보관되어 스트림으로 출력될 때까지 대기중인 데이터를 스트림으로 내보내는 것을 flush 라고 한다. BufferedWriter 클래스는 버퍼가 가득 차거나 스트림이 정상적으로 닫힐 때 자동으로 flush() 메소드를 호출한다. BufferedWriter 클래스의 flush() 메소드가 호출되기 전까지는 버퍼에 임시로 보관된 데이터는 스트림으로 실제로 출력되지 않고 버퍼에 대기하고 있게 된다.

PrintWriter 클래스는 생성자 중에 autoFlush 옵션이 있는 것이 있다. 이 옵션이 true 값으로 설정되면 print() 또는 write() 메소드의 경우엔 상관없지만, println() 메소드가 호출되면 자동으로 flush() 메소드를 호출한다. 그러나, 실제로 이 옵션을 사용하면 지나치게 빈번하게 버퍼를 비우는 경향이 발생한다.

다음은 BufferedWriter 클래스를 사용할 때와 사용하지 않을 때, PrintWriter 클래스의 autoFlush 옵션에 관한 성능 테스트이다. test5() 메소드와 test6() 메소드는 각각 test3() 메소드와 test4() 메소드에 autoFlush 옵션을 추가한 메소드이다.

   public void test5() throws IOException {
      PrintWriter wr = new PrintWriter(new BufferedWriter(new FileWriter("test5.txt")),true);
      for (int i = 0; i <= count; i++) {
         for (int j=0; j < size; j++) 
            wr.println(str1);
      }
      wr.close();
   }

   public void test6() throws IOException {
      PrintWriter wr = new PrintWriter(new FileWriter("test6.txt"),true);
      for (int i = 0; i <= count; i++) {
         for (int j=0; j < size; j++) 
            wr.println(str1);
      }
      wr.close();
   }

각 메소드를 6만4천회 반복하여 성능 테스트를 한 결과이다.


PrintWriter 클래스의 autoFlush 옵션을 true 값으로 설정한 경우 지나치게 빈번한 flush() 메소드의 호출로 출력 버퍼의 효과가 거의 없고 3-4배에 달하는 소요시간을 필요로 하게 된다. 남은 자유 메모리는 autoFlush 옵션보다는 BufferedWriter 클래스를 사용하는지 여부에 더 관련된다.

따라서, 특별히 이유가 있지 않다면 flush() 메소드나 autoFlush 옵션으로 출력 버퍼를 지나치게 빈번히 비우는 것은 자원을 낭비하게 된다. 따라서 BufferedWriter 클래스가 자동으로 출력 버퍼를 비우도록 하는 것이 좋다.

결론

IO 스트림에서 출력 버퍼를 사용하는 것이 어느 정도 성능 향상과 관련이 있는지를 알아보았다. 또한, autoFlush 옵션에 따른 빈번한 flush() 호출이 성능에 미치는 효과를 알아보았다. 다음 편에는 출력 버퍼의 크기에 따른 성능 비교를 통하여 적절한 버퍼의 크기를 알아본다.



본 글의 저작권은 이동훈에 있으며 저작권자의 허락없이 온라인/오프라인으로 본 글을 유보/복사하는 것을 금합니다.
Posted by 최범균 madvirus

댓글을 달아 주세요

  1. 개발자 2017.07.08 13:33 신고  댓글주소  수정/삭제  댓글쓰기

    감사합니다
    filewriter 만 쓰고 개발을 했더니 메모리가 점점 잡아먹는 현상ㅇ있었는데
    이 블로그의 내용대로 한다면 그런증상은 없어지겠네요

    다시한번 정보 공유에 감사드립니다.