Öyle ya da böyle herhangi bir performans avantajı var mı? Derleyiciye / sanal makineye özel mi? Hotspot kullanıyorum.
Öyle ya da böyle herhangi bir performans avantajı var mı? Derleyiciye / sanal makineye özel mi? Hotspot kullanıyorum.
Yanıtlar:
İlk olarak: Performans temelinde statik ve statik olmayan arasında seçim yapmamalısınız.
İkincisi: pratikte herhangi bir fark yaratmayacak. Hotspot, bir yöntem için statik çağrıları daha hızlı, statik olmayan çağrıları ise başka bir yöntem için daha hızlı hale getirecek şekilde optimize etmeyi seçebilir.
Üçüncüsü: Statik ve statik olmayanı çevreleyen efsanelerin çoğu ya çok eski JVM'lere (Hotspot'un yaptığı optimizasyonun yakınında hiçbir yerde bulunmadı) ya da C ++ hakkında bazı hatırlanan bilgiler (dinamik bir çağrının bir bellek erişimi daha kullandığı statik bir çağrıdan daha fazla).
Dört yıl sonra...
Tamam, bu soruyu sonsuza kadar çözme umuduyla, farklı arama türlerinin (sanal, sanal olmayan, statik) birbirleriyle nasıl karşılaştırıldığını gösteren bir kıyaslama yazdım.
Bunu ideone üzerinde çalıştırdım ve elimde bu:
(Daha fazla sayıda yineleme daha iyidir.)
Success time: 3.12 memory: 320576 signal:0
Name | Iterations
VirtualTest | 128009996
NonVirtualTest | 301765679
StaticTest | 352298601
Done.
Beklendiği gibi, sanal yöntem çağrıları en yavaş olanıdır, sanal olmayan yöntem çağrıları daha hızlıdır ve statik yöntem çağrıları daha da hızlıdır.
Beklemediğim şey, farklılıkların bu kadar belirgin olmasıydı: Sanal yöntem çağrıları, sanal olmayan yöntem çağrılarının yarısından daha az hızda çalışacak şekilde ölçüldü, bu da statik çağrılardan % 15 daha yavaş çalışacak şekilde ölçüldü . Bu ölçümler bunu gösteriyor; Her sanal, sanal olmayan ve statik yöntem çağrısı için, kıyaslama kodumun bir tamsayı değişkenini artırma, bir boole değişkenini kontrol etme ve doğru değilse döngü gibi ek bir sabit ek yükü olduğundan, gerçek farklılıklar aslında biraz daha belirgin olmalıdır.
Sanırım sonuçlar CPU'dan CPU'ya ve JVM'den JVM'ye değişecek, bu yüzden bir deneyin ve ne elde ettiğinizi görün:
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) {}
} );
}
Bu performans farkının yalnızca parametresiz yöntemleri çağırmaktan başka hiçbir şey yapmayan koda uygulanabilir olduğuna dikkat etmek önemlidir. Çağrılar arasında sahip olduğunuz diğer kod ne olursa olsun, farklılıkları azaltacaktır ve buna parametre geçişi de dahildir. Aslında, durağan ve sanal olmayan çağrılar arasındaki% 15'lik fark, muhtemelen işaretçinin statik yönteme geçirilmesine gerek olmadığı gerçeğiyle tam olarak açıklanmaktadır this
. Dolayısıyla, farklı çağrı türleri arasındaki farkın hiçbir net etkiye sahip olmayacak şekilde seyreltilmesi için çağrılar arasında önemsiz şeyler yapan oldukça az miktarda kod gerekir.
Ayrıca, sanal yöntem çağrılarının bir nedeni vardır; hizmet verecek bir amacı vardır ve temel donanım tarafından sağlanan en verimli araçlar kullanılarak uygulanırlar. (CPU komut seti.) Bunları sanal olmayan veya statik çağrılarla değiştirerek ortadan kaldırmak istiyorsanız, işlevselliklerini taklit etmek için bir miktar fazladan kod eklemek zorunda kalırsanız, sonuçta ortaya çıkan net ek yükünüz bağlanır. daha az değil, daha çok olmak. Büyük olasılıkla, çok, çok, anlaşılmaz biçimde çok, daha fazlası.
VirtualTest | 488846733 -- NonVirtualTest | 480530022 -- StaticTest | 484353198
OpenJDK kurulumumda. FTR: final
Değiştiriciyi kaldırırsam bu bile doğru . Btw. terminate
Sahaya gitmek zorundaydım volatile
, aksi halde test bitmedi.
VirtualTest | 12451872 -- NonVirtualTest | 12089542 -- StaticTest | 8181170
. Sadece dizüstü bilgisayarımdaki OpenJDK, 40 kat daha fazla yineleme gerçekleştirmeyi başarmakla kalmıyor, statik test her zaman yaklaşık% 30 daha az iş hacmine sahip. Bu, SANAT'a özgü bir fenomen olabilir, çünkü Android 4.4 tablette beklenen sonucu alıyorum:VirtualTest | 138183740 -- NonVirtualTest | 142268636 -- StaticTest | 161388933
Eh, statik aramalar olamaz (bu nedenle her zaman inlining aday olan) ve herhangi bir hükümsüzlük kontrolleri gerekmez geçersiz kılınan olun. HotSpot, bu avantajları tamamen ortadan kaldırabilecek yöntemler gibi bir dizi harika optimizasyon yapar, ancak bunlar statik bir çağrının daha hızlı olmasının olası nedenleri.
Bununla birlikte, bu tasarımınızı etkilememelidir - en okunaklı, doğal şekilde kodlayın - ve bu tür bir mikro optimizasyon için yalnızca haklı bir sebebiniz varsa endişelenmelisiniz (ki bunu neredeyse asla yapmayacaksınız).
Derleyiciye / VM'ye özgüdür.
Bu nedenle, bunu uygulamanızda gerçekten kritik bir performans sorunu olarak tanımlamadıysanız, muhtemelen rahatsız etmeye değmez. Erken optimizasyon, tüm kötülüklerin vb. Köküdür ...
Ancak var bu optimizasyon aşağıdaki durumda önemli bir performans artışı elde görülme:
Yukarıdakiler sizin için geçerliyse, test etmeye değer olabilir.
Statik bir yöntemi kullanmak için başka bir iyi (ve potansiyel olarak daha da önemli!) Neden daha vardır - eğer yöntemin aslında statik semantiği varsa (yani mantıksal olarak sınıfın belirli bir örneğine bağlı değilse), o zaman onu statik yapmak mantıklıdır. bu gerçeği yansıtmak için. Deneyimli Java programcıları daha sonra statik değiştiriciyi fark edecek ve hemen "aha! Bu yöntemin statik olduğunu, böylece bir örneğe ihtiyaç duymayacağını ve muhtemelen örneğe özgü durumu değiştirmediğini" düşüneceklerdir. Böylece yöntemin statik doğasını etkili bir şekilde iletmiş olacaksınız ...
Statik ve statik olmayan çağrıların performansındaki herhangi bir farkın uygulamanızda bir fark yaratması inanılmaz derecede olası değildir. "Erken optimizasyonun tüm kötülüklerin kökü olduğunu" unutmayın.
Önceki posterlerin dediği gibi: Bu, erken bir optimizasyona benziyor.
Bununla birlikte, bir fark vardır (statik olmayan çağrıların işlemsel yığına bir aranan nesnenin ek bir itilmesini gerektirmesi gerçeğinin bir kısmı):
Statik yöntemler geçersiz kılınamadığından, çalışma zamanında statik yöntem çağrısı için herhangi bir sanal arama olmayacaktır . Bu, bazı durumlarda gözlemlenebilir bir farka neden olabilir.
Bayt kod düzeyinde fark olmayan statik yöntem çağrısı aracılığıyla yapılır olmasıdır INVOKEVIRTUAL
, INVOKEINTERFACE
ya INVOKESPECIAL
statik bir yöntem çağrısı aracılığıyla yapılır iken INVOKESTATIC
.
invokespecial
sanal olmadığı için kullanılarak çağrılır .
7 yıl sonra ...
Mike Nakis'in bulduğu sonuçlara çok fazla güvenmiyorum çünkü bunlar Hotspot optimizasyonları ile ilgili bazı genel sorunları ele almıyorlar. JMH kullanarak karşılaştırmalar yaptım ve bir örnek yönteminin ek yükünün makinemde statik bir çağrıya kıyasla yaklaşık% 0,75 olduğunu buldum. Düşük ek yük göz önüne alındığında, gecikmeye duyarlı operasyonların çoğu dışında, muhtemelen bir uygulama tasarımındaki en büyük endişe olmadığını düşünüyorum. JMH benchmarkımın özet sonuçları aşağıdaki gibidir;
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
Koda buradan Github'da bakabilirsiniz;
https://github.com/nfisher/svsi
Kriterin kendisi oldukça basittir, ancak ölü kodu ortadan kaldırmayı ve sürekli katlamayı en aza indirmeyi amaçlamaktadır. Muhtemelen kaçırdığım / gözden kaçırdığım başka optimizasyonlar da var ve bu sonuçların JVM sürümüne ve işletim sistemine göre değişiklik göstermesi muhtemel.
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
birincil olarak bir ART ortamı dışındaki ölçütler üzerinde sahip olabileceği olası faydaları merak ediyorum (örneğin, bellek kullanımı, azaltılmış .oat dosya boyutu vb.). Bu diğer ölçümleri karşılaştırmak için nispeten basit araçlar / yollar biliyor musunuz?
Bir yöntemin statik olup olmayacağına karar vermek için performans yönü ilgisiz olmalıdır. Bir performans sorununuz varsa, birçok yöntemi statik yapmak günü kurtarmayacaktır. Bununla birlikte, statik yöntemler neredeyse kesinlikle herhangi bir örnek yöntemden daha yavaş değildir , çoğu durumda marjinal olarak daha hızlıdır :
1.) Statik yöntemler polimorfik değildir, bu nedenle JVM'nin yürütülecek gerçek kodu bulmak için daha az karar vermesi gerekir. Hotspot, yalnızca bir uygulama sitesi olan örnek yöntemi çağrılarını optimize edeceğinden, Hotspot Age of Hotspot'ta tartışmalı bir noktadır.
2.) Bir başka ince fark, statik yöntemlerin açıkça "bu" referansa sahip olmamasıdır. Bu, aynı imzaya ve gövdeye sahip bir örnek yönteminden bir daha küçük bir yığın çerçevesi ile sonuçlanır ("bu", bayt kodu seviyesindeki yerel değişkenlerin 0 numaralı yuvasına yerleştirilirken, statik yöntemler için yuva 0, ilk yöntemin parametresi).
Bir fark olabilir ve herhangi bir kod parçası için her iki yönde de olabilir ve JVM'nin küçük bir sürümüyle bile değişebilir.
Bu, kesinlikle unutmanız gereken küçük verimliliklerin% 97'sinin bir parçasıdır .
TableView
milyonlarca kayıt için bir arama .
Teorik olarak daha ucuz.
Statik başlatma, nesnenin bir örneğini oluştursanız bile yapılacaktır, oysa statik yöntemler normalde bir yapıcıda yapılan herhangi bir başlatma yapmaz.
Ancak bunu test etmedim.
Jon'un belirttiği gibi, statik yöntemler geçersiz kılınamaz, bu nedenle basitçe statik bir yöntemi çağırmak - yeterince saf bir Java çalışma zamanında - bir örnek yöntemini çağırmaktan daha hızlı olabilir .
Ama sonra, birkaç nanosaniye tasarruf etmek için tasarımınızı karıştırmayı önemsediğiniz noktada olduğunuzu varsayarsak bile, bu sadece başka bir soruyu gündeme getiriyor: kendinizi geçersiz kılacak yönteme ihtiyacınız olacak mı? Burada ve orada bir nanosaniye tasarruf etmek için bir örnek yöntemini statik bir yönteme dönüştürmek için kodunuzu değiştirirseniz ve ardından geri dönüp bunun üzerine kendi dağıtım programınızı uygularsanız, sizinki neredeyse kesinlikle oluşturulandan daha az verimli olacaktır. zaten Java çalışma zamanınıza.
Buradaki diğer harika yanıtlara bunun sizin akışınıza da bağlı olduğunu eklemek isterim, örneğin:
Public class MyDao {
private String sql = "select * from MY_ITEM";
public List<MyItem> getAllItems() {
springJdbcTemplate.query(sql, new MyRowMapper());
};
};
Her arama için yeni bir MyRowMapper nesnesi oluşturduğunuza dikkat edin.
Bunun yerine burada statik bir alan kullanmanızı öneririm.
Public class MyDao {
private static RowMapper myRowMapper = new MyRowMapper();
private String sql = "select * from MY_ITEM";
public List<MyItem> getAllItems() {
springJdbcTemplate.query(sql, myRowMapper);
};
};