Czy są jakieś korzyści związane z wydajnością? Czy jest to specyficzne dla kompilatora / maszyny wirtualnej? Używam Hotspot.
Czy są jakieś korzyści związane z wydajnością? Czy jest to specyficzne dla kompilatora / maszyny wirtualnej? Używam Hotspot.
Odpowiedzi:
Po pierwsze: nie powinieneś dokonywać wyboru statycznego lub niestatycznego na podstawie wydajności.
Po drugie: w praktyce nie będzie to miało znaczenia. Hotspot może zdecydować się na optymalizację w taki sposób, aby połączenia statyczne były szybsze dla jednej metody, a połączenia niestatyczne szybciej dla innej.
Po trzecie: wiele mitów otaczających statyczne i niestatyczne jest oparte albo na bardzo starych maszynach JVM (które nie były w żaden sposób zbliżone do optymalizacji, jaką robi Hotspot) lub na kilku zapamiętanych ciekawostkach dotyczących C ++ (w których dynamiczne wywołanie używa jeszcze jednego dostępu do pamięci niż połączenie statyczne).
Cztery lata później...
Okay, mając nadzieję na rozwiązanie tego problemu raz na zawsze, napisałem test porównawczy, który pokazuje, jak różne rodzaje połączeń (wirtualne, niewirtualne, statyczne) porównują się ze sobą.
Uruchomiłem go na ideone i oto co mam:
(Większa liczba iteracji jest lepsza).
Success time: 3.12 memory: 320576 signal:0
Name | Iterations
VirtualTest | 128009996
NonVirtualTest | 301765679
StaticTest | 352298601
Done.
Zgodnie z oczekiwaniami wywołania metod wirtualnych są najwolniejsze, wywołania metod niewirtualnych są szybsze, a wywołania metod statycznych są jeszcze szybsze.
Nie spodziewałem się, że różnice będą tak wyraźne: zmierzono, że wywołania metod wirtualnych działały z mniej niż połową szybkości wywołań metod niewirtualnych, które z kolei działały o całe 15% wolniej niż wywołania statyczne. To właśnie pokazują te pomiary; rzeczywiste różnice muszą być w rzeczywistości nieco bardziej wyraźne, ponieważ dla każdego wywołania metody wirtualnej, nie wirtualnej i statycznej mój kod porównawczy ma dodatkowy stały narzut zwiększania jednej zmiennej całkowitej, sprawdzania zmiennej boolowskiej i zapętlania, jeśli nie jest prawdą.
Przypuszczam, że wyniki będą się różnić w zależności od procesora i od JVM do JVM, więc spróbuj i zobacz, co otrzymasz:
import java.io.*;
class StaticVsInstanceBenchmark
{
public static void main( String[] args ) throws Exception
{
StaticVsInstanceBenchmark program = new StaticVsInstanceBenchmark();
program.run();
}
static final int DURATION = 1000;
public void run() throws Exception
{
doBenchmark( new VirtualTest( new ClassWithVirtualMethod() ),
new NonVirtualTest( new ClassWithNonVirtualMethod() ),
new StaticTest() );
}
void doBenchmark( Test... tests ) throws Exception
{
System.out.println( " Name | Iterations" );
doBenchmark2( devNull, 1, tests ); //warmup
doBenchmark2( System.out, DURATION, tests );
System.out.println( "Done." );
}
void doBenchmark2( PrintStream printStream, int duration, Test[] tests ) throws Exception
{
for( Test test : tests )
{
long iterations = runTest( duration, test );
printStream.printf( "%15s | %10d\n", test.getClass().getSimpleName(), iterations );
}
}
long runTest( int duration, Test test ) throws Exception
{
test.terminate = false;
test.count = 0;
Thread thread = new Thread( test );
thread.start();
Thread.sleep( duration );
test.terminate = true;
thread.join();
return test.count;
}
static abstract class Test implements Runnable
{
boolean terminate = false;
long count = 0;
}
static class ClassWithStaticStuff
{
static int staticDummy;
static void staticMethod() { staticDummy++; }
}
static class StaticTest extends Test
{
@Override
public void run()
{
for( count = 0; !terminate; count++ )
{
ClassWithStaticStuff.staticMethod();
}
}
}
static class ClassWithVirtualMethod implements Runnable
{
int instanceDummy;
@Override public void run() { instanceDummy++; }
}
static class VirtualTest extends Test
{
final Runnable runnable;
VirtualTest( Runnable runnable )
{
this.runnable = runnable;
}
@Override
public void run()
{
for( count = 0; !terminate; count++ )
{
runnable.run();
}
}
}
static class ClassWithNonVirtualMethod
{
int instanceDummy;
final void nonVirtualMethod() { instanceDummy++; }
}
static class NonVirtualTest extends Test
{
final ClassWithNonVirtualMethod objectWithNonVirtualMethod;
NonVirtualTest( ClassWithNonVirtualMethod objectWithNonVirtualMethod )
{
this.objectWithNonVirtualMethod = objectWithNonVirtualMethod;
}
@Override
public void run()
{
for( count = 0; !terminate; count++ )
{
objectWithNonVirtualMethod.nonVirtualMethod();
}
}
}
static final PrintStream devNull = new PrintStream( new OutputStream()
{
public void write(int b) {}
} );
}
Warto zauważyć, że ta różnica w wydajności ma zastosowanie tylko do kodu, który nie robi nic innego niż wywoływanie metod bez parametrów. Jakikolwiek inny kod, który masz między wywołaniami, osłabi różnice, co obejmuje przekazywanie parametrów. W rzeczywistości 15% różnica między wywołaniami statycznymi i niewirtualnymi jest prawdopodobnie w pełni wyjaśniona przez fakt, że this
wskaźnik nie musi być przekazywany do metody statycznej. Tak więc wystarczyłaby tylko dość mała ilość kodu wykonującego trywialne rzeczy pomiędzy wywołaniami, aby różnica między różnymi rodzajami wywołań została rozcieńczona do punktu, w którym nie miałoby żadnego wpływu netto.
Nie bez powodu istnieją również wywołania metod wirtualnych; mają cel do spełnienia i są wdrażane przy użyciu najbardziej wydajnych środków zapewnianych przez podstawowy sprzęt. (Zestaw instrukcji procesora). Jeśli chcąc je wyeliminować, zastępując je wywołaniami nie wirtualnymi lub statycznymi, w końcu będziesz musiał dodać nawet jotę dodatkowego kodu, aby emulować ich funkcjonalność, wówczas wynikowy narzut netto jest ograniczony nie mniej, ale więcej. Całkiem możliwe, że dużo, dużo, niewyobrażalnie dużo, więcej.
VirtualTest | 488846733 -- NonVirtualTest | 480530022 -- StaticTest | 484353198
na mojej instalacji OpenJDK. FTR: To prawda, nawet jeśli usunę final
modyfikator. Przy okazji. Musiałem zrobić terminate
pole volatile
, inaczej test się nie skończył.
VirtualTest | 12451872 -- NonVirtualTest | 12089542 -- StaticTest | 8181170
. Nie dość, że OpenJDK na moim notebooku jest w stanie wykonać 40 razy więcej iteracji, test statyczny zawsze ma około 30% mniejszą przepustowość. Może to być zjawisko specyficzne dla ART, ponieważ otrzymuję oczekiwany wynik na tablecie z Androidem 4.4:VirtualTest | 138183740 -- NonVirtualTest | 142268636 -- StaticTest | 161388933
Cóż, wywołań statycznych nie można przesłonić (więc zawsze są kandydatami do wstawiania) i nie wymagają żadnych sprawdzeń nieważności. HotSpot wykonuje kilka fajnych optymalizacji, na przykład metod, które mogą równie dobrze negować te zalety, ale są one możliwe powody, dla których statyczne wywołanie może być szybsze.
Nie powinno to jednak wpływać na Twój projekt - kodowanie w najbardziej czytelny, naturalny sposób - i martw się o tego rodzaju mikro-optymalizację tylko wtedy, gdy masz tylko przyczynę (której prawie nigdy nie będziesz).
Jest to specyficzne dla kompilatora / maszyny wirtualnej.
Dlatego prawdopodobnie nie warto się tym przejmować, chyba że zidentyfikujesz to jako naprawdę krytyczny problem z wydajnością w swojej aplikacji. Przedwczesna optymalizacja jest źródłem wszelkiego zła itp ...
Jednak ja nie widziałem tego optymalizacja daje znaczny wzrost wydajności w następującej sytuacji:
Jeśli powyższe dotyczy Ciebie, warto przetestować.
Jest jeszcze jeden dobry (i potencjalnie nawet ważniejszy!) Powód, aby używać metody statycznej - jeśli metoda faktycznie ma semantykę statyczną (tj. Logicznie nie jest połączona z daną instancją klasy), to warto uczynić ją statyczną aby odzwierciedlić ten fakt. Doświadczeni programiści Java zauważą wtedy modyfikator static i natychmiast pomyślą: „aha! Ta metoda jest statyczna, więc nie wymaga instancji i prawdopodobnie nie manipuluje stanem konkretnej instancji”. Dzięki temu skutecznie przekazujesz statyczny charakter metody ...
Jest niewiarygodnie mało prawdopodobne, że jakakolwiek różnica w wydajności wywołań statycznych i niestatycznych ma wpływ na działanie aplikacji. Pamiętaj, że „przedwczesna optymalizacja jest źródłem wszelkiego zła”.
Jak powiedzieli poprzednie postery: Wydaje się, że to przedwczesna optymalizacja.
Jest jednak jedna różnica (część z faktu, że wywołania niestatyczne wymagają dodatkowego wypchnięcia obiektu wywoływanego na stos operandów):
Ponieważ metod statycznych nie można zastąpić, nie będzie żadnych wirtualnych wyszukiwań w czasie wykonywania dla wywołania metody statycznej. W pewnych okolicznościach może to spowodować zauważalną różnicę.
Różnica na poziomie kodu bajtowego polega na tym, że wywołanie metody niestatycznej jest wykonywane za pośrednictwem INVOKEVIRTUAL
, INVOKEINTERFACE
lub INVOKESPECIAL
podczas gdy wywołanie metody statycznej jest wykonywane za pośrednictwem INVOKESTATIC
.
invokespecial
ponieważ nie jest wirtualna.
7 lat później ...
Nie mam dużej pewności co do wyników, które znalazł Mike Nakis, ponieważ nie rozwiązują one niektórych typowych problemów związanych z optymalizacją Hotspot. Dokonałem instrumentacji testów porównawczych za pomocą JMH i stwierdziłem, że narzut metody instancji wynosi około 0,75% na moim komputerze w porównaniu z wywołaniem statycznym. Biorąc pod uwagę ten niski narzut, myślę, że z wyjątkiem operacji najbardziej wrażliwych na opóźnienia, prawdopodobnie nie jest to największy problem w projektowaniu aplikacji. Podsumowanie wyników mojego benchmarku JMH jest następujące;
java -jar target/benchmark.jar
# -- snip --
Benchmark Mode Cnt Score Error Units
MyBenchmark.testInstanceMethod thrpt 200 414036562.933 ± 2198178.163 ops/s
MyBenchmark.testStaticMethod thrpt 200 417194553.496 ± 1055872.594 ops/s
Możesz spojrzeć na kod tutaj na Github;
https://github.com/nfisher/svsi
Sam test porównawczy jest dość prosty, ale ma na celu zminimalizowanie eliminacji martwego kodu i ciągłego zwijania. Prawdopodobnie istnieją inne optymalizacje, które przeoczyłem / przeoczyłem i te wyniki mogą się różnić w zależności od wersji JVM i systemu operacyjnego.
package ca.junctionbox.svsi;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.infra.Blackhole;
class InstanceSum {
public int sum(final int a, final int b) {
return a + b;
}
}
class StaticSum {
public static int sum(final int a, final int b) {
return a + b;
}
}
public class MyBenchmark {
private static final InstanceSum impl = new InstanceSum();
@State(Scope.Thread)
public static class Input {
public int a = 1;
public int b = 2;
}
@Benchmark
public void testStaticMethod(Input i, Blackhole blackhole) {
int sum = StaticSum.sum(i.a, i.b);
blackhole.consume(sum);
}
@Benchmark
public void testInstanceMethod(Input i, Blackhole blackhole) {
int sum = impl.sum(i.a, i.b);
blackhole.consume(sum);
}
}
ops/s
głównie w środowisku ART (np. Zużycie pamięci, zmniejszony rozmiar pliku .oat itp.). Czy znasz jakieś stosunkowo proste narzędzia / sposoby, w jakie można by spróbować porównać te inne wskaźniki?
Przy podejmowaniu decyzji, czy metoda powinna być statyczna, aspekt wydajności powinien być nieistotny. Jeśli masz problem z wydajnością, statyczne ustawienie wielu metod nie uratuje sytuacji. To powiedziawszy, metody statyczne prawie na pewno nie są wolniejsze niż jakakolwiek metoda instancji, w większości przypadków nieznacznie szybsze :
1.) Metody statyczne nie są polimorficzne, więc JVM ma mniej decyzji do podjęcia, aby znaleźć właściwy kod do wykonania. Jest to kwestia sporna w Age of Hotspot, ponieważ Hotspot zoptymalizuje wywołania metod instancji, które mają tylko jedną witrynę implementacji, więc będą wykonywać to samo.
2.) Inną subtelną różnicą jest to, że metody statyczne oczywiście nie mają „tego” odniesienia. Powoduje to, że ramka stosu o jedną szczelinę jest mniejsza niż w przypadku metody instancji z tą samą sygnaturą i treścią („this” jest umieszczane w szczelinie 0 zmiennych lokalnych na poziomie kodu bajtowego, podczas gdy w przypadku metod statycznych szczelina 0 jest używana jako pierwsza parametr metody).
Może istnieć różnica i może się to udać w przypadku każdego konkretnego fragmentu kodu, a nawet może się zmienić nawet przy niewielkim wydaniu maszyny JVM.
Jest to zdecydowanie część z 97% małych wydajności, o których należy zapomnieć .
TableView
milionów rekordów.
Teoretycznie tańsze.
Inicjalizacja statyczna zostanie wykonana nawet jeśli utworzysz instancję obiektu, podczas gdy metody statyczne nie wykonają żadnej inicjalizacji normalnie wykonywanej w konstruktorze.
Jednak nie testowałem tego.
Jak zauważa Jon, metod statycznych nie można przesłonić, więc proste wywołanie metody statycznej może być - w wystarczająco naiwnym środowisku wykonawczym Java - szybsze niż wywołanie metody instancji.
Ale nawet zakładając, że jesteś w punkcie, w którym zależy ci na zepsuciu projektu, aby zaoszczędzić kilka nanosekund, pojawia się kolejne pytanie: czy potrzebujesz metody nadpisującej siebie? Jeśli zmienisz swój kod, aby przekształcić metodę instancji w metodę statyczną, aby zaoszczędzić nanosekundę tu i tam, a następnie obrócisz i zaimplementujesz własny dyspozytor, prawie na pewno będzie mniej wydajny niż ten zbudowany do środowiska wykonawczego Java.
Chciałbym dodać do innych świetnych odpowiedzi tutaj, że zależy to również od twojego przepływu, na przykład:
Public class MyDao {
private String sql = "select * from MY_ITEM";
public List<MyItem> getAllItems() {
springJdbcTemplate.query(sql, new MyRowMapper());
};
};
Zwróć uwagę, że tworzysz nowy obiekt MyRowMapper dla każdego wywołania.
Zamiast tego sugeruję użycie tutaj pola statycznego.
Public class MyDao {
private static RowMapper myRowMapper = new MyRowMapper();
private String sql = "select * from MY_ITEM";
public List<MyItem> getAllItems() {
springJdbcTemplate.query(sql, myRowMapper);
};
};