私のWebアプリケーションのパフォーマンスが気になるのですが、パフォーマンスに関して「if / else」またはswitchステートメントのどちらが優れているのでしょうか。
if
など
私のWebアプリケーションのパフォーマンスが気になるのですが、パフォーマンスに関して「if / else」またはswitchステートメントのどちらが優れているのでしょうか。
if
など
回答:
それは悪であるミクロの最適化と時期尚早の最適化です。むしろ、問題のコードの可読性と保守性について心配します。3つ以上のif/else
ブロックが接着されている場合、またはそのサイズが予測できない場合は、switch
ステートメントを検討することをお勧めします。
または、ポリモーフィズムを取得することもできます。まず、いくつかのインターフェースを作成します。
public interface Action {
void execute(String input);
}
そして、いくつかのすべての実装を把握してくださいMap
。これは静的にも動的にも実行できます。
Map<String, Action> actions = new HashMap<String, Action>();
最後にif/else
またはswitch
を次のようなものに置き換えます(ヌルポインタのようなささいなチェックは残しておきます)。
actions.get(name).execute(input);
それは可能性がある microslowerよりもif/else
やswitch
が、コードは、少なくともはるかに良い保守しています。
Webアプリケーションについて話しているときに、HttpServletRequest#getPathInfo()
アクションキーとして使用できます(最終的には、アクションが見つかるまで、ループ内でpathinfoの最後の部分を分割するためのコードをさらに記述します)。あなたはここで同様の答えを見つけることができます:
Java EE Webアプリケーションのパフォーマンス全般について心配している場合は、この記事も役立つかもしれません。生のJavaコードのみを(マイクロ)最適化するよりもはるかに高いパフォーマンス向上をもたらす他の領域があります。
時期尚早な最適化は避けるべきものであるという意見に完全に同意します。
しかし、Java VMにswitch()に使用できる特別なバイトコードがあることは事実です。
WM仕様を参照(lookupswitchおよびtableswitch)
したがって、コードがパフォーマンスCPUグラフの一部である場合、パフォーマンスが向上する可能性があります。
if / elseまたはスイッチがパフォーマンスの問題の原因となることはほとんどありません。パフォーマンスの問題がある場合は、まずパフォーマンスプロファイリング分析を行って、スロースポットの場所を特定する必要があります。時期尚早の最適化はすべての悪の根源です!
それにもかかわらず、Javaコンパイラの最適化を使用して、switchとif / elseの相対的なパフォーマンスについて話すことができます。Javaでは、switchステートメントは非常に限られたドメイン(整数)で動作することに注意してください。一般に、switchステートメントは次のように表示できます。
switch (<condition>) {
case c_0: ...
case c_1: ...
...
case c_n: ...
default: ...
}
ここc_0
でc_1
、、、 ...、およびc_N
整数は、switchステートメントのターゲットであり<condition>
、整数式に解決する必要があります。
このセットが「密」である場合、つまり(max(c i)+ 1-min(c i))/ n>αであり、0 <k <α<1でありk
、ある経験値aよりも大きい場合非常に効率的なジャンプテーブルを生成できます。
このセットが非常に密ではないが、n> =βの場合、二分探索木はO(2 * log(n))でターゲットを見つけることができますが、それでもやはり効率的です。
他のすべてのケースでは、switchステートメントは同等の一連のif / elseステートメントとまったく同じくらい効率的です。αとβの正確な値は、いくつかの要因に依存し、コンパイラーのコード最適化モジュールによって決定されます。
最後に、もちろん、のドメインが<condition>
整数でない場合、switchステートメントはまったく役に立ちません。
スイッチを使用してください!
if-else-blocksを維持するのは嫌いです!テストを受ける:
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を最適化しなかったと確信していますか?
Javaバイトコードには2種類のSwitchステートメントがあることを読んだことを覚えています。(「Javaパフォーマンスチューニング」にあったと思います。1つは、実行するコードのオフセットを知るためにswitchステートメントの整数値を使用する非常に高速な実装です。これには、すべての整数が連続していて、明確に定義された範囲内である必要があります。 Enumのすべての値を使用すると、そのカテゴリにも当てはまると思います。
私は他の多くのポスターにも同意します...これが非常にホットなコードでない限り、これを心配するのは時期尚早かもしれません。
switch
いくつかの異なる方法を実装しています。一般に、効率は単純な「if
はしご」よりも悪くはありませんが、十分なバリエーションがあり(特にJITCの場合)、それよりもはるかに正確にするのは困難です。
2009年のJava OneのCliff Clickによると、モダンハードウェアのクラッシュコース:
今日、パフォーマンスはメモリアクセスのパターンによって支配されています。キャッシュミスが優勢–メモリは新しいディスクです。[スライド65]
クリフの例(スライド30で終了)は、CPUがレジスタの名前変更、分岐予測、および投機的実行を行っている場合でも、4つのクロックサイクルで7つの操作しか開始できず、2つのキャッシュミスが原因でブロックする必要があることを示しています。 戻るには300クロックサイクル。
したがって、彼はプログラムを高速化するために、この種の小さな問題ではなく、「SOAP→XML→DOM→SQL→…の変換など、不要なデータ形式の変換を行っているかどうかなど、大きな問題については考えないでください。 「すべてのデータをキャッシュに渡します」。
私のテストでは、より良いパフォーマンスは、Windows7のENUM> MAP> SWITCH> IF / ELSE IFです。
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
ほとんどの場合switch
、ほとんどのif-then-else
ブロック、私はいかなる感知または重大なパフォーマンス関連の問題があることを想像することはできません。
しかし、これが問題です。switch
ブロックを使用している場合、その非常に優れた使用方法は、コンパイル時に既知の定数のセットから取得した値をオンにすることを示唆しています。この場合、switch
もし使用できるなら、ステートメントをまったく使用すべきではありませんenum
定数固有のメソッドで。
switch
ステートメントと比較して、列挙型はより優れた型の安全性と維持しやすいコードを提供します。列挙型は、定数が定数のセットに追加された場合、新しい値に対して定数固有のメソッドを提供しないとコードがコンパイルされないように設計できます。一方、新しいを追加し忘れcase
にswitch
あなたがしている幸運十分に例外をスローするようにあなたのブロックを設定しているしている場合、時には唯一の実行時にキャッチすることができますブロック。
switch
とenum
定数固有のメソッドの間のパフォーマンスは大幅に異なるべきではありませんが、後者の方が読みやすく、安全で、保守が容易です。