블로그 이미지
평범하게 살고 싶은 월급쟁이 기술적인 토론 환영합니다.같이 이야기 하고 싶으시면 부담 말고 연락주세요:이메일-bwcho75골뱅이지메일 닷컴. 조대협


Archive»


 

'File writing'에 해당되는 글 1

  1. 2009.03.31 Java File Writing 성능 비교 (8)
 

Java File Writing 성능 비교

성능과 튜닝/자바 성능팁 | 2009.03.31 17:39 | Posted by 조대협
JAPM을 업그레이드 할까 싶어서 Log Writing 부분을 개선하고자 해서
File Writing을 어떻게 하는 것이 제일 빠를까 테스트를 해봤다.
크게 아래 케이스인데.

1. FileWriter fw = new FileWriter(LOG_HOME+"writer.log");
2. BufferedWriter bw = new BufferedWriter(new FileWriter(LOG_HOME+"writer.log"));
3. FileOutputStream fos = new FileOutputStream(LOG_HOME+"outputstream.log");
4. BufferedOutputStream fos =  new BufferedOutputStream(new FileOutputStream(LOG_HOME+"bufferedoutputstream.log"));
5. FileChannel fc =(new FileOutputStream(new File(LOG_HOME+"filechannel.log"))).getChannel(); + Byte Buffer 매번 생성
6. FileChannel fc =(new FileOutputStream(new File(LOG_HOME+"filechannel.log"))).getChannel(); + ByteBuffer 재사용

테스트의 정확성을 위해서 측정중에 GC가 발생하지 않도록 NewSize와 HeapSize를 크게 해놓고 테스트를 하였다. Vm 옵션은 -XX:NewSize=480m -ms768m -mx768m -verbosegc 이다.
환경 : Windows XP, Sun JVM 1.5, Centrio VPro Dual core, IBM Thinkpad X61s

결과는 다음과 같다.
   1K  2K  5K  10K  50K  
 FileWriter  31  32  94 203  1281  
 FileWriter + BufferedWriter  15  31  94  188  1000  
 FileOutputStream  32 47  109  188  1063  
 FileOutputStream + BufferedOutputStream  31  47  109  203  1578  
FileChannel  47  63  109  219  2906  
FileChannel + Byte Buffer 재사용 31 47 188 250 2766

(해당 레코드를 1000번씩 write)

예상하기로는 NIO의 FileChannel이 가장 빠를것이라 생각했는데, 의외로 FileWrite와 FileOutputStream의 성능이 높게 나왔다. NIO의 경우 JVM 벤더에 종속성이 있겠지만 이런 결과가 나오는것은 약간 의외였다.
 오히려 프로그래밍 기법을 내서 FileChannel을 사용할 경우 최대 3배까지 성능이 나쁜 경우가 올 수 있으니. 이런건 차라리 모르는게 나을 수 도 있겠다~~ 싶다.. ^^

BufferedWriter등의 경우 GC를 유발하는 문제가 있을 수 있기 때문에 또 다른 성능 저하 요인이 될 수 는 있겠지만, 현재까지 테스트 결과로는 Windows Platform에서는 FileWriter + BufferedWriter의 경우가 성능이 제일 좋은것 같다.
아래는 테스트에 사용한 소스 코드
==
package bcho.filewriter.test;

import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

import junit.framework.TestCase;

public class FileWriterTest extends TestCase {
final static int loop = 1000;
final static String LOG_HOME="c:/temp/";
static String LOG_STRING="";
static {
}
public void testSuite() throws Exception{
int stringSize[]={100,200,500,1000,5000};
for(int i=0;i<stringSize.length;i++){
LOG_STRING="";
for(int j=0;j<stringSize[i];j++) LOG_STRING+="1234567890";
log(stringSize[i]+"0 bytes");
testFileWriter();
System.gc();
testBufferedWriter();
System.gc();
testFileOutputStream();
System.gc();
testFileBufferedOutputStream();
System.gc();
testFileChannel();
System.gc();
testFileChannelOneBuffer();
System.gc();
}
}
public  static void log(String str){
System.out.println(str);
}
// java.io.FileWriter
private void testFileWriter() throws Exception {
FileWriter fw = new FileWriter(LOG_HOME+"writer.log");
long st = Timer.getCurrentTime();
for(int i =0;i<loop;i++){
fw.write(LOG_STRING);
}
log("FileWriter :"+Timer.getElapsedTime(st) +"ms");
fw.close();
}
// java.io.BufferedWriter
private void testBufferedWriter() throws Exception {
BufferedWriter bw = new BufferedWriter(new FileWriter(LOG_HOME+"writer.log"));
long st = Timer.getCurrentTime();
for(int i =0;i<loop;i++){
bw.write(LOG_STRING);
}
log("BufferedWriter :"+Timer.getElapsedTime(st) +"ms");
bw.close();
}
// java.io.FileOutputStream
private void testFileOutputStream() throws Exception{
FileOutputStream fos = new FileOutputStream(LOG_HOME+"outputstream.log");
long st = Timer.getCurrentTime();
for(int i=0;i<loop;i++){
byte[] buf = LOG_STRING.getBytes();
fos.write(buf);
}
log("FileOutputStream :"+Timer.getElapsedTime(st) +"ms");
fos.close();
}
// java.io.FileOutputStream
// + java.io.BufferedOutputStream
private void testFileBufferedOutputStream() throws Exception{
BufferedOutputStream fos = 
new BufferedOutputStream(
new FileOutputStream(LOG_HOME+"bufferedoutputstream.log"));
long st = Timer.getCurrentTime();
for(int i=0;i<loop;i++){
byte[] buf = LOG_STRING.getBytes();
fos.write(buf);
}
log("FileBufferedOutputStream :"+Timer.getElapsedTime(st) +"ms");
fos.close();
}
private void testFileChannel() throws Exception {
FileChannel fc =(new FileOutputStream(new File(LOG_HOME+"filechannel.log"))).getChannel();
long st = Timer.getCurrentTime();
for(int i=0;i<loop;i++){
byte[] buf = LOG_STRING.getBytes();
ByteBuffer bytebuffer = ByteBuffer.allocate(buf.length);
bytebuffer.put(buf);
bytebuffer.flip();
fc.write(bytebuffer);
}
log("FileChannel  :"+Timer.getElapsedTime(st) +"ms");
fc.close();
}
private void testFileChannelOneBuffer() throws Exception {
FileChannel fc =(new FileOutputStream(new File(LOG_HOME+"filechannelonebuf.log"))).getChannel();
int BUF_SIZE=1000;
long st = Timer.getCurrentTime();
ByteBuffer bytebuffer = ByteBuffer.allocate(BUF_SIZE);
for(int i=0;i<loop;i++){
byte[] buf = LOG_STRING.getBytes();
int offset=0;
int length= buf.length;
while(offset < length){
int chunkSize = BUF_SIZE > length-offset ? length-offset-1 : BUF_SIZE;
bytebuffer.put(buf, offset, chunkSize);
bytebuffer.flip();
offset+=BUF_SIZE;
fc.write(bytebuffer);
bytebuffer.clear();

}
}
log("FileChannel with reusing buffer:"+Timer.getElapsedTime(st) +"ms");
fc.close();
}


}


'성능과 튜닝 > 자바 성능팁' 카테고리의 다른 글

Java File Writing 성능 비교  (8) 2009.03.31
본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

댓글을 달아 주세요

  1. 놀새~ 2009.04.01 08:14  댓글주소  수정/삭제  댓글쓰기

    Back to the basic, 기본의 중요성이 강조될 수 있는 글이 될 듯요..

  2. joowonpapa 2009.04.01 09:41 신고  댓글주소  수정/삭제  댓글쓰기

    제가 테스트했던 코드와 대부분(?) 비슷한데요. 채널의 transferTo 테스트 해보셨는지요? 제 경우 리눅스 머신(x86)에서는 transferTo가 가장 빨리 나온 것으로 기억됩니다.

    위의 코드로 저도 다시 테스트 해봐야겠습니다.

  3. 지나가던 2009.04.01 15:44  댓글주소  수정/삭제  댓글쓰기

    음...;;
    아시다시피 FileWriter는 내부적으로 어차피 FileOutputStream에 쓰도록 되어 있습니다.
    테스트 결과에 대한 부연 설명이 더 필요할 것 같습니다.
    순수하게 write의 성능을 측정하려면 String.getBytes()가 엄청 느린 연산이라는 점을 감안해야 합니다.

    private void testBufferedWriter() throws Exception {
    BufferedWriter bw = new BufferedWriter(new FileWriter(LOG_HOME
    + "writer.log"));
    long st = System.nanoTime();
    char[] buf = new char[LOG_STRING.length()];
    LOG_STRING.getChars(0, LOG_STRING.length(), buf, 0);
    for (int i = 0; i < loop; i++) {
    // bw.write(LOG_STRING);
    bw.write(buf);
    }
    log("BufferedWriter :" + (System.nanoTime() - st) + "ms");
    bw.close();
    }

    // java.io.FileOutputStream
    private void testFileOutputStream() throws Exception {
    FileOutputStream fos = new FileOutputStream(LOG_HOME
    + "outputstream.log");
    long st = System.nanoTime();
    byte[] buf = LOG_STRING.getBytes();
    for (int i = 0; i < loop; i++) {
    fos.write(buf);
    }
    log("FileOutputStream :" + (System.nanoTime() - st) + "ms");
    fos.close();
    }

    이런식으로 String을 write에 맞게 바꾸는 부분을 loop 밖으로 빼시면 결과가 다르게 나올겁니다.
    (StreamEncoder 클래스가 실제로 Writer의 작업을 담당하니 코드를 확인해 보시는 것도 좋을 듯 합니다.)

    물론 실제 logging framework을 제작하려면 getBytes() 호출도 당연히 감안해서 측정하는게 맞습니다. 다만, 현재 코드로는 그 부분을 정확히 측정하기 어려우니 변경이 필요할 것 같습니다.

    그리고 채널은 대부분의 경우(Channel To Channel 등의 작업이 아닌이상) 특별히 빠를 이유가 없지요 ^^;

    • 조대협 2009.04.01 16:57 신고  댓글주소  수정/삭제

      의견 감사합니다.
      getBytes가 loop내에 있는 이유는 이 프로그램은 테스트 코드이기 때문에, String이 고정되어 있지만, 실제 Logging 프로그램에서는 로깅 메세지가 매번 바뀌기 때문에, Bytes에 대한 변환 시간도 매번 측정해야 한다는 판단에 위와 같이 테스트 프로그램을 구현하였습니다.

      혹시 다른 의견있으신가요?

  4. gim 2010.07.07 12:22  댓글주소  수정/삭제  댓글쓰기

    ByteBuffer bytebuffer = ByteBuffer.allocate(buf.length);

    nio의 장점을 살리려면 allocateDirect() 메소드를 사용해야 하는거 아닌가요?

  5. HackerK 2013.10.19 15:29  댓글주소  수정/삭제  댓글쓰기

    위에분 말이 맞습니다.
    allocate는 커널 버퍼에 로드한 후 JVM내의 일반 버퍼로 복사를 하고, allocateDirect는 커널 버퍼에 로드합니다.
    allocate를 쓰면 의미가 없어요

  6. 영준이 2015.10.04 05:26 신고  댓글주소  수정/삭제  댓글쓰기

    출처밝히고 블로그에좀 담아갈게요 ㅠㅠ