Wpis z mikrobloga

#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)

# 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ć ( ͡º ͜ʖ͡º)
  • 11
@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.
@AwizisieAkat:
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
@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
@Porana123: sprawdzę przez weekend

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
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

@GotoFinal:

return *((jint*)(&f));
no ale troche oszukujesz tutaj bo jest jeszcze check isNan
@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