Khawatir tentang performa aplikasi web saya, saya bertanya-tanya pernyataan "if / else" atau switch mana yang lebih baik terkait performa?
if
dll.
Khawatir tentang performa aplikasi web saya, saya bertanya-tanya pernyataan "if / else" atau switch mana yang lebih baik terkait performa?
if
dll.
Jawaban:
Itu adalah pengoptimalan mikro dan pengoptimalan prematur, yang jahat. Agak khawatir tentang keterbacaan dan pemeliharaan kode yang dipermasalahkan. Jika ada lebih dari dua if/else
balok yang direkatkan atau ukurannya tidak dapat diprediksi, maka Anda mungkin sangat mempertimbangkan switch
pernyataan.
Atau, Anda juga bisa mengambil Polimorfisme . Pertama buat beberapa antarmuka:
public interface Action {
void execute(String input);
}
Dan dapatkan semua implementasi di beberapa Map
. Anda dapat melakukan ini baik secara statis atau dinamis:
Map<String, Action> actions = new HashMap<String, Action>();
Terakhir, ganti if/else
atau switch
dengan sesuatu seperti ini (dengan mengesampingkan pemeriksaan sepele seperti nullpointers):
actions.get(name).execute(input);
Ini mungkin menjadi microslower dari if/else
atau switch
, tetapi kode ini setidaknya jauh lebih baik dipertahankan.
Saat Anda berbicara tentang aplikasi web, Anda dapat menggunakan HttpServletRequest#getPathInfo()
sebagai kunci tindakan (akhirnya menulis beberapa kode lagi untuk memisahkan bagian terakhir dari pathinfo dalam satu lingkaran sampai tindakan ditemukan). Anda dapat menemukan jawaban serupa di sini:
Jika Anda mengkhawatirkan kinerja aplikasi web Java EE secara umum, artikel ini juga dapat bermanfaat bagi Anda. Ada area lain yang memberikan lebih banyak keuntungan kinerja daripada hanya (mikro) yang mengoptimalkan kode Java mentah.
Saya sangat setuju dengan pendapat bahwa pengoptimalan prematur adalah sesuatu yang harus dihindari.
Tetapi memang benar bahwa Java VM memiliki bytecode khusus yang dapat digunakan untuk switch ().
Lihat Spesifikasi WM ( lookupswitch dan tableswitch )
Jadi mungkin ada beberapa peningkatan kinerja, jika kode tersebut merupakan bagian dari grafik CPU kinerja.
Sangat tidak mungkin bahwa jika / lain atau sakelar akan menjadi sumber penurunan kinerja Anda. Jika Anda mengalami masalah kinerja, Anda harus melakukan analisis profil kinerja terlebih dahulu untuk menentukan di mana titik lambatnya. Optimasi prematur adalah akar dari segala kejahatan!
Namun demikian, dimungkinkan untuk membicarakan kinerja relatif switch vs. if / else dengan pengoptimalan kompilator Java. Catatan pertama bahwa di Java, pernyataan switch beroperasi pada domain yang sangat terbatas - integer. Secara umum, Anda dapat melihat pernyataan switch sebagai berikut:
switch (<condition>) {
case c_0: ...
case c_1: ...
...
case c_n: ...
default: ...
}
dimana c_0
,, c_1
..., dan c_N
adalah bilangan integral yang merupakan target pernyataan switch, dan <condition>
harus menyelesaikan ekspresi integer.
Jika set ini "padat" - yaitu, (maks (c i ) + 1 - min (c i )) / n> α, di mana 0 <k <α <1, di mana k
lebih besar dari beberapa nilai empiris, a tabel lompat dapat dibuat, yang sangat efisien.
Jika himpunan ini tidak terlalu padat, tetapi n> = β, pohon pencarian biner dapat menemukan target dalam O (2 * log (n)) yang juga masih efisien.
Untuk semua kasus lainnya, pernyataan switch sama efisiennya dengan rangkaian pernyataan if / else yang setara. Nilai yang tepat dari α dan β bergantung pada sejumlah faktor dan ditentukan oleh modul pengoptimalan kode penyusun.
Akhirnya, tentu saja, jika domain dari <condition>
bukan bilangan bulat, pernyataan switch sama sekali tidak berguna.
Gunakan saklar!
Saya benci mempertahankan blok if-else-! Lakukan tes:
public class SpeedTestSwitch
{
private static void do1(int loop)
{
int temp = 0;
for (; loop > 0; --loop)
{
int r = (int) (Math.random() * 10);
switch (r)
{
case 0:
temp = 9;
break;
case 1:
temp = 8;
break;
case 2:
temp = 7;
break;
case 3:
temp = 6;
break;
case 4:
temp = 5;
break;
case 5:
temp = 4;
break;
case 6:
temp = 3;
break;
case 7:
temp = 2;
break;
case 8:
temp = 1;
break;
case 9:
temp = 0;
break;
}
}
System.out.println("ignore: " + temp);
}
private static void do2(int loop)
{
int temp = 0;
for (; loop > 0; --loop)
{
int r = (int) (Math.random() * 10);
if (r == 0)
temp = 9;
else
if (r == 1)
temp = 8;
else
if (r == 2)
temp = 7;
else
if (r == 3)
temp = 6;
else
if (r == 4)
temp = 5;
else
if (r == 5)
temp = 4;
else
if (r == 6)
temp = 3;
else
if (r == 7)
temp = 2;
else
if (r == 8)
temp = 1;
else
if (r == 9)
temp = 0;
}
System.out.println("ignore: " + temp);
}
public static void main(String[] args)
{
long time;
int loop = 1 * 100 * 1000 * 1000;
System.out.println("warming up...");
do1(loop / 100);
do2(loop / 100);
System.out.println("start");
// run 1
System.out.println("switch:");
time = System.currentTimeMillis();
do1(loop);
System.out.println(" -> time needed: " + (System.currentTimeMillis() - time));
// run 2
System.out.println("if/else:");
time = System.currentTimeMillis();
do2(loop);
System.out.println(" -> time needed: " + (System.currentTimeMillis() - time));
}
}
switch
es itu?
Saya ingat pernah membaca bahwa ada 2 jenis pernyataan Switch di bytecode Java. (Saya pikir itu di 'Java Performance Tuning' One adalah implementasi yang sangat cepat yang menggunakan nilai integer pernyataan switch untuk mengetahui offset kode yang akan dieksekusi. Ini akan membutuhkan semua integer untuk berurutan dan dalam kisaran yang terdefinisi dengan baik Saya menduga bahwa menggunakan semua nilai Enum akan termasuk dalam kategori itu juga.
Saya setuju dengan banyak poster lain ... mungkin terlalu dini untuk mengkhawatirkan hal ini, kecuali ini adalah kode yang sangat panas.
switch
beberapa cara berbeda, beberapa lebih efisien daripada yang lain. Secara umum, efisiensinya tidak lebih buruk daripada " if
tangga" lurus ke depan , tetapi ada cukup banyak variasi (terutama dengan JITC) sehingga sulit untuk lebih tepat dari itu.
Menurut Cliff Click dalam bukunya Java One talk 2009 A Crash Course in Modern Hardware :
Saat ini, performa didominasi oleh pola akses memori. Cache merindukan mendominasi - memori adalah disk baru. [Slide 65]
Anda bisa mendapatkan slide lengkapnya di sini .
Cliff memberi contoh (menyelesaikan Slide 30) yang menunjukkan bahwa meskipun CPU melakukan penggantian nama register, prediksi cabang, dan eksekusi spekulatif, ia hanya dapat memulai 7 operasi dalam 4 siklus jam sebelum harus memblokir karena dua cache yang meleset. 300 siklus jam untuk kembali.
Jadi dia mengatakan untuk mempercepat program Anda, Anda seharusnya tidak melihat masalah kecil seperti ini, tetapi pada masalah yang lebih besar seperti apakah Anda membuat konversi format data yang tidak perlu, seperti mengonversi "SOAP → XML → DOM → SQL →… "yang" melewatkan semua data melalui cache ".
Dalam pengujian saya, kinerja yang lebih baik adalah ENUM> MAP> SWITCH> IF / ELSE IF di Windows7.
import java.util.HashMap;
import java.util.Map;
public class StringsInSwitch {
public static void main(String[] args) {
String doSomething = null;
//METHOD_1 : SWITCH
long start = System.currentTimeMillis();
for (int i = 0; i < 99999999; i++) {
String input = "Hello World" + (i & 0xF);
switch (input) {
case "Hello World0":
doSomething = "Hello World0";
break;
case "Hello World1":
doSomething = "Hello World0";
break;
case "Hello World2":
doSomething = "Hello World0";
break;
case "Hello World3":
doSomething = "Hello World0";
break;
case "Hello World4":
doSomething = "Hello World0";
break;
case "Hello World5":
doSomething = "Hello World0";
break;
case "Hello World6":
doSomething = "Hello World0";
break;
case "Hello World7":
doSomething = "Hello World0";
break;
case "Hello World8":
doSomething = "Hello World0";
break;
case "Hello World9":
doSomething = "Hello World0";
break;
case "Hello World10":
doSomething = "Hello World0";
break;
case "Hello World11":
doSomething = "Hello World0";
break;
case "Hello World12":
doSomething = "Hello World0";
break;
case "Hello World13":
doSomething = "Hello World0";
break;
case "Hello World14":
doSomething = "Hello World0";
break;
case "Hello World15":
doSomething = "Hello World0";
break;
}
}
System.out.println("Time taken for String in Switch :"+ (System.currentTimeMillis() - start));
//METHOD_2 : IF/ELSE IF
start = System.currentTimeMillis();
for (int i = 0; i < 99999999; i++) {
String input = "Hello World" + (i & 0xF);
if(input.equals("Hello World0")){
doSomething = "Hello World0";
} else if(input.equals("Hello World1")){
doSomething = "Hello World0";
} else if(input.equals("Hello World2")){
doSomething = "Hello World0";
} else if(input.equals("Hello World3")){
doSomething = "Hello World0";
} else if(input.equals("Hello World4")){
doSomething = "Hello World0";
} else if(input.equals("Hello World5")){
doSomething = "Hello World0";
} else if(input.equals("Hello World6")){
doSomething = "Hello World0";
} else if(input.equals("Hello World7")){
doSomething = "Hello World0";
} else if(input.equals("Hello World8")){
doSomething = "Hello World0";
} else if(input.equals("Hello World9")){
doSomething = "Hello World0";
} else if(input.equals("Hello World10")){
doSomething = "Hello World0";
} else if(input.equals("Hello World11")){
doSomething = "Hello World0";
} else if(input.equals("Hello World12")){
doSomething = "Hello World0";
} else if(input.equals("Hello World13")){
doSomething = "Hello World0";
} else if(input.equals("Hello World14")){
doSomething = "Hello World0";
} else if(input.equals("Hello World15")){
doSomething = "Hello World0";
}
}
System.out.println("Time taken for String in if/else if :"+ (System.currentTimeMillis() - start));
//METHOD_3 : MAP
//Create and build Map
Map<String, ExecutableClass> map = new HashMap<String, ExecutableClass>();
for (int i = 0; i <= 15; i++) {
String input = "Hello World" + (i & 0xF);
map.put(input, new ExecutableClass(){
public void execute(String doSomething){
doSomething = "Hello World0";
}
});
}
//Start test map
start = System.currentTimeMillis();
for (int i = 0; i < 99999999; i++) {
String input = "Hello World" + (i & 0xF);
map.get(input).execute(doSomething);
}
System.out.println("Time taken for String in Map :"+ (System.currentTimeMillis() - start));
//METHOD_4 : ENUM (This doesn't use muliple string with space.)
start = System.currentTimeMillis();
for (int i = 0; i < 99999999; i++) {
String input = "HW" + (i & 0xF);
HelloWorld.valueOf(input).execute(doSomething);
}
System.out.println("Time taken for String in ENUM :"+ (System.currentTimeMillis() - start));
}
}
interface ExecutableClass
{
public void execute(String doSomething);
}
// Enum version
enum HelloWorld {
HW0("Hello World0"), HW1("Hello World1"), HW2("Hello World2"), HW3(
"Hello World3"), HW4("Hello World4"), HW5("Hello World5"), HW6(
"Hello World6"), HW7("Hello World7"), HW8("Hello World8"), HW9(
"Hello World9"), HW10("Hello World10"), HW11("Hello World11"), HW12(
"Hello World12"), HW13("Hello World13"), HW14("Hello World4"), HW15(
"Hello World15");
private String name = null;
private HelloWorld(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void execute(String doSomething){
doSomething = "Hello World0";
}
public static HelloWorld fromString(String input) {
for (HelloWorld hw : HelloWorld.values()) {
if (input.equals(hw.getName())) {
return hw;
}
}
return null;
}
}
//Enum version for betterment on coding format compare to interface ExecutableClass
enum HelloWorld1 {
HW0("Hello World0") {
public void execute(String doSomething){
doSomething = "Hello World0";
}
},
HW1("Hello World1"){
public void execute(String doSomething){
doSomething = "Hello World0";
}
};
private String name = null;
private HelloWorld1(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void execute(String doSomething){
// super call, nothing here
}
}
/*
* http://stackoverflow.com/questions/338206/why-cant-i-switch-on-a-string
* https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-3.html#jvms-3.10
* http://forums.xkcd.com/viewtopic.php?f=11&t=33524
*/
Time taken for String in Switch :3235 Time taken for String in if/else if :3143 Time taken for String in Map :4194 Time taken for String in ENUM :2866
Untuk sebagian besar switch
dan sebagian besar if-then-else
blok, saya tidak dapat membayangkan bahwa ada masalah terkait kinerja yang cukup berarti atau signifikan.
Tapi inilah masalahnya: jika Anda menggunakan switch
blok, penggunaannya menunjukkan bahwa Anda mengaktifkan nilai yang diambil dari sekumpulan konstanta yang diketahui pada waktu kompilasi. Dalam kasus ini, Anda seharusnya tidak menggunakan switch
pernyataan sama sekali jika Anda dapat menggunakanenum
dengan konstanta spesifik.
Dibandingkan dengan switch
pernyataan, enum memberikan keamanan tipe dan kode yang lebih baik yang lebih mudah dipelihara. Enum dapat dirancang sehingga jika sebuah konstanta ditambahkan ke himpunan konstanta, kode Anda tidak akan dikompilasi tanpa menyediakan metode spesifik-konstanta untuk nilai baru. Di sisi lain, lupa menambahkan yang baru case
ke sebuah switch
blok terkadang hanya dapat ditangkap pada waktu proses jika Anda cukup beruntung untuk mengatur blok Anda untuk membuat pengecualian.
Performa antara switch
dan enum
metode spesifik-konstan seharusnya tidak jauh berbeda, tetapi metode terakhir lebih mudah dibaca, lebih aman, dan lebih mudah dikelola.