과거에 Java NIO관련 포스팅을 몇 번 했습니다. 그리고 꼭 allocateDirect()
를 이용하여 커널버퍼를 직접 사용하는 것을 권장 했습니다. 하지만 allocateDirect()
메서드는 allocate()
메서드에 비해 굉장히 오버헤드가 심하므로 한번만 allocateDirect()
를 쓰고 해당 ByteBuffer
는 재활용 해야합니다. 필요할 때마다 만들어 쓰지 않고 한 번 만들어서 필요할 때마다 다시 써야 좋은 퍼포먼스를 낼 수 있습니다. 하지만 allocate()
로 할당할때와 allocateDirect()
로 할당 할 때의 속도 차이는 대체 얼마나 나는 걸까요. 직접 비교해 보았습니다.
1. 테스트 코드 작성
테스트 코드를 작성하여 속도 비교를 하려고 했습니다. ByteBuffer
를 allocate()
메서드로 할당, allocateDirect()
메서드로 할 당 한 것, 그리고 byte 배열을 할당한 것 3가지 케이스를 테스트 하려고 합니다. 모두 1024바이트의 버퍼를 생성해 보겠습니다. 테스트 코드는 다음과 같습니다.
long startTime = System.currentTimeMillis();
for(int i = 0; i<1000000 ; i++){
ByteBuffer buf = ByteBuffer.allocate(1024);
// ByteBuffer buf = ByteBuffer.allocateDirect(1024);
// byte[] buf = new byte[1024];
}
long endTime = System.currentTimeMillis();
long elapsedTime = endTime - startTime;
System.out.println("elapsedTime = " + elapsedTime)
위에서 주석 처리한 부분을 하나씩 풀어 실행 시킬 것 입니다.
2. 퍼포먼스 비교
각 모두 for문을 통해 백만번 할당을 하면서 결과를 비교하겠습니다. 단위는 밀리새컨드입니다.
byte[] | non-direct ByteBuffer | direct ByteBuffer | |
---|---|---|---|
1차 | 328 | 375 | 10063 |
2차 | 360 | 391 | 8719 |
3차 | 312 | 375 | 8656 |
4차 | 344 | 375 | 8625 |
5차 | 313 | 375 | 8875 |
6차 | 296 | 359 | 8516 |
7차 | 313 | 375 | 8625 |
8차 | 312 | 375 | 8594 |
9차 | 297 | 375 | 8641 |
10차 | 313 | 406 | 8734 |
결과를 보면 byte 배열 할당보다 non-direct ByteBuffer 할당이 살짝 느린 것으로 나타납니다만 그렇게 큰 차이는 아닙니다. channel을 이용할 수 있다는 점에서 때론 byte 배열 할당보다 non-direct ByteBuffer를 이용하는 것이 효과적일 때도 있으니 그리 큰 차이는 아닙니다. 하지만 direct ByteBuffer는 위 두가지 경우와 큰 차이를 보입니다. 속다가 25배가넘네요.
참고로 1차의 기록은 JVM초기화에 필요한 시간도 포함이므로 큰 의미는 없습니다.
3. Direct ByteBuffer는 재활용 하자!
보시는바와 같이 byte[]
와 non-direct ByteBuffer에 비해 direct ByteBuffer의 할당 속도가 매우 느린 것으로 나타납니다. 따라서 ByteBuffer
가 필요할때마다 그때그떄 할당해서 사용한다면 프로그램 퍼포먼스에 큰 영향을 줄 수 있습니다. 이 오버헤드는 무시 할 수 있을 것 같지 않습니다. 따라서 ByteBuffer
를 재활용하여 사용합시다. 재활용 방법으로는 ByteBufferPool이 가능 할 것입니다. 자세한건 Pool패턴을 참고합시다.