Lo lắng về hiệu suất của ứng dụng web của tôi, tôi đang tự hỏi câu lệnh "if / else" hoặc câu lệnh switch nào tốt hơn về hiệu suất?
if
v.v.
Lo lắng về hiệu suất của ứng dụng web của tôi, tôi đang tự hỏi câu lệnh "if / else" hoặc câu lệnh switch nào tốt hơn về hiệu suất?
if
v.v.
Câu trả lời:
Đó là tối ưu hóa vi mô và tối ưu hóa quá sớm, đó là những điều xấu xa. Thay vì lo lắng về tính ổn định và khả năng bảo trì của mã được đề cập. Nếu có nhiều hơn hai if/else
khối được dán lại với nhau hoặc kích thước của nó là không thể đoán trước, thì bạn có thể cân nhắc một switch
tuyên bố.
Ngoài ra, bạn cũng có thể lấy Đa hình . Đầu tiên, hãy tạo một số giao diện:
public interface Action {
void execute(String input);
}
Và nắm được tất cả các triển khai trong một số Map
. Bạn có thể làm điều này tĩnh hoặc động:
Map<String, Action> actions = new HashMap<String, Action>();
Cuối cùng thay thế if/else
hoặc switch
bằng một cái gì đó như thế này (bỏ qua các kiểm tra tầm thường như nullpointers):
actions.get(name).execute(input);
Nó có thể nhỏ hơn if/else
hoặc nhỏ hơn switch
, nhưng mã ít nhất có thể bảo trì tốt hơn nhiều.
Khi bạn đang nói về các ứng dụng web, bạn có thể sử dụng HttpServletRequest#getPathInfo()
làm khóa hành động (cuối cùng viết thêm một số mã để tách phần cuối cùng của pathinfo đi trong một vòng lặp cho đến khi một hành động được tìm thấy). Bạn có thể tìm thấy câu trả lời tương tự ở đây:
Nếu bạn đang lo lắng về hiệu suất ứng dụng web Java EE nói chung, thì bạn cũng có thể thấy bài viết này hữu ích. Có những khu vực khác mà đưa ra một nhiều hơn nữa đạt được hiệu suất hơn chỉ (vi) tối ưu hóa mã Java thô.
Tôi hoàn toàn đồng ý với ý kiến rằng việc tối ưu hóa quá sớm là điều nên tránh.
Nhưng đúng là máy ảo Java có các mã bytecodes đặc biệt có thể được sử dụng cho switch ().
Xem WM Spec ( lookupswitch và tableswitch )
Vì vậy, có thể có một số mức tăng hiệu suất, nếu mã là một phần của biểu đồ CPU hiệu suất.
Rất khó xảy ra trường hợp if / else hoặc một công tắc sẽ là nguồn gốc gây ra tai họa về hiệu suất của bạn. Nếu bạn đang gặp sự cố về hiệu suất, trước tiên bạn nên thực hiện phân tích hồ sơ hiệu suất để xác định đâu là điểm chậm. Tối ưu hóa sớm là gốc rễ của mọi điều ác!
Tuy nhiên, có thể nói về hiệu suất tương đối của switch so với if / else với các tối ưu hóa trình biên dịch Java. Đầu tiên lưu ý rằng trong Java, các câu lệnh switch hoạt động trên một miền rất hạn chế - số nguyên. Nói chung, bạn có thể xem một câu lệnh chuyển đổi như sau:
switch (<condition>) {
case c_0: ...
case c_1: ...
...
case c_n: ...
default: ...
}
where c_0
, c_1
..., và c_N
là các số tích phân là mục tiêu của câu lệnh switch và <condition>
phải chuyển thành một biểu thức số nguyên.
Nếu tập hợp này là "dày đặc" - nghĩa là (max (c i ) + 1 - min (c i )) / n> α, trong đó 0 <k <α <1, trong đó k
lớn hơn một số giá trị thực nghiệm, a bảng nhảy có thể được tạo, có hiệu quả cao.
Nếu tập hợp này không quá dày đặc, nhưng n> = β, cây tìm kiếm nhị phân có thể tìm thấy mục tiêu trong O (2 * log (n)) mà vẫn hiệu quả.
Đối với tất cả các trường hợp khác, một câu lệnh switch chính xác hiệu quả như một chuỗi các câu lệnh if / else tương đương. Các giá trị chính xác của α và β phụ thuộc vào một số yếu tố và được xác định bởi mô-đun tối ưu hóa mã của trình biên dịch.
Cuối cùng, tất nhiên, nếu miền của <condition>
không phải là các số nguyên, một câu lệnh switch hoàn toàn vô dụng.
Sử dụng công tắc!
Tôi ghét duy trì if-else-blocks! Có bài kiểm tra:
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 đi?
Tôi nhớ đã đọc rằng có 2 loại câu lệnh Switch trong Java bytecode. (Tôi nghĩ đó là trong 'Điều chỉnh hiệu suất Java'. Một là một triển khai rất nhanh sử dụng các giá trị số nguyên của câu lệnh switch để biết độ lệch của mã sẽ được thực thi. Điều này sẽ yêu cầu tất cả các số nguyên phải liên tiếp và trong một phạm vi được xác định rõ Tôi đoán rằng việc sử dụng tất cả các giá trị của Enum cũng sẽ thuộc loại đó.
Tôi đồng ý với nhiều áp phích khác mặc dù ... có thể còn sớm để lo lắng về điều này, trừ khi đây là mã rất nóng.
switch
một số cách khác nhau, một số cách hiệu quả hơn những cách khác. Nói chung, hiệu quả sẽ không kém hơn một " if
bậc thang" thẳng tiến , nhưng có đủ các biến thể (đặc biệt là với JITC) mà khó có thể chính xác hơn nhiều.
Theo Cliff Click trong bài nói chuyện Java One 2009 A Crash Course in Modern Hardware :
Ngày nay, hiệu suất bị chi phối bởi các kiểu truy cập bộ nhớ. Bộ nhớ cache không chiếm ưu thế - bộ nhớ là đĩa mới. [Trang trình bày 65]
Bạn có thể lấy các slide đầy đủ của anh ấy ở đây .
Cliff đưa ra một ví dụ (kết thúc trên Slide 30) cho thấy rằng ngay cả khi CPU thực hiện đổi tên thanh ghi, dự đoán nhánh và thực thi suy đoán, nó chỉ có thể bắt đầu 7 hoạt động trong 4 chu kỳ đồng hồ trước khi phải chặn do hai lần bỏ lỡ bộ nhớ cache. 300 chu kỳ đồng hồ để quay trở lại.
Vì vậy, anh ấy nói để tăng tốc chương trình của bạn, bạn không nên xem xét loại vấn đề nhỏ này, nhưng trên những vấn đề lớn hơn, chẳng hạn như liệu bạn có đang thực hiện các chuyển đổi định dạng dữ liệu không cần thiết hay không, chẳng hạn như chuyển đổi "SOAP → XML → DOM → SQL →… "mà" chuyển tất cả dữ liệu qua bộ nhớ đệm ".
Trong thử nghiệm của tôi, hiệu suất tốt hơn là ENUM> MAP> SWITCH> IF / ELSE IF trong 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
Đối với hầu hết switch
và hầu hết if-then-else
các khối, tôi không thể tưởng tượng rằng có bất kỳ mối quan tâm đáng kể hoặc đáng kể nào liên quan đến hiệu suất.
Nhưng đây là vấn đề: nếu bạn đang sử dụng một switch
khối, cách sử dụng của nó gợi ý rằng bạn đang chuyển sang một giá trị được lấy từ một tập hợp các hằng số đã biết tại thời điểm biên dịch. Trong trường hợp này, bạn thực sự không nên sử dụng các switch
câu lệnh nếu bạn có thể sử dụng một enum
với các phương thức cụ thể.
So với một switch
câu lệnh, một enum cung cấp an toàn kiểu tốt hơn và mã dễ bảo trì hơn. Enums có thể được thiết kế để nếu một hằng số được thêm vào tập hợp hằng số, mã của bạn sẽ không biên dịch mà không cung cấp một phương thức cụ thể cho giá trị mới. Mặt khác, việc quên thêm một khối mới case
vào một switch
khối đôi khi chỉ có thể bị bắt gặp trong thời gian chạy nếu bạn đủ may mắn đã thiết lập khối của mình để ném một ngoại lệ.
Hiệu suất giữa switch
và một enum
phương pháp cụ thể không đổi không được chênh lệch đáng kể, nhưng phương pháp sau dễ đọc hơn, an toàn hơn và dễ bảo trì hơn.