ByteBuffer.allocate () ile ByteBuffer.allocateDirect ()


144

İşte allocate()ya da allocateDirect()böyle, soru bu.

Birkaç yıldır DirectByteBuffers'nin işletim sistemi düzeyinde doğrudan bir bellek eşlemesi olduğu için, alma / koyma çağrılarıyla HeapByteBuffers'den daha hızlı performans göstereceği fikrine bağlı kaldım . Durumla ilgili kesin detayları şimdiye kadar bulmakla hiç ilgilenmemiştim. İki türden hangisinin ByteBufferdaha hızlı ve hangi şartlarda olduğunu bilmek istiyorum .


Belirli bir cevap vermek için, onlarla ne yaptığınızı özellikle belirtmeniz gerekir. Biri her zaman diğerinden daha hızlı olsaydı, neden iki varyant olurdu? Belki de neden şimdi "kesin detayları bulmakla gerçekten ilgileniyorsunuz" üzerine genişleyebilirsiniz BTW: DirectByteBuffer için esp kodunu okudunuz mu?
Peter Lawrey

SocketChannelEngelleme için yapılandırılmış olanlardan okumak ve bunlara yazmak için kullanılırlar . @Bmargulies'in söylediklerine göre, DirectByteBufferkanallar için daha hızlı performans gösterecek.

@Gnarly Cevabımın en azından güncel sürümü kanalların faydalanmasının beklendiğini söylüyor.
bmargulies

Yanıtlar:


150

Ron Hitches mükemmel kitabında Java NIO , sorunuza iyi bir cevap olabileceğini düşündüğüm şeyi sunuyor gibi görünüyor:

İşletim sistemleri bellek alanlarında G / Ç işlemleri gerçekleştirir. Bu bellek alanları, işletim sistemi ile ilgili olarak, bitişik bayt dizileridir. O zaman sadece bayt arabelleklerinin G / Ç işlemlerine katılmaya uygun olması şaşırtıcı değildir. Ayrıca, işletim sisteminin verileri aktarmak için işlemin adres alanına, bu durumda JVM işlemine doğrudan erişeceğini unutmayın. Bu, G / Ç işlemlerinin hedefi olan bellek alanlarının bitişik bayt dizileri olması gerektiği anlamına gelir. JVM'de, bir bayt dizisi bitişik olarak bellekte saklanamayabilir veya Çöp Toplayıcı herhangi bir zamanda taşıyabilir. Diziler Java'daki nesnelerdir ve verilerin bu nesnenin içinde depolanma biçimi bir JVM uygulamasından diğerine değişebilir.

Bu nedenle, doğrudan bir tampon kavramı getirildi. Doğrudan tamponlar kanallarla ve yerel G / Ç rutinleriyle etkileşim için tasarlanmıştır. Bayt öğelerini, işletim sistemine doğrudan bellek alanını boşaltmasını veya doldurmasını söylemek için yerel kodu kullanarak kanalın doğrudan veya ham erişim için kullanabileceği bir bellek alanında saklamak için en iyi çabayı gösterirler.

Doğrudan bayt arabellekleri genellikle G / Ç işlemleri için en iyi seçimdir. Tasarım gereği, JVM'nin kullanabileceği en verimli I / O mekanizmasını desteklerler. Doğrudan olmayan bayt arabellekleri kanallara geçirilebilir, ancak bunu yapmak performans cezasına neden olabilir. Doğrudan olmayan bir arabelleğin yerel bir G / Ç işleminin hedefi olması genellikle mümkün değildir. Yazmak için bir kanala doğrudan olmayan bir ByteBuffer nesnesi iletirseniz, kanal her çağrıda dolaylı olarak aşağıdakileri yapabilir:

  1. Geçici bir doğrudan ByteBuffer nesnesi oluşturun.
  2. Doğrudan olmayan arabelleğin içeriğini geçici arabelleğe kopyalayın.
  3. Geçici arabelleği kullanarak düşük seviye G / Ç işlemini gerçekleştirin.
  4. Geçici arabellek nesnesi kapsam dışına çıkar ve sonunda çöp toplanır.

Bu, potansiyel olarak her G / Ç üzerinde tam olarak kaçınmak istediğimiz şeyler olan tampon kopyalamaya ve nesne karmaşasına neden olabilir. Ancak, uygulamaya bağlı olarak, işler bu kadar kötü olmayabilir. Çalışma zamanı, doğrudan arabellekleri önbelleğe alır ve yeniden kullanır veya verimi artırmak için başka akıllı numaralar gerçekleştirir. Tek seferlik kullanım için sadece bir tampon oluşturuyorsanız, fark önemli değildir. Öte yandan, ara belleği yüksek performanslı bir senaryoda tekrar tekrar kullanacaksanız, doğrudan arabellekleri ayırıp yeniden kullanmanız daha iyi olur.

Doğrudan arabellekler I / O için en uygunudur, ancak doğrudan bayt arabelleklerinden daha pahalı olabilirler. Doğrudan arabellekler tarafından kullanılan bellek, standart JVM yığınını atlayarak yerel, işletim sistemine özgü koda çağrılarak tahsis edilir. Doğrudan arabelleklerin ayarlanması ve yırtılması, ana bilgisayar işletim sistemine ve JVM uygulamasına bağlı olarak yığınta yerleşik arabelleklerden önemli ölçüde daha pahalı olabilir. Doğrudan arabelleklerin bellek depolama alanları, standart JVM yığınının dışında oldukları için çöp toplamaya tabi değildir.

Doğrudan ve doğrudan olmayan arabellekleri kullanma performans sıralamaları JVM, işletim sistemi ve kod tasarımına göre büyük farklılıklar gösterebilir. Yığın dışında bellek ayırarak, uygulamanızı JVM'nin farkında olmadığı ek güçlere maruz bırakabilirsiniz. Ek hareketli parçaları oyuna dahil ederken, istediğiniz efekti elde ettiğinizden emin olun. Eski yazılım maxim'i öneriyorum: önce çalışmasını sağlayın, sonra hızlı yapın. Optimizasyon konusunda çok endişelenmeyin; önce doğruluk üzerine konsantre olun. JVM uygulaması, çok fazla gereksiz çaba harcamadan size ihtiyacınız olan performansı verecek tampon önbellekleme veya diğer optimizasyonları gerçekleştirebilir.


9
Bu alıntıyı sevmiyorum çünkü çok fazla tahmin içeriyor. Ayrıca, JVM'nin doğrudan olmayan bir ByteBuffer için IO yaparken kesinlikle doğrudan bir ByteBuffer tahsis etmesi gerekmez: yığın üzerindeki bir bayt dizisini yanlış konumlandırmak, IO yapmak, baytlardan ByteBuffer'a kopyalamak ve baytları serbest bırakmak yeterlidir. Bu alanlar bile önbelleğe alınabilir. Ancak bunun için bir Java nesnesi tahsis etmek tamamen gereksizdir. Gerçek cevaplar sadece ölçümden elde edilecektir. En son ölçüm yaptığımda önemli bir fark yoktu. Tüm spesifik detayları bulmak için testleri tekrarlamam gerekecekti.
Robert Klemme

4
NIO'yu (ve yerel işlemleri) açıklayan bir kitabın içinde kesinlikler olup olmadığı şüphelidir. Sonuçta, farklı JVM'ler ve işletim sistemleri işleri farklı şekilde yönetir, böylece yazar belirli davranışları garanti edemediği için suçlanamaz.
Martin Tuskevicius

@RobertKlemme, +1, hepimiz tahmin çalışmasından nefret ediyoruz, Ancak, çok fazla büyük işletim sistemi olduğu için tüm büyük işletim sistemleri için performansı ölçmek imkansız olabilir. Başka bir yazı bunu denedi, ancak "sonuçlar işletim sistemine bağlı olarak geniş ölçüde dalgalanıyor" ile başlayarak karşılaştırmalı değerlendirme ile ilgili birçok sorun görebiliyoruz . Ayrıca, her G / Ç'de arabellek kopyalama gibi korkunç şeyler yapan bir kara koyun varsa ne olur? O zaman bu koyun yüzünden, aksi takdirde kullanacağımız kodların yazılmasını önlemek zorunda kalabiliriz, sadece bu en kötü senaryolardan kaçınmak için.
Pacerier

@RobertKlemme katılıyorum. Burada çok fazla tahmin var. JVM'nin bayt dizilerini seyrek olarak tahsis etmesi pek olası değildir.
Lorne Marquis

@Edwin Dalorzo: Neden gerçek dünyada böyle bir bayt tamponuna ihtiyacımız var? Süreç arasında hafızayı paylaşmak için bir kesmek olarak icat edildi mi? Diyelim ki JVM bir işlem üzerinde çalışıyor ve ağ veya veri bağlantı katmanında çalışan başka bir işlem olurdu - verilerin iletilmesinden sorumlu olan - bu bayt tamponları bu işlemler arasında belleği paylaşmak için mi ayrılmış? Yanılıyorsam lütfen beni düzeltin ..
Tom Taylor

25

Doğrudan arabelleklerin jvm içindeki erişim için daha hızlı olmasını beklemek için hiçbir neden yoktur . Avantajları, onları her türlü kanalın arkasındaki kod gibi yerel koda geçirdiğinizde gelir.


Aslında. Gibi, Scala / Java IO yapmak ve algoritmik işleme veya doğrudan Tensorflow bir GPU veri beslemek için büyük bellek verileri ile gömülü Python / yerli libs çağırmak gerektiğinde.
SemanticBeeng

21

DirectByteBuffers işletim sistemi düzeyinde doğrudan bir bellek eşlemesi olduğundan

Onlar değil. Bunlar sadece normal uygulama işlem hafızasıdır, ancak Java GC sırasında JNI katmanındaki şeyleri önemli ölçüde basitleştiren yer değiştirmeye tabi değildir. Açıkladığınız şeyler için geçerlidir MappedByteBuffer.

get / put çağrıları ile daha hızlı performans göstermesi

Sonuç öncülten gelmez; premiss yanlıştır; ve sonuç da yanlıştır. Eğer okuma ve aynı yazılı olup olmadığını Onlar JNI'yı tabakasının içine bir kez daha hızlı ve DirectByteBufferbunlar çok veri hiç JNI'yı sınırını geçmeye vardır çünkü hiçbir zaman, daha hızlı.


7
Bu iyi ve önemli bir noktadır: IO yolunda Java geçmek zorunda - En JNI'yı sınır bazı noktada. Doğrudan ve doğrudan olmayan bayt arabellekleri yalnızca sınırı taşır: doğrudan bir arabellekle Java topraklarından tüm koyma işlemlerinin geçmesi gerekirken, doğrudan olmayan bir arabellekle tüm IO işlemlerinin geçmesi gerekir. Daha hızlı olan şey uygulamaya bağlıdır.
Robert Klemme

@RobertKlemme Özetiniz yanlış. Tüm arabelleklerde, Java'ya gelen ve Java'dan gelen tüm veriler JNI sınırını aşmak zorundadır. Doğrudan arabelleklerin amacı, verileri bir kanaldan diğerine kopyalarsanız, örneğin bir dosya yüklüyorsanız, Java'ya hiç girmenize gerek yoktur, bu da çok daha hızlıdır.
Lorne Marquis

özetim tam olarak nerede yanlış? Ve hangi "özet" ile başlayalım? Açıkça "Java ülkesinden operasyonlar" hakkında konuşuyordum. Yalnızca Kanallar arasında veri kopyalarsanız (yani, Java ülkesindeki verilerle asla uğraşmak zorunda kalmazsanız) bu farklı bir hikaye.
Robert Klemme

@RobertKlemme 'Doğrudan bir arabellekle [sadece] Java toprağından tüm koyma işlemlerinin geçmesi gerektiği' ifadeniz yanlıştır. Hem alır hem de koyar geçmek gerekir.
Lorne

EJP, görünüşe göre @RobertKlemme'in "put operasyonları" kelimelerini tek bir cümleyle kullanmayı ve cümlenin karşıt cümlesinde "IO operasyonları" kelimelerini kullanmayı seçerek hala amaçlanan ayrımı kaçırıyorsunuz. İkinci ifadede amacı, tampon ile OS tarafından sağlanan bir tür aygıt arasındaki işlemleri ifade etmekti.
naki

18

Kendi ölçümlerinizi yapmak için en iyisi. Hızlı yanıt, bir allocateDirect()arabellekten göndermenin allocate(), boyuta bağlı olarak , varyanttan % 25 ila% 75 daha az zaman aldığını (bir dosyayı / dev / null'a kopyalama olarak test edildi), ancak ayırmanın kendisinin önemli ölçüde daha yavaş olabileceğini ( 100x faktörü).

Kaynaklar:


Teşekkürler. Cevabınızı kabul ediyorum ama performans farklarıyla ilgili daha spesifik ayrıntılar arıyorum.
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.