Эффективность кода Java с примитивными типами - PullRequest
2 голосов
/ 07 мая 2010

Я хочу спросить, какой фрагмент кода более эффективен в Java? Код 1:

void f()
{
 for(int i = 0 ; i < 99999;i++)
 {
  for(int j = 0 ; j < 99999;j++)
  {
   //Some operations
  }
 }

}

Код 2:

void f()
{
 int i,j;
 for(i = 0 ; i < 99999;i++)
 {
  for(j = 0 ; j < 99999;j++)
  {
   //Some operations
  }
 }

}

Мой учитель сказал, что второе лучше, но я не могу согласиться с этим мнением.

Ответы [ 9 ]

31 голосов
/ 07 мая 2010

IT.НЕ.ДЕЛАТЬ.A. РАЗНИЦА.

Остановить микрооптимизацию.Эти маленькие хитрости не заставляют программы работать намного быстрее.

Сконцентрируйтесь на оптимизации больших картинок и написании читаемого кода.

Объявляйте переменные там, где они имеют смысл, и где это помогает понять семантикуобщий код в более широком контексте, а не потому, что вы думаете это быстрее, чем одно место над другим.

17 голосов
/ 07 мая 2010

Я бы предпочел первое по сравнению со вторым, потому что оно удерживает переменные цикла в стороне от остальной части кода метода. Поскольку они не видны за пределами цикла, вы не сможете случайно обратиться к ним позже.

Другие ответы тоже верны: не беспокоитесь о таких вещах для производительности. Но делайте подумайте об этом из соображений читабельности кода и для сообщения намерений программиста следующему человеку, который придет. Это гораздо важнее, чем проблемы микрооптимизации.

Теперь, это на уровне языка Java (как в спецификации языка Java). На уровне виртуальной машины Java абсолютно не имеет значения, какой из этих двух вы используете. Местные жители распределяются точно так же.

Если вы не уверены, вы всегда можете скомпилировать его и посмотреть, что произойдет. Давайте сделаем два класса, f1 и f2, для двух версий:

$ cat f1.java
public class f1 {
  void f() {
    for(int i = 0 ; i < 99999;i++) {
      for(int j = 0 ; j < 99999;j++) {
      }
    }
  }
}

$ cat f2.java
public class f2 {
  void f() {
    int i, j;
    for(i = 0 ; i < 99999;i++) {
      for(j = 0 ; j < 99999;j++) {
      }
    }
  }
}

Скомпилируйте их:

$ javac f1.java
$ javac f2.java

И декомпилировать их:

$ javap -c f1 > f1decomp
$ javap -c f2 > f2decomp

И сравните их:

$ diff f1decomp f2decomp
1,3c1,3
< Compiled from "f1.java"
< public class f1 extends java.lang.Object{
< public f1();
---
> Compiled from "f2.java"
> public class f2 extends java.lang.Object{
> public f2();

Нет абсолютно никакой разницы в байт-коде.

13 голосов
/ 07 мая 2010

Остерегайтесь опасностей микро-бенчмаркинга !!!

Я взял код, обернул метод вокруг и выполнил это 10 раз в цикле.Результаты:

50, 3, 
3, 0, 
0, 0, 
0, 0, 
....

Без некоторого реального кода в циклах компиляторы могут выяснить, что циклы не выполняют никакой полезной работы, и полностью их оптимизировать.Учитывая измеренную производительность, я подозреваю, что эта оптимизация могла быть сделана javac.

Урок 1. Компиляторы часто оптимизируют код, который выполняет бесполезную «работу».Чем умнее компилятор, тем больше вероятность того, что такого рода вещи произойдут.Если вы не учитываете это при кодировании, эталонный тест может быть бессмысленным.

Поэтому я добавил следующее простое вычисление в оба цикла if (i < 2 * j) longK++; и заставил тестовый метод вернуть окончательное значениеlongK.Результаты:

32267, 33382,
34542, 30136,
12893, 12900,
12897, 12889,
12904, 12891,
12880, 12891,
....

Мы явно остановили компиляторы, оптимизирующие цикл.Но теперь мы видим эффекты разогрева JVM в (в данном случае) первых двух парах итераций цикла.Первые две пары итераций (один вызов метода), вероятно, выполняются исключительно в интерпретируемом режиме.И, похоже, третья итерация может работать параллельно с JIT.По третьей паре итераций мы, скорее всего, запускаем чистый нативный код.И с тех пор разница между временными интервалами двух версий цикла - просто шум.

Урок 2: всегда учитывайте эффект разогрева JVM.Это может серьезно исказить результаты тестов, как микро, так и макро.

Вывод - после разогрева JVM между двумя версиями цикла нет ощутимой разницы.

6 голосов
/ 07 мая 2010

Второй хуже.

Почему?Потому что переменная цикла находится вне цикла.i и j будут иметь значение после завершения цикла.Как правило, это не то, что вы хотите.Первый определяет переменные цикла, поэтому он виден только внутри цикла.

2 голосов
/ 07 мая 2010

Нет, это не имеет никакого значения (скорость). Они оба скомпилированы в один и тот же код. И, как сказал Мастер Гаурав, распределения и освобождения не происходит.

Когда метод запускается, JVM выделяет достаточно слотов памяти для всех локальных переменных, и больше никаких распределений не происходит до конца метода.

Единственное небольшое крошечное несущественное отличие (кроме области действия) заключается в том, что в первом примере память, выделенная для i & j, может быть повторно использована для других переменных. Поэтому JVM выделит меньше слотов памяти для этого метода (ну, вы сохранили несколько битов)

2 голосов
/ 07 мая 2010

Я думаю, что нет никакой разницы в эффективности для любой полуприличной реализации JVM.

0 голосов
/ 07 мая 2010

Есть еще один аспект, который очень важен для двух разных версий:

В то время как в варианте 2 создаются только два временных объекта (i и j), в варианте 1 будет создано 100000 объектов (1 x i и 999999 x j). Возможно, ваш компилятор собирается оптимизировать это, но в этом вы не можете быть уверены. Если это не оптимизировать, сборщик мусора будет вести себя значительно хуже.

0 голосов
/ 07 мая 2010

Во-первых, да, ваш учитель не прав, второй код не лучше. Что значит лучше в любом случае? Это связано с тем, что в любом нормальном цикле операции внутри тела цикла являются частью, занимающей много времени. Поэтому Code 2 - это просто микрооптимизация, которая не добавляет достаточной скорости (если таковая имеется), чтобы оправдать плохую читаемость кода.

0 голосов
/ 07 мая 2010

Секунда лучше для скорости.

Причина в том, что в первом случае область действия j ограничена внутренним циклом for.

Таким образом, в тот момент, когда внутренний цикл завершен, память для j освобождается и снова выделяется для следующей итерации внешнего цикла.

Поскольку выделение и освобождение памяти занимает некоторое время, даже если оно находится в стеке, производительность первого из них ниже.

...