#java #programowanie #jvm Jako takie trochę sprostowanie/rozwinięcie wpisu @AwizisieAkat przedstawiam własne wyniki, bo coś mi się nie podobały jego.
Moje wyniki:
// Benchmark Mode Cnt Score Error Units // FloatToIntBits.floatToIntBits avgt 5 4,565 ± 0,018 ns/op // FloatToIntBits.floatToIntBitsNative avgt 5 8,232 ± 0,012 ns/op // FloatToIntBits.unsafeRawAllocation avgt 5 81,395 ± 0,507 ns/op // FloatToIntBits.unsafeSharedAllocation_not_thread_safe avgt 5 4,865 ± 0,024 ns/op // FloatToIntBits.unsafeThreadPooledAllocation avgt 5 6,894 ± 0,099 ns/op I teraz o co chodzi, java ogólnie sobie świetnie z jitem radzi, trzeba dac jej tylko trochę czasu, natywna funkcja jest ciut wolniejsza jednak, a unsafe działa tak samo szybko jak java, o ile za każdym razem nie alokujesz nowego fragmentu pamięci - to długo trwa. (bez zwalniania pamięci wynik to ~63ns, więc zwalnianie też trwa).
Podsumowując: Jit radzi sobie świetnie i metoda z javy jest w tym zestawieniu optymalna. Metoda natywna przegrywa - co mnie nie dziwi, ten kod jest tak prosty że overhead wywołania natywnej metody jest większy niż to co potrafi wygenerować JIT dla tak prostego kodu. Alokacja pamięci z Unsafe jest powolna. (jak na taką skalę, no bo kto normlany alokuje 4 bajty co kilka ns) Jeśli użyjesz jednego fragmentu pamięci dla wszystkich operacji to wydajność jest taka sama jak metody z JDK, jednak nie jest to thread-safe. Co mnie pozytywnie zaskoczyło metoda z threadpoolem wychodzi całkiem nieźle pomimo faktu użycia typu obiektowego, z porządniejszym kodem pewnie da się osiągnąć 5ns
Ale ogólnie jak zawsze - zostaw robotę JDK, w większości przypadków poradzi sobie najlepiej, natywne metody zostaw do kodu gdzie potrzebujesz dostępu do czegoś niżej lub wydajności w przetwarzaniu dużych byte[] (konwertery, kodowanie, kompresja itd)
@AwizisieAkat: nie widzę byś wyłączał tam JIT, tylko nie napisałeś odpowiedniego kodu do benchmarkowania, benchmarkowanie to bardzo trudny temat, sam tu raczkuje, nie można tak po prostu w pętli sobie testować, bo wtedy ladownaie sie JVM i klas spowalnia pierwsze wykonania, tak samo JIT dopiero się rozgrzewa.
JMH poprawnie użyty pozwala pozbyć się tych błędów w miły i przyjazny sposób pozwalając na dokładniejsze benchmarkowanie.
@GotoFinal: i moje: jdk: 32 ns / op native 60 ns / op unsafe: 187 ns / op
z jitem wychodz mi gówno, bo mój kod do testowania jest wysoce optymalizowalny - nie wykorzystuje nigdzie wyników więc cośtam pewnie ucieka, ale napisze co wyszło: jdk: 0 ns / op native: 15 ns / op unsafe 60 ns / op
@AwizisieAkat: bez JHM też można coś benchmarkować, tylko że trzeba umieć, testować tylko 1 metodą na raz, przed prawdziwym testem zrobić pętlę z wykonaniem metody dla testu, i dopiero potem próbować testować. No i lepiej użyć System.nanoTime Ale jednak JHM radzi sobie lepiej, bo np upewnia się że return z metody zostanie uznany przez JIT jako używany, więc na pewno nie usunie kodu itd, no i zwyczajniej prościej napisać taki benchmark
@GotoFinal: @AwizisieAkat: Z tą metodą to chyba trochę odpalanie helikoptera żeby skosić trawę bo jak sam mówisz samo wywołanie natywnej kosztuje zbyt dużo, jak by to mniej więcej mogło wyglądać dla poteżnych natywnych metod? Kumpel robi inżynierkę dorabiając 'na boku' funkcjonalność do jakiegoś gotowego projektu, który jest w Javie i w tym projekcie jest sporo metod natywnych wywołujących kod z C, i nie są to metody sięgające do pamięci systemowej
@Porana123: Jit radzi sobie bardzo dobrze z wieloma rzeczami, a z bardzo abstrakcyjnym i obiektowym kodem pokazuje magie jakiej nie osiągnie natywny kod, jednak jak przychodzi do kodu nisko-poziomowego jak przetwarzanie dużej ilości prostych danych, gdzie nie masz potrzeby żadnych klas itd, to narzut javy nawet na takich prostych rzeczach jak sprawdzenie czy index array mieści się w zakresie, potrafi być zauważalny, np nie znajdziesz wydajnego kompresora czy kodeka w czystej
od jakiego momentu takie coś się może opłacać i czy w ogóle.
@Porana123: jak masz duzo alokacji (jesli Float a nie float) - robiac natywnie unikniesz gc, ale i tak trzeba bencharkowac, bo z tego co wiem teraz jest escapeanalysis i obiekt w javie (w zasadzie jego pola) moga sie znalezc na stosie i nie ma gc
@afe1: kod natywny kopiowałem od @AwizisieAkat, ja z cpp to ten, dodać, odjać, wyświetlić, z 30 minut się bawiłem jak to skompilować (✌゚∀゚)☞
A tak to możliwości jest sporo, ale i tak nie ma co kombinować jak nie robimy właśnie czegoś co operuje na dużych prostych danych. A testowanie małych metod w JMH jakoś tam działa, ale nie można tego czytać jako "metoda wykonuje
@afe1: @GotoFinal: a u mnie nie ma oszustwa xd wystarczy podmienić metodę floatToIntBits na floatToRawIntBits, ale nie wydaje mi się, żeby to miało duże znaczenie - wyniki wyszły dość podobne
Jako takie trochę sprostowanie/rozwinięcie wpisu @AwizisieAkat przedstawiam własne wyniki, bo coś mi się nie podobały jego.
Moje wyniki:
// Benchmark Mode Cnt Score Error Units
// FloatToIntBits.floatToIntBits avgt 5 4,565 ± 0,018 ns/op
// FloatToIntBits.floatToIntBitsNative avgt 5 8,232 ± 0,012 ns/op
// FloatToIntBits.unsafeRawAllocation avgt 5 81,395 ± 0,507 ns/op
// FloatToIntBits.unsafeSharedAllocation_not_thread_safe avgt 5 4,865 ± 0,024 ns/op
// FloatToIntBits.unsafeThreadPooledAllocation avgt 5 6,894 ± 0,099 ns/op
I teraz o co chodzi, java ogólnie sobie świetnie z jitem radzi, trzeba dac jej tylko trochę czasu, natywna funkcja jest ciut wolniejsza jednak, a unsafe działa tak samo szybko jak java, o ile za każdym razem nie alokujesz nowego fragmentu pamięci - to długo trwa. (bez zwalniania pamięci wynik to ~63ns, więc zwalnianie też trwa).
Podsumowując:
Jit radzi sobie świetnie i metoda z javy jest w tym zestawieniu optymalna.
Metoda natywna przegrywa - co mnie nie dziwi, ten kod jest tak prosty że overhead wywołania natywnej metody jest większy niż to co potrafi wygenerować JIT dla tak prostego kodu.
Alokacja pamięci z Unsafe jest powolna. (jak na taką skalę, no bo kto normlany alokuje 4 bajty co kilka ns)
Jeśli użyjesz jednego fragmentu pamięci dla wszystkich operacji to wydajność jest taka sama jak metody z JDK, jednak nie jest to thread-safe.
Co mnie pozytywnie zaskoczyło metoda z threadpoolem wychodzi całkiem nieźle pomimo faktu użycia typu obiektowego, z porządniejszym kodem pewnie da się osiągnąć 5ns
Ale ogólnie jak zawsze - zostaw robotę JDK, w większości przypadków poradzi sobie najlepiej, natywne metody zostaw do kodu gdzie potrzebujesz dostępu do czegoś niżej lub wydajności w przetwarzaniu dużych byte[] (konwertery, kodowanie, kompresja itd)
# JMH 1.17.5
# VM version: JDK 1.8.0121
Cały użyty kod użyty do testów dostępny jest tutaj: https://gist.github.com/GotoFinal/2e61c2243d0f2b3421b5c1dd5a36a6e6
Wołam też tych co się tam wypowiadali dla spokoju: @CiekawskiJ @Zdupcyngiel @dan3k @Kresse
Jeśli coś zrypałem to pisać ( ͡º ͜ʖ͡º)
JMH poprawnie użyty pozwala pozbyć się tych błędów w miły i przyjazny sposób pozwalając na dokładniejsze benchmarkowanie.
jdk: 32 ns / op
native 60 ns / op
unsafe: 187 ns / op
z jitem wychodz mi gówno, bo mój kod do testowania jest wysoce optymalizowalny - nie wykorzystuje nigdzie wyników więc cośtam pewnie ucieka, ale napisze co wyszło:
jdk: 0 ns / op
native: 15 ns / op
unsafe 60 ns / op
wrzuce też tu linka do mojego kodu: https://github.com/awizisieakat/float-to-int-test
może ogarnę to jmh
To ja dodam od siebie bez jita:
Benchmark Mode Cnt Score Error Units
FloatToIntBits.floatToIntBits avgt 3 108,776 ± 3,745 ns/op
FloatToIntBits.floatToIntBitsNative avgt 3 94,566 ± 1,107 ns/op
FloatToIntBits.unsafeRawAllocation avgt 3 266,908 ± 27,911 ns/op
FloatToIntBits.unsafeSharedAllocation_not_thread_safe avgt 3 147,806 ± 4,424 ns/op
FloatToIntBits.unsafeThreadPooledAllocation avgt 3 324,423 ± 42,538 ns/op
No i lepiej użyć System.nanoTime
Ale jednak JHM radzi sobie lepiej, bo np upewnia się że return z metody zostanie uznany przez JIT jako używany, więc na pewno nie usunie kodu itd, no i zwyczajniej prościej napisać taki benchmark
moje wyniki z jmh, wszystkie ustawienia takie same
JIT
Benchmark Mode Cnt Score Error Units
Main.jdk avgt 5 2,534 ± 0,409 ns/op
Main.nat avgt 5 8,471 ± 0,333 ns/op
Main.unsafe avgt 5 92,973 ± 22,487 ns/op
bez JIT
Benchmark Mode Cnt Score Error Units
Main.jdk avgt 5 102,189 ± 18,350 ns/op
Main.nat avgt 5 79,242 ± 7,291 ns/op
Main.unsafe avgt 5 237,860 ± 13,847 ns/op
@Porana123: jak masz duzo alokacji (jesli Float a nie float) - robiac natywnie unikniesz gc, ale i tak trzeba bencharkowac, bo z tego co wiem teraz jest escapeanalysis i obiekt w javie (w zasadzie jego pola) moga sie znalezc na stosie i nie ma gc
@GotoFinal:
return *((jint*)(&f));
no ale troche oszukujesz tutaj bo jest jeszcze check isNan
A tak to możliwości jest sporo, ale i tak nie ma co kombinować jak nie robimy właśnie czegoś co operuje na dużych prostych danych.
A testowanie małych metod w JMH jakoś tam działa, ale nie można tego czytać jako "metoda wykonuje