ByteBuffer.allocate () 대 ByteBuffer.allocateDirect ()


144

allocate()나에 allocateDirect(), 그 질문은.

몇 년 동안 나는 DirectByteBuffers가 OS 수준에서 직접 메모리 매핑 이기 때문에 s보다 get / put 호출을 더 빠르게 수행 할 것이라는 생각에 매달렸다 HeapByteBuffer. 나는 지금까지 상황에 관한 정확한 세부 사항을 찾는 데 정말로 관심이 없었습니다. 나는 두 가지 유형 중 어느 ByteBuffer것이 더 빠르며 어떤 조건에 있는지 알고 싶습니다 .


특정 답변을 제공하려면 특정 답변을 구체적으로 말해야합니다. 하나가 항상 다른 것보다 빠르면 왜 두 가지 변형이 있습니까? BTW : DirectByteBuffer에 대한 코드를 읽었습니까?
피터 로리

SocketChannel비 블로킹 용으로 구성된 s 에서 읽고 쓰는 데 사용됩니다 . @bmargulies가 말한 것과 관련하여 DirectByteBuffers는 채널에서 더 빠르게 수행됩니다.

@Gnarly 적어도 내 대답의 현재 버전은 채널에 도움이 될 것이라고 말합니다.
bmargulies

답변:


150

그의 뛰어난 저서 인 Java NIO 에서 Ron Hitches는 귀하의 질문에 대한 좋은 답변이 될 수 있다고 생각한 것을 제공하는 것 같습니다.

운영 체제는 메모리 영역에서 I / O 작업을 수행합니다. 이러한 메모리 영역은 운영 체제와 관련하여 연속적인 바이트 시퀀스입니다. 바이트 버퍼 만 I / O 작업에 참여할 수 있다는 것은 놀라운 일이 아닙니다. 또한 운영 체제는 프로세스의 주소 공간 (이 경우 JVM 프로세스)에 직접 액세스하여 데이터를 전송합니다. 이는 I / O 펄의 대상인 메모리 영역이 연속적인 바이트 시퀀스 여야한다는 것을 의미합니다. JVM에서 바이트 배열이 연속적으로 메모리에 저장되지 않거나 가비지 콜렉터가 언제든지이를 이동할 수 있습니다. 배열은 Java의 객체이며 해당 객체에 데이터가 저장되는 방식은 JVM 구현마다 다를 수 있습니다.

이러한 이유로 직접 버퍼의 개념이 도입되었습니다. 직접 버퍼는 채널 및 기본 I / O 루틴과의 상호 작용을위한 것입니다. 바이트 코드를 운영 체제에 메모리 영역을 직접 비우거나 채우도록 지시하는 원시 코드를 사용하여 채널이 직접 또는 원시 액세스에 사용할 수있는 메모리 영역에 바이트 요소를 저장하기 위해 최선의 노력을 다합니다.

직접 바이트 버퍼는 일반적으로 I / O 작업에 가장 적합한 선택입니다. 설계 상으로는 JVM에서 사용할 수있는 가장 효율적인 I / O 메커니즘을 지원합니다. 비 직접 바이트 버퍼는 채널로 전달 될 수 있지만 성능이 저하 될 수 있습니다. 비 직접 버퍼가 기본 I / O 작업의 대상이되는 것은 일반적으로 불가능합니다. 쓰기를 위해 비 직접 ByteBuffer 객체를 채널에 전달하면 각 호출에서 채널이 암시 적으로 다음을 수행 할 수 있습니다.

  1. 임시 직접 ByteBuffer 객체를 만듭니다.
  2. 비 직접 버퍼의 내용을 임시 버퍼로 복사하십시오.
  3. 임시 버퍼를 사용하여 저수준 I / O 작업을 수행하십시오.
  4. 임시 버퍼 개체가 범위를 벗어 났으며 결국 가비지 수집됩니다.

이로 인해 잠재적으로 모든 I / O에서 버퍼 복사 및 객체 이탈이 발생할 수 있으며, 이는 바로 우리가 피하고 싶은 것들입니다. 그러나 구현에 따라 상황이 나쁘지 않을 수 있습니다. 런타임은 직접 버퍼를 캐시 및 재사용하거나 다른 영리한 트릭을 수행하여 처리량을 높일 수 있습니다. 단순히 일회용 버퍼를 만드는 경우 그 차이는 크지 않습니다. 반면, 고성능 시나리오에서 버퍼를 반복적으로 사용하는 경우 직접 버퍼를 할당하고 재사용하는 것이 좋습니다.

직접 버퍼는 I / O에 최적이지만 비 직접 바이트 버퍼보다 ​​작성 비용이 더 많이들 수 있습니다. 직접 버퍼가 사용하는 메모리는 표준 JVM 힙을 무시하고 기본 운영 체제 별 코드를 호출하여 할당됩니다. 직접 버퍼 설정 및 해제는 호스트 운영 체제 및 JVM 구현에 따라 힙 상주 버퍼보다 ​​훨씬 비쌀 수 있습니다. 직접 버퍼의 메모리 저장 영역은 표준 JVM 힙 외부에 있으므로 가비지 수집 대상이 아닙니다.

직접 버퍼와 비 직접 버퍼의 성능 트레이드 오프는 JVM, 운영 체제 및 코드 디자인에 따라 크게 달라질 수 있습니다. 힙 외부에 메모리를 할당하면 응용 프로그램이 JVM을 인식하지 못하는 추가적인 힘을받을 수 있습니다. 추가로 움직이는 부품을 사용할 때는 원하는 효과를 얻을 수 있어야합니다. 오래된 소프트웨어를 권장합니다. 먼저 작동시킨 다음 빠르게 만드십시오. 최적화에 대해 너무 걱정하지 마십시오. 정확성에 먼저 집중하십시오. JVM 구현은 버퍼 캐싱 또는 기타 최적화를 수행하여 불필요한 노력 없이도 필요한 성능을 제공 할 수 있습니다.


9
나는 너무 많은 추측을 포함하고 있기 때문에 그 인용을 좋아하지 않습니다. 또한 JVM은 비 직접 ByteBuffer에 대해 IO를 수행 할 때 직접 ByteBuffer를 할당 할 필요가 없습니다. 힙에서 바이트 시퀀스를 malloc하고 IO를 수행하고 바이트에서 ByteBuffer로 복사하여 바이트를 해제하는 것으로 충분합니다. 이러한 영역은 캐시 될 수도 있습니다. 그러나이를 위해 Java 객체를 할당 할 필요는 없습니다. 실제 답변은 측정을 통해서만 얻을 수 있습니다. 마지막으로 측정했을 때 큰 차이는 없었습니다. 구체적인 세부 사항을 모두 찾으려면 테스트를 다시 실행해야합니다.
Robert Klemme

4
NIO (및 기본 오퍼레이션)를 설명하는 책에 확실성이있을 수 있는지는 의문입니다. 결국, JVM과 운영 체제에 따라 다른 방식으로 관리하므로 특정 동작을 보장 할 수 없다는 책임이 저자에게 있습니다.
Martin Tuskevicius

@RobertKlemme, +1, 우리 모두는 추측을 싫어하지만, 주요 OS가 너무 많기 때문에 모든 주요 OS의 성능을 측정하는 것이 불가능할 수 있습니다. 다른 게시물에서 시도했지만 "OS에 따라 결과가 크게 변동 됨"으로 시작하여 벤치 마크와 관련된 많은 문제 를 볼 수 있습니다 . 또한 모든 I / O에서 버퍼 복사와 같은 끔찍한 일을하는 검은 양이 있다면 어떨까요? 그리고 그 때문에 양, 우리는 우리가 그렇지 않은 경우, 사용하는 것이 코드를 작성 방지하기 위해 어쩔 수없이 단지 이러한 최악의 시나리오를 피할 수 있습니다.
Pacerier

@RobertKlemme 동의합니다. 여기에는 너무 많은 추측이 있습니다. 예를 들어 JVM은 바이트 배열을 드물게 할당 할 가능성이 거의 없습니다.
Lorne의 후작

@ Edwin Dalorzo : 우리는 왜 현실 세계에서 그러한 바이트 버퍼가 필요합니까? 프로세스간에 메모리를 공유하는 핵으로 발명 되었습니까? 예를 들어 JVM이 프로세스에서 실행되고 데이터 전송을 담당하는 네트워크 또는 데이터 링크 계층에서 실행되는 다른 프로세스라고 가정하면 이러한 바이트 버퍼는 이러한 프로세스간에 메모리를 공유하도록 할당됩니까? 내가 틀렸다면 저를 바로 잡으십시오 ..
Tom Taylor

25

jvm 내부 에서 액세스하기 위해 직접 버퍼가 더 빠를 것으로 예상 할 이유가 없습니다 . 모든 종류의 채널 뒤의 코드와 같은 네이티브 코드로 전달할 때 장점이 있습니다.


과연. 마찬가지로 Scala / Java에서 IO를 수행하고 알고리즘 처리를 위해 대용량 메모리 데이터가 포함 된 내장 Python / 기본 라이브러리를 호출하거나 Tensorflow의 GPU에 데이터를 직접 공급해야합니다.
SemanticBeeng

21

DirectByteBuffers는 OS 수준에서 직접 메모리 매핑이기 때문에

그렇지 않습니다. 그것들은 단지 일반적인 애플리케이션 프로세스 메모리이지만 Java GC 동안 재배치되지 않아 JNI 계층 내부의 것들을 상당히 단순화시킵니다. 설명하는 내용이에 적용됩니다 MappedByteBuffer.

get / put 호출로 더 빨리 수행 할 수 있도록

결론은 전제에서 따르지 않습니다. 전제는 거짓이다. 결론도 틀 렸습니다. JNI 계층에 들어가면 속도가 빠르며, 데이터를 JNI 경계를 넘지 않아도되기 때문에 데이터를 읽고 쓰는 DirectByteBuffer경우 훨씬 빠릅니다.


7
이것은 좋은 중요한 점입니다. IO의 경로에서 어떤 시점 에서 Java-JNI 경계를 넘어야 합니다. 직접 및 비 직접 바이트 버퍼는 경계 만 이동합니다. 직접 버퍼를 사용하면 Java 랜드의 모든 put 작업이 교차되어야하고 비 직접 버퍼의 경우 모든 IO 작업이 교차해야합니다. 더 빠른 것은 응용 프로그램에 따라 다릅니다.
Robert Klemme

@RobertKlemme 요약이 잘못되었습니다. 모든 버퍼에서 Java로 들어오고 나가는 모든 데이터는 JNI 경계를 넘어야합니다. 직접 버퍼의 요점은 데이터를 한 채널에서 다른 채널로 복사하는 경우 (예 : 파일 업로드) 전혀 Java로 가져올 필요가 없으므로 훨씬 빠릅니다.
Lorne의 후작

내 요약이 정확히 어디입니까? 그리고 무엇 "요약"으로 시작합니까? 나는 "자바 토지에서 작업을 넣어"에 대해 명시 적으로 이야기했다. 채널 사이에서만 데이터를 복사하는 경우 (즉, Java 랜드의 데이터를 처리 할 필요가없는 경우) 다른 이야기입니다.
Robert Klemme

@RobertKlemme '직접 버퍼 [전용]만으로 Java 토지의 모든 넣기 작업이 교차해야한다'는 말이 잘못되었습니다. 도착과 풋이 교차해야합니다.
Lorne의 후작

EJP, @RobertKlemme은 한 문장에서 "put operations"이라는 단어를 사용하고 문장의 대조되는 문구에서 "IO operations"라는 단어를 사용하여 의도 한 구별을 여전히 잃어버린 것 같습니다. 후자의 구절에서, 그의 의도는 버퍼와 OS 제공 장치 사이의 작업을 언급하는 것이 었습니다.
naki

18

직접 측정하는 것이 가장 좋습니다. 빠른 대답은 크기에 따라 allocateDirect()버퍼 에서 전송하는 것이 allocate()변형에 비해 시간이 25 %에서 75 % 덜 걸리는 것 (파일을 / dev / null로 테스트 한 것으로 확인 됨)이지만 할당 자체가 상당히 느려질 수 있습니다. 100 배의 계수).

출처 :


감사. 귀하의 답변을 수락 할 것이지만 성능 차이에 대한보다 구체적인 내용을 찾고 있습니다.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.