Webアプリケーションのパフォーマンスを心配して、「if/else」またはswitchステートメントのどちらがパフォーマンスに関して優れているのか疑問に思っています。
それはミクロな最適化と時期尚早な最適化であり、悪です。問題のコードの可読性と保守性について心配する必要はありません。 3つ以上のif/else
ブロックが接着されている場合、またはそのサイズが予測できない場合、switch
ステートメントを考慮することができます。
または、Polymorphismを取得することもできます。最初にインターフェイスを作成します。
public interface Action {
void execute(String input);
}
そして、いくつかのMap
のすべての実装を把握します。これは、静的または動的に実行できます。
Map<String, Action> actions = new HashMap<String, Action>();
最後に、if/else
またはswitch
を次のようなものに置き換えます(nullpointersのような簡単なチェックは別として)。
actions.get(name).execute(input);
mightはif/else
またはswitch
よりも非常に遅いかもしれませんが、少なくともコードのメンテナンス性ははるかに優れています。
Webアプリケーションについては、アクションキーとして HttpServletRequest#getPathInfo()
を使用できます(最終的に、アクションが見つかるまでpathinfoの最後の部分をループに分割するコードをさらに記述します)。ここで同様の答えを見つけることができます:
一般的にJava EE Webアプリケーションのパフォーマンスを心配している場合は、 この記事 も役に立つかもしれません。生のJavaコードを(マイクロ)最適化するだけよりもはるかにパフォーマンスを向上させる領域があります。
時期尚早な最適化は避けるべきものであるという意見に完全に同意します。
しかし、Java VMにはswitch()に使用できる特別なバイトコードがあるのは事実です。
WM Spec ( lookupswitch および tableswitch )を参照
そのため、コードがパフォーマンスCPUグラフの一部である場合、パフォーマンスがいくらか向上する可能性があります。
If/elseまたはスイッチがパフォーマンスの問題の原因になることはほとんどありません。パフォーマンスに問題がある場合は、最初にパフォーマンスプロファイリング分析を実行して、スロースポットの場所を特定する必要があります。早すぎる最適化はすべての悪の根源です!
それでも、Javaコンパイラー最適化を使用して、スイッチと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私)+ 1-min(c私))/ n>α、0 <k <α<1、k
が経験値よりも大きい場合、ジャンプテーブルを生成できます。これは非常に効率的です。
このセットの密度がそれほど高くないが、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));
}
}
2009年のCliff ClickによるとJava One talk 現代のハードウェアのクラッシュコース :
今日、パフォーマンスはメモリアクセスのパターンに支配されています。キャッシュミスが支配的–メモリは新しいディスクです。 [スライド65]
彼の完全なスライドを入手できます こちら 。
Cliffは、CPUがレジスタの名前変更、分岐予測、投機的実行を行っている場合でも、2つのキャッシュミスが原因でブロックする前に4クロックサイクルで7つの操作しか開始できないことを示す例を示します(スライド30で終了)。 300返すクロックサイクル。
だから彼はあなたのプログラムを高速化するために、この種の小さな問題を見るべきではないが、「SOAP→XML→DOM→SQL→… 「「すべてのデータをキャッシュに渡す」」。
Javaバイトコードには2種類のSwitchステートメントがあることを読んだことを覚えています。 (私はそれが「Javaパフォーマンスチューニング」にあったと思います1つは、実行されるコードのオフセットを知るためにswitchステートメントの整数値を使用する非常に高速な実装です。これには、すべての整数が連続し、明確に定義された範囲内にある必要があります。Enumのすべての値を使用すると、そのカテゴリにも該当すると推測しています。
私は他の多くのポスターにも同意します...これが非常にホットなコードでない限り、これを心配するのは時期尚早かもしれません。
私のテストでは、パフォーマンスの向上は、Windows 7では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
*/
ほとんどのswitch
ブロックとほとんどのif-then-else
ブロックについて、パフォーマンスに関連するかなりのまたは重大な懸念があるとは想像できません。
しかし、ここに問題があります。switch
ブロックを使用している場合、その使用は、コンパイル時に既知の定数のセットから取得した値を切り替えることを示唆しています。この場合、定数固有のメソッドでswitch
を使用できる場合は、enum
ステートメントを使用しないでください。
switch
ステートメントと比較すると、enumは型の安全性とコードのメンテナンスが容易です。列挙型は、定数が定数セットに追加された場合、新しい値に定数固有のメソッドを提供せずにコードがコンパイルされないように設計できます。一方、新しいcase
をswitch
ブロックに追加するのを忘れることは、例外をスローするようにブロックを設定した幸運な場合にのみ実行時にキャッチされることがあります。
switch
とenum
定数固有のメソッドのパフォーマンスに大きな違いはありませんが、後者の方が読みやすく、安全で、保守が簡単です。