Беспокоясь о производительности моего веб-приложения, мне интересно, какой из операторов if / else или switch лучше с точки зрения производительности?
if
и т. Д.
Беспокоясь о производительности моего веб-приложения, мне интересно, какой из операторов if / else или switch лучше с точки зрения производительности?
if
и т. Д.
Ответы:
Это микрооптимизация и преждевременная оптимизация - зло. Скорее беспокойтесь о читабельности и ремонтопригодности рассматриваемого кода. Если if/else
склеено более двух блоков или их размер непредсказуем, то вам стоит подумать о switch
заявлении.
В качестве альтернативы вы также можете использовать Polymorphism . Сначала создайте интерфейс:
public interface Action {
void execute(String input);
}
И получить все реализации в некоторых Map
. Вы можете сделать это статически или динамически:
Map<String, Action> actions = new HashMap<String, Action>();
Наконец, замените if/else
или switch
на что-то вроде этого (оставив в стороне тривиальные проверки, такие как нулевые указатели):
actions.get(name).execute(input);
Это может быть немного меньше if/else
или switch
, но код, по крайней мере, намного лучше обслуживается.
Когда вы говорите о веб-приложениях, вы можете использовать ее в HttpServletRequest#getPathInfo()
качестве клавиши действия (в конечном итоге напишите еще код, чтобы отделить последнюю часть pathinfo в цикле, пока действие не будет найдено). Вы можете найти здесь похожие ответы:
Если вы беспокоитесь о производительности веб-приложений Java EE в целом, эта статья также может оказаться полезной для вас. Есть и другие области, которые дают гораздо больший прирост производительности, чем простая (микро) оптимизация исходного кода Java.
Я полностью согласен с мнением, что преждевременной оптимизации следует избегать.
Но верно, что виртуальная машина Java имеет специальные байт-коды, которые можно использовать для switch ().
См. Спецификацию WM ( переключатель поиска и переключатель таблиц )
Так что может быть некоторый прирост производительности, если код является частью графика производительности ЦП.
Крайне маловероятно, что if / else или переключатель станут источником проблем с производительностью. Если у вас проблемы с производительностью, вам следует сначала провести анализ профилирования производительности, чтобы определить, где находятся медленные места. Преждевременная оптимизация - это корень всех зол!
Тем не менее, можно говорить об относительной производительности switch по сравнению с if / else с оптимизацией компилятора Java. Прежде всего отметим, что в Java операторы switch работают с очень ограниченным доменом - целыми числами. В общем, вы можете просмотреть оператор switch следующим образом:
switch (<condition>) {
case c_0: ...
case c_1: ...
...
case c_n: ...
default: ...
}
где c_0
, c_1
, ..., и c_N
являются целыми числами , которые являются мишенями заявления переключателя, и <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! Пройдите тест:
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». Одна из них - очень быстрая реализация, которая использует целочисленные значения оператора switch, чтобы знать смещение кода, который будет выполняться. Для этого потребуется, чтобы все целые числа были последовательными и в четко определенном диапазоне Я предполагаю, что использование всех значений Enum тоже попадет в эту категорию.
Я согласен со многими другими плакатами ... может быть преждевременно беспокоиться об этом, если только это не очень-очень горячий код.
switch
несколько различных способов, некоторые из которых более эффективны, чем другие. В целом эффективность будет не хуже, чем у простой « if
лестницы», но есть достаточно вариаций (особенно с JITC), поэтому трудно быть намного более точным.
Согласно Клиффу Клик в его выступлении на Java One 2009 Ускоренный курс современного оборудования :
Сегодня в производительности преобладают шаблоны доступа к памяти. Преобладают пропуски кеша - новый диск - это память. [Слайд 65]
Вы можете получить его полные слайды здесь .
Клифф приводит пример (завершение слайда 30), показывающий, что даже когда ЦП выполняет переименование регистров, предсказание ветвлений и спекулятивное выполнение, он может запустить только 7 операций за 4 тактовых цикла, прежде чем потребуется блокировка из-за двух промахов в кэше, которые требуют 300 тактов для возврата.
Поэтому он говорит, что для ускорения вашей программы вы должны обращать внимание не на такие второстепенные проблемы, а на более серьезные, например, выполняете ли вы ненужные преобразования формата данных, такие как преобразование «SOAP → XML → DOM → SQL →… "который" передает все данные через кеш ".
В моем тесте лучшая производительность - ENUM> MAP> SWITCH> IF / ELSE IF в 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
Я не могу себе представить, что для большинства switch
и большинства if-then-else
блоков есть какие-либо заметные или существенные проблемы, связанные с производительностью.
Но вот в чем дело: если вы используете switch
блок, само его использование предполагает, что вы включаете значение, взятое из набора констант, известных во время компиляции. В этом случае вам действительно не следует использовать switch
операторы вообще, если вы можете использовать их enum
с методами, зависящими от константы.
По сравнению с switch
оператором перечисление обеспечивает лучшую безопасность типов и код, который легче поддерживать. Перечисления могут быть спроектированы таким образом, что если к набору констант добавляется константа, ваш код не будет компилироваться без предоставления специфичного для константы метода для нового значения. С другой стороны, если вы забыли добавить новый блок case
в switch
блок, это может быть обнаружено только во время выполнения, если вам посчастливилось настроить свой блок на создание исключения.
Производительность между switch
и enum
специфичным для константы методом не должна существенно отличаться, но последний более читабелен, безопаснее и проще в обслуживании.