자바 NIO의 화일 처리
자바에서는 JVM으로 인하여, 다른 언어와 달리 IO에 대한 성능이 떨어지는 구조를 가지고 있다.
Kenel(운영체제)상에 읽혀진 파일의 내용이 다시 JVM 메모리상으로 이동해야만 Java 프로그램상에서 접근할 수 있는 구조로서, 이중 데이터 구조로서 Kernel과 JVM 상의 데이터 복제에 따라 성능 저하가 나타나는 구조이다.
오라클에서 추천하는 NIO 기법이 과연 기존 방식에 비해서 얼마나 빠른지 확인 해보고자 테스트를 수행하였다.
두장의 차트로서 설명이 다 된것 같습니다.
M 단위는 화일의 용량을 나타내며, 테스트 기법은 Stream, BufferedStream, Channel, ChannelTransfer 방법입니다.
이중 Chanle과 ChannelTransfer 방법이 NIO 기법이 되겠습니다.
프로파일 정보입니다. 아무리 봐도 NIO 빠르다는 것을 수긍하기에는 실제 정보는 워낙 달라서 믿기 어려운 상황이 되어 버렸습니다.
테스트 소스 :
package file;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.time.StopWatch;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* Created by IntelliJ IDEA.
* User: Administrator
* Date: 11. 6. 7
* Time: 오전 11:34
* To change this template use File | Settings | File Templates.
*/
public class NIOFileTest {
public static final String nioDstFilename = "c:\\java\\NIO_TEST\\nio_dest.rar";
public static final String stmDstFilename = "c:\\java\\NIO_TEST\\stm_dest.rar";
public static final String bufDstFilename = "c:\\java\\NIO_TEST\\buf_dest.rar";
public static final String zeroDstFilename = "c:\\java\\NIO_TEST\\zero_dest.rar";
public static void main(String[] args) {
//이 메서드는 fileinputstream 및 bufferedfileinputstream, FileChannel(use byteBuffer allocateDirect),
// channel's transferFrom, transferTo 를 이용해서 처리하는 방법을
//테스트하는 것이다.
final String path = "C:\\Java\\NIO_TEST\\";
final String[] filename = {"source_1M.rar", "source_1.7M.rar", "source_3M.rar", "source_9M.rar",
"source_17M.rar", "source_56M.rar", "source_130M.rar"};
StringBuilder fp = new StringBuilder(30);
for (int i = 0; i < filename.length; i++) {
fp.setLength(0);
fp.append(path).append(filename[i]);
doStream(fp.toString());
doBuffer(fp.toString());
doChannel(fp.toString());
doChannelTransfer(fp.toString());
System.out.println();
}
}
public static void doStream(String srcFilename) {
FileInputStream fis = null;
FileOutputStream fos = null;
File source;
File target;
byte[] data;
try {
StopWatch swt = new StopWatch();
swt.start();
//이미 테스트한 화일이 있으면 삭제 처리 한다.
target = new File(stmDstFilename);
if (target.exists()) {
target.delete();
}
////////////////////////////////////////////////////
//소스 화일에 대한 정보을 얻는다.
source = new File(srcFilename);
fis = new FileInputStream(source);
fos = new FileOutputStream(target);
data = new byte[1024];
while ((fis.read(data) > -1)) {
fos.write(data);
}
swt.stop();
System.out.println("doStream : " + srcFilename + " : " + swt.getTime());
} catch (IOException ioe) {
ioe.printStackTrace();
} finally {
IOUtils.closeQuietly(fis);
IOUtils.closeQuietly(fos);
data = null;
}
}
public static void doBuffer(String srcFilename) {
FileInputStream fis = null;
FileOutputStream fos = null;
BufferedInputStream bis;
BufferedOutputStream bos;
File source;
File target;
byte[] data;
try {
StopWatch swt = new StopWatch();
swt.start();
//이미 테스트한 화일이 있으면 삭제 처리 한다.
target = new File(bufDstFilename);
if (target.exists()) {
target.delete();
}
////////////////////////////////////////////////////
//소스 화일에 대한 정보을 얻는다.
source = new File(srcFilename);
fis = new FileInputStream(source);
bis = new BufferedInputStream(fis);
fos = new FileOutputStream(target);
bos = new BufferedOutputStream(fos);
data = new byte[1024];
while ((bis.read(data) > -1)) {
bos.write(data);
}
bos.flush();
swt.stop();
System.out.println("bufStream : " + srcFilename + " : " + swt.getTime());
} catch (IOException ioe) {
ioe.printStackTrace();
} finally {
IOUtils.closeQuietly(fis);
IOUtils.closeQuietly(fos);
data = null;
}
}
public static void doChannel(String srcFilename) {
FileInputStream fis = null;
FileOutputStream fos = null;
FileChannel fic = null;
FileChannel foc = null;
File source;
File target;
ByteBuffer data;
try {
StopWatch swt = new StopWatch();
swt.start();
//이미 테스트한 화일이 있으면 삭제 처리 한다.
target = new File(nioDstFilename);
if (target.exists()) {
target.delete();
}
////////////////////////////////////////////////////
data = ByteBuffer.allocateDirect(1024);
//소스 화일에 대한 정보을 얻는다.
source = new File(srcFilename);
fis = new FileInputStream(source);
fic = fis.getChannel();
fos = new FileOutputStream(target);
foc = fos.getChannel();
while ((fic.read(data) > -1)) {
data.flip();
foc.write(data);
data.clear();
}
swt.stop();
System.out.println("doChannel : " + srcFilename + " : " + swt.getTime());
} catch (IOException ioe) {
ioe.printStackTrace();
} finally {
IOUtils.closeQuietly(foc);
IOUtils.closeQuietly(fos);
IOUtils.closeQuietly(fic);
IOUtils.closeQuietly(fis);
data = null;
}
}
public static void doChannelTransfer(String srcFilename) {
FileInputStream fis = null;
FileOutputStream fos = null;
FileChannel fic = null;
FileChannel foc = null;
File source;
File target;
try {
StopWatch swt = new StopWatch();
swt.start();
//이미 테스트한 화일이 있으면 삭제 처리 한다.
target = new File(zeroDstFilename);
if (target.exists()) {
target.delete();
}
////////////////////////////////////////////////////
//소스 화일에 대한 정보을 얻는다.
source = new File(srcFilename);
fis = new FileInputStream(source);
fic = fis.getChannel();
fos = new FileOutputStream(target);
foc = fos.getChannel();
fic.transferTo(0L, fic.size(), foc);
swt.stop();
System.out.println("doChannelTransfer : " + srcFilename + " : " + swt.getTime());
} catch (IOException ioe) {
ioe.printStackTrace();
} finally {
IOUtils.closeQuietly(foc);
IOUtils.closeQuietly(fos);
IOUtils.closeQuietly(fic);
IOUtils.closeQuietly(fis);
}
}
}
jdk version :
jdk1.6.0_25 64bit
hardware :
windows 7 64비트 ultimate
intel i7-2720QM(쿼드코어 노트북 프로세서)
8기가 램
7600rpm 500기가 노트북 하드 디스크
Dell xps 15 L502X
복사 테스트
doStream : C:\Java\NIO_TEST\source_1M.rar : 22
bufStream : C:\Java\NIO_TEST\source_1M.rar : 17
doChannel : C:\Java\NIO_TEST\source_1M.rar : 31
doChannelTransfer : C:\Java\NIO_TEST\source_1M.rar : 13
doStream : C:\Java\NIO_TEST\source_1.7M.rar : 16
bufStream : C:\Java\NIO_TEST\source_1.7M.rar : 6
doChannel : C:\Java\NIO_TEST\source_1.7M.rar : 25
doChannelTransfer : C:\Java\NIO_TEST\source_1.7M.rar : 2
doStream : C:\Java\NIO_TEST\source_3M.rar : 27
bufStream : C:\Java\NIO_TEST\source_3M.rar : 10
doChannel : C:\Java\NIO_TEST\source_3M.rar : 42
doChannelTransfer : C:\Java\NIO_TEST\source_3M.rar : 3
doStream : C:\Java\NIO_TEST\source_9M.rar : 75
bufStream : C:\Java\NIO_TEST\source_9M.rar : 27
doChannel : C:\Java\NIO_TEST\source_9M.rar : 93
doChannelTransfer : C:\Java\NIO_TEST\source_9M.rar : 5
doStream : C:\Java\NIO_TEST\source_17M.rar : 136
bufStream : C:\Java\NIO_TEST\source_17M.rar : 36
doChannel : C:\Java\NIO_TEST\source_17M.rar : 128
doChannelTransfer : C:\Java\NIO_TEST\source_17M.rar : 17
doStream : C:\Java\NIO_TEST\source_56M.rar : 526
bufStream : C:\Java\NIO_TEST\source_56M.rar : 132
doChannel : C:\Java\NIO_TEST\source_56M.rar : 598
doChannelTransfer : C:\Java\NIO_TEST\source_56M.rar : 647
doStream : C:\Java\NIO_TEST\source_130M.rar : 1035
bufStream : C:\Java\NIO_TEST\source_130M.rar : 247
doChannel : C:\Java\NIO_TEST\source_130M.rar : 964
doChannelTransfer : C:\Java\NIO_TEST\source_130M.rar : 5004
테스트 결과 :
자바의 최신기술인 NIO는 기존 stream에 비해서 매우 빠른 특성을 보이지는 못하였으며, ByteBuffer.allocationDirect() 메서드로 커널 메모리에 직접 할당받아도 기존의 bufferedStream 방식에 비하여 느리다는 것을 확인 할 수 있었습니다.
메모리를 공유해도 BufferedStream 처럼 IO 작업을 한번에 처리하여, IO 횟수를 줄이는 것이 가장 효율적인 방법으로 확인되었으며,
NIO 넌블럭킹 기법을 통해서 서버의 Socket 프로그래밍등에 커다란 성능 향상을 이루어 주는 것에 효과가 크며, 일반 화일처리에는 큰 효과가 별로 없다는 것을 확인하였습니다.
그리하여, Zero Copy 라던 Channel.transfer 메서드도 이론과는 다른 현상을 보여 주었습니다.
이것은 제 PC에서 테스트한것입니다. 이것을 참조하시려는 분은 직접 해보시고 판단하세요.



