다른 개발자가 개발한 파일업로드용 웹 서버의 코드를 보면서 든 예전에 이런 부분에 대해서 간단히 노트해 본다.

수많은 개발자들이 “당연히 nio가 io보다 성능이 좋다”라는 인식을 많이 하고 있다. 나도 그렇게 생각해오긴 했었다. 그러나 그런 것만은 아니다. 환경에 따라서 io가 nio보다 성능이 더 좋을 수 있다.  내가 보았던 테스트중의 일부는 nio보다 io가 더 빨랐다. (진짜!. io가 nio보다 10~20% 정도 더 나왔다)

한 블로그(http://geekomatic.ch/2009/01/16/1232134440000.html)의 글에서는 nio가 io에 비해서 판정승으로 성능이 좋다.

java.io versus java.nio

 

 

다른 블로그(참조 : http://geekomatic.ch/2008/09/06/1220730740479.html) 의 글을 참조해 본다. 역시 nio가 io보다 성능이 좀 더 좋다.

x축은 크기, y축은 속도를 의미하며, 파란색은 nio이고, 빨간색은 io 테스트이다. 용량이 작을 때는 오히려 io가 더 속도가 높을 때(file size가 10mb)가 있다. 그러나 큰 용량으로 갈 수록 nio가 확실히 속도가 빨라진다.

java.io versus java.nio

 

구글의 한 개발자(http://www.mailinator.com/tymaPaulMultithreaded.pdf)의 발표 자료을 보면, nio보다 io가 성능이 더 높게 나오게 했다. (do not compare charts 를 하지 말라고 했지만. 해버렸다.)

image

또다른 분의 글(http://www.thebuzzmedia.com/java-io-faster-than-nio-old-is-new-again/)을 참조하면 위의 ppt에 사족을 다신 분이 있다. 이 분의 글을 잠시 빌려온다면 다음과 같다.

NIO가 빠른 이유는 asynchrous와 non-blocking 때문에 빠르다는 것이다.

 

 

누가 더 성능이 좋냐. 참 성능 이라는 지표가 io/net, 코드, vm, OS에 depedent가 있기 때문에 상당히 애매모호하다.

stackoverflow(http://stackoverflow.com/questions/1605332/java-nio-filechannel-versus-fileoutputstream-performance-usefulness)에 좋은 자료가 있다.

아래 코드를 가지고 테스트했는데, nio보다 io의 성능이 더 좋냐는 질문이었다.

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class JavaNIOTest {
    public static void main(String[] args) throws Exception {
        useNormalIO();
        useFileChannel();
    }

    private static void useNormalIO() throws Exception {
        File file = new File("/home/developer/test.iso");
        File oFile = new File("/home/developer/test2");

        long time1 = System.currentTimeMillis();
        InputStream is = new FileInputStream(file);
        FileOutputStream fos = new FileOutputStream(oFile);
        byte[] buf = new byte[64 * 1024];
        int len = 0;
        while((len = is.read(buf)) != -1) {
                fos.write(buf, 0, len);
        }
        fos.flush();
        fos.close();
        is.close();
        long time2 = System.currentTimeMillis();
        System.out.println("Time taken: "+(time2-time1)+" ms");
    }

    private static void useFileChannel() throws Exception {
        File file = new File("/home/developer/test.iso");
        File oFile = new File("/home/developer/test2");

        long time1 = System.currentTimeMillis();
        FileInputStream is = new FileInputStream(file);
        FileOutputStream fos = new FileOutputStream(oFile);
        FileChannel f = is.getChannel();
        FileChannel f2 = fos.getChannel();

        ByteBuffer buf = ByteBuffer.allocateDirect(64 * 1024);
        long len = 0;
        while((len = f.read(buf)) != -1) {
                buf.flip();
                f2.write(buf);
                buf.clear();
        }

        f2.close();
        f.close();

        long time2 = System.currentTimeMillis();
        System.out.println("Time taken: "+(time2-time1)+" ms");
    }
}

 

좋은 답이라 투표를 받은 글의 전문은 다음과 같다.  너무 괜찮아서 번역 없이 그냥 작성한다.  결론은 nio 코딩할 때는 nio 코딩의 묘미에 맞게 코딩해야 제대로 성능이 나온다는 것이다.

My experience with larger files sizes has been that java.nio is faster than java.io. Solidly faster.Like in the >250% range. That said, I am eliminating obvious bottlenecks, which I suggest your micro-benchmark might suffer from. Potential areas for investigating:

The buffer size. The algorithm you basically have is

  • copy from disk to buffer
  • copy from buffer to disk

My own experience has been that this buffer size is ripe for tuning. I've settled on 4KB for one part of my application, 256KB for another. I suspect your code is suffering with such a large buffer. Run some benchmarks with buffers of 1KB, 2KB, 4KB, 8KB, 16KB, 32KB and 64KB to prove it to yourself.

Don't perform java benchmarks that read and write to the same disk.

If you do, then you are really benchmarking the disk, and not Java. I would also suggest that if your CPU is not busy, then you are probably experience some other bottle neck.

Don't use a buffer if you don't need to.

Why copy to memory if your target is another disk or a NIC? With larger files, the latency incured is non-trivial.

Like other have said, use FileChannel.transferTo() or FileChannel.transferFrom(). The key advantage here is that the JVM uses the OS's access to DMA (Direct Memory Access), if present.(This is implementation dependent, but modern Sun and IBM versions on general purpose CPUs are good to go.) What happens is the data goes straight to/from disc, to the bus, and then to the destination...by passing any circuit through RAM or the CPU.

The web app I spent my days and night working on is very IO heavy. I've done micro benchmarks and real-world benchmarks to. And the results are up on my blog, have a look-see:

Use production data and environments

Micro-benchmarks are prone to distortion. If you can, make the effort to gather data from exactly what you plan to do, with the load you expect, on the hardware you expect.

My benchmarks are solid and reliable because they took place on a production system, a beefy system, a system under load, gathered in logs. Not my notebook's 7200 RPM 2.5" SATA drive while I watched intensely as the JVM work my hard disc.

What are you running on? It matters.

 

 

마치며..

nio 가 io 보다 성능이 낮게 나온다면 nio가 성능이 나오도록 제대로 코딩했는지 살펴볼 필요가 있다. bottleneck이 있는지, 버퍼를 너무 크게 잡았는지, api를 잘 사용했는지, 상황에 맞게 코딩했는지 살펴보면서 테스트하면 좋은 결과가 있을 것 같다.

Posted by '김용환'
,