Vergelijk Directe en niet-directe ByteBuffer get/put-bewerkingen

Wordt get/put van een niet-directe bytebuffer sneller dan get/put van directe bytebuffer?

Als ik moet lezen/schrijven vanuit directe bytebuffer, is het dan beter eerst eerst een lokale bytearray te lezen/schrijven en vervolgens de directe bytebuffer volledig bij te werken (voor schrijft) met de bytearray?

10

2 antwoord

Wordt get/put van een niet-directe bytebuffer sneller dan get/put van directe bytebuffer?

Als u heap-buffer vergelijkt met directe buffer die geen native bytevolgorde gebruikt (de meeste systemen zijn klein endian en de standaard voor directe ByteBuffer is big endian), zijn de prestaties erg vergelijkbaar.

Als u native byte-buffers gebruikt, kan de prestatie aanzienlijk beter zijn voor multi-bytewaarden. Voor byte maakt het weinig uit, wat je ook doet.

In HotSpot/OpenJDK gebruikt ByteBuffer de Unsafe-klasse en veel van de native -methoden worden behandeld als intrinsiek . Dit is afhankelijk van JVM en AFAIK de Android VM behandelt het als een intrinsieke in recente versies.

Als u de gegenereerde assembly dumpt, kunt u zien dat de intrinsieke eigenschappen in Onveilig zijn omgezet in een instructie voor de machinecode. d.w.z. ze hebben niet de overhead van een JNI-oproep.

Als je bijvoorbeeld bezig bent met micro-tuning, zul je merken dat het grootste deel van de tijd van een ByteBuffer getXxxx of setXxxx wordt besteed aan het controleren van grenzen, niet aan de daadwerkelijke toegang tot het geheugen. Om deze reden gebruik ik nog steeds Unsafe direct wanneer ik nodig heb voor maximale prestaties (Opmerking: dit wordt afgeraden door Oracle)

Als ik moet lezen/schrijven vanuit directe bytebuffer, is het dan beter om eerst te lezen/schrijven in een lokale bytarasterreeks en dan de directe bytebuffer volledig bij te werken (voor schrijft) met de bytearray?

Ik zou het vreselijk vinden om te zien wat dat is beter dan. ;) Het klinkt heel ingewikkeld.

Vaak zijn de eenvoudigste oplossingen beter en sneller.


Je kunt dit zelf testen met deze code.

public static void main(String... args) {
    ByteBuffer bb1 = ByteBuffer.allocateDirect(256 * 1024).order(ByteOrder.nativeOrder());
    ByteBuffer bb2 = ByteBuffer.allocateDirect(256 * 1024).order(ByteOrder.nativeOrder());
    for (int i = 0; i < 10; i++)
        runTest(bb1, bb2);
}

private static void runTest(ByteBuffer bb1, ByteBuffer bb2) {
    bb1.clear();
    bb2.clear();
    long start = System.nanoTime();
    int count = 0;
    while (bb2.remaining() > 0)
        bb2.putInt(bb1.getInt());
    long time = System.nanoTime() - start;
    int operations = bb1.capacity()/4 * 2;
    System.out.printf("Each putInt/getInt took an average of %.1f ns%n", (double) time/operations);
}

prints

Each putInt/getInt took an average of 83.9 ns
Each putInt/getInt took an average of 1.4 ns
Each putInt/getInt took an average of 34.7 ns
Each putInt/getInt took an average of 1.3 ns
Each putInt/getInt took an average of 1.2 ns
Each putInt/getInt took an average of 1.3 ns
Each putInt/getInt took an average of 1.2 ns
Each putInt/getInt took an average of 1.2 ns
Each putInt/getInt took an average of 1.2 ns
Each putInt/getInt took an average of 1.2 ns

Ik ben er vrij zeker van dat een JNI-oproep langer duurt dan 1,2 ns.


Om aan te tonen dat het niet de "JNI" -roep is, maar de klier eromheen die de vertraging veroorzaakt. U kunt dezelfde lus rechtstreeks met Unsafe schrijven.

public static void main(String... args) {
    ByteBuffer bb1 = ByteBuffer.allocateDirect(256 * 1024).order(ByteOrder.nativeOrder());
    ByteBuffer bb2 = ByteBuffer.allocateDirect(256 * 1024).order(ByteOrder.nativeOrder());
    for (int i = 0; i < 10; i++)
        runTest(bb1, bb2);
}

private static void runTest(ByteBuffer bb1, ByteBuffer bb2) {
    Unsafe unsafe = getTheUnsafe();
    long start = System.nanoTime();
    long addr1 = ((DirectBuffer) bb1).address();
    long addr2 = ((DirectBuffer) bb2).address();
    for (int i = 0, len = Math.min(bb1.capacity(), bb2.capacity()); i < len; i += 4)
        unsafe.putInt(addr1 + i, unsafe.getInt(addr2 + i));
    long time = System.nanoTime() - start;
    int operations = bb1.capacity()/4 * 2;
    System.out.printf("Each putInt/getInt took an average of %.1f ns%n", (double) time/operations);
}

public static Unsafe getTheUnsafe() {
    try {
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        return (Unsafe) theUnsafe.get(null);
    } catch (Exception e) {
        throw new AssertionError(e);
    }
}

prints

Each putInt/getInt took an average of 40.4 ns
Each putInt/getInt took an average of 44.4 ns
Each putInt/getInt took an average of 0.4 ns
Each putInt/getInt took an average of 0.3 ns
Each putInt/getInt took an average of 0.3 ns
Each putInt/getInt took an average of 0.3 ns
Each putInt/getInt took an average of 0.3 ns
Each putInt/getInt took an average of 0.3 ns
Each putInt/getInt took an average of 0.3 ns
Each putInt/getInt took an average of 0.3 ns

U kunt dus zien dat de native -aanroep veel sneller is dan u zou verwachten voor een JNI-aanroep. De belangrijkste reden voor deze vertraging kan de L2-cachesnelheid zijn. ;)

Allen draaien op een i3 3.3 GHz

23
toegevoegd
@ zhong.j.yu Heb je tests om dit te laten zien?
toegevoegd de auteur Peter Lawrey, de bron
Zoals de naam al doet vermoeden, is het onveilig om te gebruiken en een fout kan het systeem doen crashen. d.w.z. het is sneller omdat alle beveiligingen uit zijn. Wat ik doe is twee implementaties, een die byteBuffers gebruikt zoals het is en een ander die onveilig gebruikt. Als ik vertrouwen heb in het testen van de software en ik het nodig heb, kun je de onveilige versie gebruiken.
toegevoegd de auteur Peter Lawrey, de bron
@MatejTymes 0.3 ns is één klokcyclus. Als u minder kunt krijgen, is uw code waarschijnlijk geoptimaliseerd. ;)
toegevoegd de auteur Peter Lawrey, de bron
@MatejTymes Als u een ByteBuffer in een andere kunt plaatsen, gebruikt u Unsgae.copyMemory onder de omslagen. Dit gaat sneller dan het tegelijkertijd kopiëren van één int . Het kan blokken geheugen in één keer kopiëren, b.v. 8 bytes of meer zonder een gebonden controle op elke toegang.
toegevoegd de auteur Peter Lawrey, de bron
@ Bober02 Als u Onveilig direct gebruikt, is het net zo snel als het gebruik van de heap. Het is niet veilig, noch natuurlijk, noch productief en niet zo gemakkelijk te onderhouden, maar kan net zo snel of sneller zijn (als u de lay-out van uw gegevens kunt/moet bepalen). Uit heapgeheugen vermindert ook de impact van GC's.
toegevoegd de auteur Peter Lawrey, de bron
@ Bober02 Er zijn geen garanties hoe dan ook. Veel hangt af van wat u test en zelfs van het systeem dat u gebruikt. Op een machine voor een test sta ik van de heap twee keer zo snel en op een ander systeem op de heap was 5x sneller. Wat je kunt zeggen is dat off heap afval kan verminderen, afhankelijk van hoe je het doet.
toegevoegd de auteur Peter Lawrey, de bron
@ user1643723 bedankt voor de correctie. Het kan op een gegeven moment in de afgelopen 5 jaar zijn toegevoegd. Ik had het ook niet nagekeken.
toegevoegd de auteur Peter Lawrey, de bron
In feite heb ik Unsafe gebruikt om opzettelijk het systeem te laten crashen, bijvoorbeeld Ik wil testen wat er gebeurt als de toepassing hier ;) crasht
toegevoegd de auteur Peter Lawrey, de bron
@Peter, je eerste voorbeeld zou absolute get/put moeten gebruiken, bb2.putInt (i, bb1.getInt (i)) die aanzienlijk sneller is.
toegevoegd de auteur ZhongYu, de bron
@Peter Ik heb je 1e test gebruikt, heb de loop vervangen door absolute get/put en heb die tijd met 40% verlaagd, vermoedelijk omdat er geen aanwijzerupdate is in absolute get/put
toegevoegd de auteur ZhongYu, de bron
Het deel over Android dat geen intrinsieke eigenschappen heeft, is onjuist. Te lui om Dalvik-bronnen te doorzoeken, maar in ieder geval op ART krijgen/put-methoden van directe buffers afgevaardigd naar intrinsieke methoden (om precies te zijn, ze gebruiken de interne geheugen -klasse, die beurtelings is geïmplementeerd via intrinsics )
toegevoegd de auteur user1643723, de bron
ik heb de implementatie bijgewerkt om alleen int view-buffers te gebruiken. op deze manier vermijd je byte-inpakken en uitpakken, dus het moet zeer performant zijn, zelfs zonder de onveilige klasse te gebruiken
toegevoegd de auteur Matej Tymes, de bron
@PeterLawrey yep, je hebt gelijk. niets kan dat verslaan :)
toegevoegd de auteur Matej Tymes, de bron
ik bedoel snelste zonder onveilig te gebruiken :)
toegevoegd de auteur Matej Tymes, de bron
dit is de snelste "ok" implementatie die ik kon krijgen: IntBuffer intBuffer1 = bb1.asIntBuffer (); IntBuffer intBuffer2 = bb2.asIntBuffer (); int count = intBuffer1.remaining (); voor (int i = 0; i
toegevoegd de auteur Matej Tymes, de bron
Bedankt Peter. Dit is erg handig. Trouwens, waarom raadt oracle aan om Unsafe niet rechtstreeks te gebruiken. Als we het in productiecode gebruiken, welke valkuilen kunnen er dan ontstaan?
toegevoegd de auteur user882659, de bron
Ik vroeg me af of de langere lees-/schrijftijden voor DirectByteBuffer versus direct gebruik van de onveilige, worden veroorzaakt door het feit dat met reads DirectByteBuffer de resultaten naar het heap-geheugen brengt, expliciet gebruik van onveilig niet?
toegevoegd de auteur Bober02, de bron
Zou in dit geval Heap ook niet beter presteren dan onveilig aanroepen?
toegevoegd de auteur Bober02, de bron
Op basis van deze twee artikelen en de eigenlijke tests die ik voer: mentablog.soliveirajr.com/2012/11/… en ashkrit.blogspot.co.uk/2013/07/… (code hier: github.com/ashkrit/blog/tree/master/allocation ) Ik raak in de war - in het eerste artikel wijzen de resultaten naar de hoop, en de auteur bewijst dat zowel bij het benaderen van objecten als bij aaneengesloten reeksen. Het tweede artikel dat een test uitvoert die lijkt op de eerste test in het eerste artikel, bewijst het tegenovergestelde ... Enige opmerkingen waarom?
toegevoegd de auteur Bober02, de bron

Een directe buffer bevat de gegevens in JNI-land, dus get() en put() moeten de JNI-grens overschrijden. Een niet-directe buffer houdt de gegevens vast in JVM-land.

Zo:

  1. Als u helemaal niet met de gegevens in Java land speelt, bijvoorbeeld gewoon een kanaal naar een ander kanaal kopiëren, directe buffers zijn sneller, omdat de gegevens helemaal niet de JNI-grens hoeven te overschrijden.

  2. Omgekeerd, als u met de gegevens in Java-land speelt, zal een niet-directe buffer sneller zijn. Of het significant is, hangt af van hoeveel gegevens de JNI-grens moeten passeren en ook van welke quanta elke keer worden overgedragen. Het verkrijgen of plaatsen van een enkele byte per keer van/naar een directe buffer kan bijvoorbeeld erg duur worden, waarbij het krijgen/plaatsen van 16384 bytes per keer de JNI-grenskosten aanzienlijk zou verminderen.

Om je tweede alinea te beantwoorden, zou ik een local byte [] array gebruiken, geen thread-local, maar als ik dan met de data in Java-land zou spelen, zou ik helemaal geen direct-byte-buffer gebruiken. Zoals de Javadoc zegt, zouden directe byte buffers alleen moeten worden gebruikt als ze een meetbaar prestatie-voordeel opleveren.

2
toegevoegd
@EJP - Ik heb een paar minuten besteed aan het bekijken van de Java 7-broncode en ik kon niet zien waar get en put op een directe buffer wordt geplaatst door JNI-aanroepen. Kun je aangeven waar het in de code dit doet?
toegevoegd de auteur Stephen C, de bron
De referentie waarnaar u linkt, zegt niet dat u een JNI-aanroep doet in get of put . Het vermeldt alleen JNI om te zeggen dat iets uiteindelijk roept sun.nio.ch.FileDispatcherImpl.write0 ... vermoedelijk wanneer de buffer vol is of je expliciet doorspoelt. Dit is ook geen verwijzing naar de code. Het is een verwijzing naar het vage commentaar van een vent op de code.
toegevoegd de auteur Stephen C, de bron
@EJP - Ik post niets. Ik wijs er slechts op dat de referentie die u beweerde dat JNI wordt gebruikt, niets dergelijks doet. In feite heeft Peter Lawley sterk bewijs geleverd dat JNI niet wordt gebruikt; d.w.z. dat de JIT-compiler de Unsafe.getXxx-oproepen optimaliseert naar side-step JNI.
toegevoegd de auteur Stephen C, de bron
Ik verwijs u terug naar mijn opmerking van "24 juni om 6:09". Het is duidelijk dat JNI op een gegeven moment gebeurt om de feitelijke I/O te doen. Er is geen bewijs dat get of put JNI-aanroepen doet om de gegevens in de buffer te lezen/schrijven. Er zijn andere manieren om het te doen. Geen van deze twee uitspraken is hiertegen in tegenspraak.
toegevoegd de auteur Stephen C, de bron
get/put passeert geen grenzen. Het is gecompileerde native code voor bijvoorbeeld memcpy (arr, & value, sizeof (value)); Na JITting is de montagesnelheid. Geen behoefte aan JNI in 2015.
toegevoegd de auteur Kr0e, de bron
@ user882659 Zie bewerking. Het heeft geen enkele zin om in dit geval een directe buffer te gebruiken.
toegevoegd de auteur EJP, de bron
@StephenC Zie de Javadoc. Het is onmogelijk, gezien de beschrijving en het doel waarvoor ze zijn bedoeld, dat een directe buffer niet JNI-oproepen doet.
toegevoegd de auteur EJP, de bron
@ Kr0e Onzin. Als wat u beweert waar was, geen directe bytebuffers zouden hoeven te bestaan.
toegevoegd de auteur EJP, de bron
@ user882659 Die suggestie postuleert dat HotSpot weet hoe JNI-code, of op zijn minst oproepen, evenals Java-bytecode moeten worden geoptimaliseerd. Ik ben niet op de hoogte van enig bewijs in die zin.
toegevoegd de auteur EJP, de bron
@StephenC Als u wilt postuleren dat er geen JNI-aspect is voor bytebuffers en dat ze identiek presteren als niet-directe bytebuffers, moet u (1) uitleg geven over de verschillen tussen directe bytebuffers en niet-directe bytes buffers waarbij JNI niet betrokken is, en die niet in tegenspraak is met de Javadoc, en (2) een benchmark die uw prestatievordering vertoont.
toegevoegd de auteur EJP, de bron
@StephenC Wat doen de uitspraken "Java Virtual Machine zal de beste poging doen om native I/O uit te voeren" en "De inhoud van directe buffers kan zich buiten de normale afvalophopte heap bevinden" verwijzen als er geen JNI-aspect is ?
toegevoegd de auteur EJP, de bron
@EJP, één vraag die ik heb is, als de code JIT is gecompileerd, is de JVM nog steeds de JNI-grens overschrijdend voor directe bytebuffer. Ik had verwacht dat JVM iets slims had gedaan.
toegevoegd de auteur user882659, de bron
toegevoegd de auteur user882659, de bron
Bedankt, Mijn berichtgrootte is meestal 256 bytes die ik naar socket wil schrijven, ik zat te denken aan het coderen van de bytes in een lokaal byte [] array en dan de byte array naar een directe bytebuffer kopiëren en de directe bytebuffer naar de socket sturen kanaal om te schrijven.
toegevoegd de auteur user882659, de bron
de directe bytebuffer worden samengevoegd. zou dit de voorkeur hebben of raadde je aan het bericht rechtstreeks in de directe bytebuffer te coderen in plaats van de tijdelijke bytearray te gebruiken?
toegevoegd de auteur user882659, de bron