성능과 튜닝/자바 성능팁

Java File Writing 성능 비교

Terry Cho 2009. 3. 31. 17:39
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();
}


}


그리드형