Apakah ada manfaat kinerja dengan satu atau lain cara? Apakah compiler / VM spesifik? Saya menggunakan Hotspot.
Apakah ada manfaat kinerja dengan satu atau lain cara? Apakah compiler / VM spesifik? Saya menggunakan Hotspot.
Jawaban:
Pertama: Anda tidak boleh membuat pilihan statis vs non-statis berdasarkan kinerja.
Kedua: dalam praktiknya, tidak ada bedanya. Hotspot dapat memilih untuk mengoptimalkan dengan cara yang membuat panggilan statis lebih cepat untuk satu metode, panggilan non-statis lebih cepat untuk metode lainnya.
Ketiga: banyak mitos seputar statis versus non-statis didasarkan pada JVM yang sangat lama (yang tidak mendekati pengoptimalan seperti yang dilakukan Hotspot), atau beberapa hal sepele yang diingat tentang C ++ (di mana panggilan dinamis menggunakan satu lagi akses memori daripada panggilan statis).
Empat tahun kemudian...
Oke, dengan harapan dapat menjawab pertanyaan ini sekali dan selamanya, saya telah menulis sebuah patokan yang menunjukkan bagaimana berbagai jenis panggilan (virtual, non-virtual, statis) dibandingkan satu sama lain.
Saya menjalankannya dengan ideone , dan inilah yang saya dapatkan:
(Jumlah iterasi yang lebih besar lebih baik.)
Success time: 3.12 memory: 320576 signal:0
Name | Iterations
VirtualTest | 128009996
NonVirtualTest | 301765679
StaticTest | 352298601
Done.
Seperti yang diharapkan, panggilan metode virtual adalah yang paling lambat, panggilan metode non-virtual lebih cepat, dan panggilan metode statis bahkan lebih cepat.
Apa yang tidak saya harapkan adalah perbedaannya menjadi begitu jelas: Panggilan metode virtual diukur untuk berjalan kurang dari setengah kecepatan panggilan metode non-virtual, yang pada gilirannya diukur untuk menjalankan keseluruhan 15% lebih lambat daripada panggilan statis. Itulah yang ditunjukkan oleh pengukuran ini; perbedaan aktual sebenarnya harus sedikit lebih jelas, karena untuk setiap panggilan metode virtual, nonvirtual, dan statis, kode pembandingan saya memiliki overhead konstan tambahan untuk menambah satu variabel integer, memeriksa variabel boolean, dan mengulang jika tidak benar.
Saya kira hasilnya akan bervariasi dari CPU ke CPU, dan dari JVM ke JVM, jadi cobalah dan lihat apa yang Anda dapatkan:
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) {}
} );
}
Perlu dicatat bahwa perbedaan kinerja ini hanya berlaku untuk kode yang tidak melakukan apa pun selain menjalankan metode tanpa parameter. Kode lain apa pun yang Anda miliki di antara pemanggilan akan mengurangi perbedaan, dan ini termasuk penerusan parameter. Sebenarnya, perbedaan 15% antara panggilan statis dan nonvirtual mungkin dijelaskan secara lengkap oleh fakta bahwa this
pointer tidak harus diteruskan ke metode statis. Jadi, hanya perlu sedikit kode yang melakukan hal-hal sepele di antara panggilan untuk perbedaan antara berbagai jenis panggilan yang akan diencerkan hingga tidak memiliki dampak bersih sama sekali.
Juga, panggilan metode virtual ada karena suatu alasan; mereka memang memiliki tujuan untuk melayani, dan mereka diimplementasikan menggunakan cara paling efisien yang disediakan oleh perangkat keras yang mendasarinya. (Set instruksi CPU.) Jika, dalam keinginan Anda untuk menghilangkannya dengan menggantinya dengan panggilan nonvirtual atau statis, Anda akhirnya harus menambahkan sebanyak sedikit kode tambahan untuk meniru fungsinya, maka overhead bersih yang Anda hasilkan terikat menjadi tidak kurang, tetapi lebih. Sangat mungkin, sangat banyak, sangat banyak.
VirtualTest | 488846733 -- NonVirtualTest | 480530022 -- StaticTest | 484353198
pada instalasi OpenJDK saya. FTR: Itu bahkan benar jika saya menghapus final
pengubahnya. Btw. Saya harus membuat terminate
lapangan volatile
, jika tidak tes tidak selesai.
VirtualTest | 12451872 -- NonVirtualTest | 12089542 -- StaticTest | 8181170
. Tidak hanya OpenJDK di notebook saya yang berhasil melakukan 40x lebih banyak iterasi, pengujian statis selalu memiliki throughput sekitar 30% lebih sedikit. Ini mungkin fenomena khusus ART, karena saya mendapatkan hasil yang diharapkan pada tablet Android 4.4:VirtualTest | 138183740 -- NonVirtualTest | 142268636 -- StaticTest | 161388933
Nah, panggilan statis tidak dapat diganti (jadi selalu kandidat untuk sebaris), dan tidak memerlukan pemeriksaan nullity apa pun. HotSpot melakukan banyak pengoptimalan keren untuk metode contoh yang mungkin meniadakan keuntungan ini, tetapi itu mungkin alasan mengapa panggilan statis bisa lebih cepat.
Namun, itu seharusnya tidak memengaruhi desain Anda - kode dengan cara yang paling mudah dibaca dan alami - dan hanya khawatir tentang pengoptimalan mikro semacam ini jika Anda memiliki penyebabnya (yang hampir tidak akan pernah Anda lakukan ).
Ini adalah compiler / VM tertentu.
Oleh karena itu, mungkin tidak perlu dirisaukan kecuali Anda telah mengidentifikasi ini sebagai masalah kinerja yang benar-benar kritis dalam aplikasi Anda. Pengoptimalan dini adalah akar dari semua kejahatan, dll.
Namun saya telah melihat pengoptimalan ini memberikan peningkatan kinerja yang substansial dalam situasi berikut:
Jika hal di atas berlaku untuk Anda, mungkin perlu diuji.
Ada juga satu alasan bagus lainnya (dan berpotensi bahkan lebih penting!) Untuk menggunakan metode statis - jika metode tersebut benar-benar memiliki semantik statis (yaitu secara logis tidak terhubung ke instance kelas tertentu) maka masuk akal untuk membuatnya statis untuk mencerminkan fakta ini. Pemrogram Java yang berpengalaman kemudian akan melihat pengubah statis dan langsung berpikir "aha! Metode ini statis sehingga tidak memerlukan instance dan mungkin tidak memanipulasi status instance tertentu". Jadi Anda akan mengkomunikasikan sifat statis dari metode ini secara efektif ....
Seperti yang dikatakan poster sebelumnya: Ini sepertinya pengoptimalan yang prematur.
Namun, ada satu perbedaan (bagian dari fakta bahwa pemanggilan non-statis memerlukan dorongan tambahan dari objek callee ke tumpukan operan):
Karena metode statis tidak dapat diganti, tidak akan ada pencarian virtual dalam waktu proses untuk panggilan metode statis. Ini dapat menghasilkan perbedaan yang dapat diamati dalam beberapa keadaan.
Perbedaan pada tingkat kode byte adalah bahwa pemanggilan metode non-statis dilakukan melalui INVOKEVIRTUAL
, INVOKEINTERFACE
atau INVOKESPECIAL
sementara pemanggilan metode statis dilakukan melalui INVOKESTATIC
.
invokespecial
karena bukan virtual.
Sangat tidak mungkin bahwa perbedaan apa pun dalam kinerja panggilan statis versus panggilan non-statis membuat perbedaan dalam aplikasi Anda. Ingatlah bahwa "pengoptimalan prematur adalah akar dari segala kejahatan".
7 tahun kemudian ...
Saya tidak terlalu yakin dengan hasil yang ditemukan Mike Nakis karena hasil tersebut tidak membahas beberapa masalah umum yang berkaitan dengan pengoptimalan Hotspot. Saya telah menginstrumentasi tolok ukur menggunakan JMH dan menemukan overhead metode instans menjadi sekitar 0,75% di mesin saya vs panggilan statis. Mengingat overhead yang rendah, saya pikir kecuali dalam operasi yang paling sensitif latensi, ini bisa dibilang bukan perhatian terbesar dalam desain aplikasi. Hasil ringkasan dari benchmark JMH saya adalah sebagai berikut;
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
Anda dapat melihat kode di sini di Github;
https://github.com/nfisher/svsi
Tolok ukur itu sendiri cukup sederhana tetapi bertujuan untuk meminimalkan penghapusan kode mati dan pelipatan konstan. Mungkin ada optimisasi lain yang saya lewatkan / abaikan dan hasil ini cenderung bervariasi per rilis JVM dan OS.
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
terutama di lingkungan ART (misalnya penggunaan memori, pengurangan ukuran file .oat, dll). Apakah Anda mengetahui alat / cara yang relatif sederhana yang dapat dicoba untuk mengukur metrik lain ini?
Untuk keputusan jika suatu metode harus statis, aspek kinerja harus tidak relevan. Jika Anda memiliki masalah kinerja, membuat banyak metode statis tidak akan menghemat waktu. Meskipun demikian, metode statis hampir pasti tidak lebih lambat daripada metode instance mana pun, dalam banyak kasus sedikit lebih cepat :
1.) Metode statis tidak polimorfik, sehingga JVM memiliki lebih sedikit keputusan yang harus dibuat untuk menemukan kode yang sebenarnya untuk dieksekusi. Ini adalah titik diperdebatkan di Age of Hotspot, karena Hotspot akan mengoptimalkan panggilan metode instance yang hanya memiliki satu situs implementasi, sehingga mereka akan melakukan hal yang sama.
2.) Perbedaan halus lainnya adalah bahwa metode statis jelas tidak memiliki referensi "ini". Ini menghasilkan bingkai tumpukan satu slot lebih kecil daripada metode instance dengan tanda tangan dan isi yang sama ("ini" diletakkan di slot 0 dari variabel lokal pada tingkat bytecode, sedangkan untuk metode statis slot 0 digunakan untuk yang pertama parameter metode).
Mungkin ada perbedaan, dan mungkin berlaku untuk bagian kode tertentu, dan mungkin berubah bahkan dengan rilis kecil JVM.
Ini pasti bagian dari 97% efisiensi kecil yang harus Anda lupakan .
TableView
jutaan catatan.
Secara teori, lebih murah.
Inisialisasi statis akan dilakukan meskipun Anda membuat instance objek, sedangkan metode statis tidak akan melakukan inisialisasi apa pun yang biasanya dilakukan dalam konstruktor.
Namun, saya belum menguji ini.
Seperti yang dicatat Jon, metode statis tidak dapat diganti, jadi dengan hanya memanggil metode statis mungkin - pada runtime Java yang cukup naif - lebih cepat daripada memanggil metode instance.
Tapi kemudian, bahkan dengan asumsi Anda berada pada titik di mana Anda peduli untuk mengacaukan desain Anda untuk menghemat beberapa nanodetik, itu hanya memunculkan pertanyaan lain: apakah Anda memerlukan metode untuk mengganti diri Anda sendiri? Jika Anda mengubah kode Anda untuk membuat metode contoh menjadi metode statis untuk menyimpan nanodetik di sana-sini, dan kemudian berbalik dan menerapkan dispatcher Anda sendiri di atas itu, Anda hampir pasti akan kurang efisien daripada yang dibangun ke dalam runtime Java Anda.
Saya ingin menambahkan jawaban hebat lainnya di sini yang juga bergantung pada aliran Anda, misalnya:
Public class MyDao {
private String sql = "select * from MY_ITEM";
public List<MyItem> getAllItems() {
springJdbcTemplate.query(sql, new MyRowMapper());
};
};
Perhatikan bahwa Anda membuat objek MyRowMapper baru untuk setiap panggilan.
Sebagai gantinya, saya menyarankan untuk menggunakan bidang statis di sini.
Public class MyDao {
private static RowMapper myRowMapper = new MyRowMapper();
private String sql = "select * from MY_ITEM";
public List<MyItem> getAllItems() {
springJdbcTemplate.query(sql, myRowMapper);
};
};