Мы обычно говорим, что Java-программисты должны писать код, который выглядит хорошо, и все другие проблемы решаются компилятором. Например, наличие сложного логического выражения лучше перенести в отдельный метод с хорошим именем и с одним оператором return, содержащим выражение. Оригинал, если или пока, будет намного легче понять. Компилятор Java достаточно умен, чтобы видеть, что код вызывается только из одного места и будет перемещать код внутри строки.
Это действительно так? Я слышал, что JIT-компилятор выполняет оптимизацию, а Javac-компилятор — нет. Давайте посмотрим на простой класс:
public class OptimizeThis {
private int a(int x, int y) {
return x + y;
}
public int add(int x, int y, int z) {
return a(a(x, y), z);
}
}
Здесь много места для оптимизации. Метод a () может быть исключен из всего забавного. Код может быть включен в метод add (), и код будет намного быстрее.
Что-то вроде этого:
public class Optimized {
public int add(int x, int y, int z) {
return x + y + z;
}
}
Давайте скомпилируем класс OptimizeThis и разберем с помощью javap:
verhasp:java verhasp$ javac OptimizeThis.java
$ javap -v -p OptimizeThis.class
Classfile /Users/verhasp/.../src/main/java/OptimizeThis.class
Last modified 2012.07.08.; size 327 bytes
MD5 checksum 9ba33fe0979ff0948a683fab2dc32d12
Compiled from "OptimizeThis.java"
public class OptimizeThis
SourceFile: "OptimizeThis.java"
minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #4.#15 // java/lang/Object."<init>":()V
#2 = Methodref #3.#16 // OptimizeThis.a:(II)I
#3 = Class #17 // OptimizeThis
#4 = Class #18 // java/lang/Object
#5 = Utf8 <init>
#6 = Utf8 ()V
#7 = Utf8 Code
#8 = Utf8 LineNumberTable
#9 = Utf8 a
#10 = Utf8 (II)I
#11 = Utf8 add
#12 = Utf8 (III)I
#13 = Utf8 SourceFile
#14 = Utf8 OptimizeThis.java
#15 = NameAndType #5:#6 // "<init>":()V
#16 = NameAndType #9:#10 // a:(II)I
#17 = Utf8 OptimizeThis
#18 = Utf8 java/lang/Object
{
public OptimizeThis();
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
private int a(int, int);
flags: ACC_PRIVATE
Code:
stack=2, locals=3, args_size=3
0: iload_1
1: iload_2
2: iadd
3: ireturn
LineNumberTable:
line 3: 0
public int add(int, int, int);
flags: ACC_PUBLIC
Code:
stack=4, locals=4, args_size=4
0: aload_0
1: aload_0
2: iload_1
3: iload_2
4: invokespecial #2 // Method a:(II)I
7: iload_3
8: invokespecial #2 // Method a:(II)I
11: ireturn
LineNumberTable:
line 7: 0
}
verhasp:java verhasp$
Вы можете видеть, что у нас есть оба метода. Код
private int a(int, int);
flags: ACC_PRIVATE
Code:
stack=2, locals=3, args_size=3
0: iload_1
1: iload_2
2: iadd
3: ireturn
это приватный метод a () и код
public int add(int, int, int);
flags: ACC_PUBLIC
Code:
stack=4, locals=4, args_size=4
0: aload_0
1: aload_0
2: iload_1
3: iload_2
4: invokespecial #2 // Method a:(II)I
7: iload_3
8: invokespecial #2 // Method a:(II)I
11: ireturn
это открытый метод add (). Сам код прост. Метод a () загружает в стек операндов первую локальную переменную (iload_1), затем делает то же самое со второй (iload_2), а затем добавляет два (iadd). То, что осталось в стеке операндов, используется для возврата (ireturn).
- Локальная переменная номер ноль это в случае нестатических методов.
- Аргументы также рассматриваются как локальные переменные.
- Для первых нескольких локальных переменных существуют сокращенные байтовые коды Java, потому что сгенерированный код обращается к ним чаще всего, и это экономит пространство и скорость.
- Мы используем только int, и поэтому нам не нужно заботиться о более сложных проблемах, таких как двойное занятие двух слотов.
Метод add () почти такой же простой. Это загружает значение этого в стек операнда два раза. Нужно вызвать нестатический метод a () . После этого он загружает первую и вторую локальные переменные в стек (первые два аргумента метода) и в команде номер 4 (строка 61) вызывает метод a () . После этого он загружает третью локальную переменную в стек. На этот раз в стеке содержится переменная this , результат предыдущего вызова метода a () и третья локальная переменная, которая является третьим аргументом метода add () . Затем он вызывает метод a () .
Давайте посмотрим на код, сгенерированный из класса Optimized :
$ javap -v -p Optimized.class
Classfile /Users/verhasp/.../src/main/java/Optimized.class
Last modified 2012.07.08.; size 251 bytes
MD5 checksum 2765acd1d55048184e9632c1a14a8e21
Compiled from "Optimized.java"
public class Optimized
SourceFile: "Optimized.java"
minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #3.#12 // java/lang/Object."<init>":()V
#2 = Class #13 // Optimized
#3 = Class #14 // java/lang/Object
#4 = Utf8 <init>
#5 = Utf8 ()V
#6 = Utf8 Code
#7 = Utf8 LineNumberTable
#8 = Utf8 add
#9 = Utf8 (III)I
#10 = Utf8 SourceFile
#11 = Utf8 Optimized.java
#12 = NameAndType #4:#5 // "<init>":()V
#13 = Utf8 Optimized
#14 = Utf8 java/lang/Object
{
public Optimized();
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
public int add(int, int, int);
flags: ACC_PUBLIC
Code:
stack=2, locals=4, args_size=4
0: iload_1
1: iload_2
2: iadd
3: iload_3
4: iadd
5: ireturn
LineNumberTable:
line 3: 0
}
Гораздо проще Это тоже быстрее? Доказательство пудинга в еде. Если это не вкусно, то собака съест это. Однако …
Здесь мы снова имеем два класса, расширенных некоторыми основными методами (по одному для каждого):
public class OptimizeThis {
private int a(int x, int y) {
return x + y;
}
public int add(int x, int y, int z) {
return a(a(x, y), z);
}
public static void main(String[] args) {
OptimizeThis adder = new OptimizeThis();
final int outer = 100_0000_000;
final int loop = 100_0000_000;
Long tStart = System.currentTimeMillis();
for (int j = 0; j < outer; j++) {
for (int i = 0; i < loop; i++) {
int x = 1;
int y = 2;
int z = 3;
adder.add(x, y, z);
}
}
Long tEnd = System.currentTimeMillis();
System.out.println(tEnd - tStart);
}
}
и
public class Optimized {
public int add(int x, int y, int z) {
return x + y + z;
}
public static void main(String[] args) {
Optimized adder = new Optimized();
final int outer = 100_0000_000;
final int loop = 100_0000_000;
Long tStart = System.currentTimeMillis();
for (int j = 0; j < outer; j++) {
for (int i = 0; i < loop; i++) {
int x = 1;
int y = 2;
int z = 3;
adder.add(x, y, z);
}
}
Long tEnd = System.currentTimeMillis();
System.out.println(tEnd - tStart);
}
}
В дополнение к этому мы также создали пустой класс с именем Empty, который возвращает постоянный ноль.
public class Empty {
public int add(int x, int y, int z) {
return 0;
}
public static void main(String[] args) {
Empty adder = new Empty();
final int outer = 100_0000_000;
final int loop = 100_0000_000;
Long tStart = System.currentTimeMillis();
for (int j = 0; j < outer; j++) {
for (int i = 0; i < loop; i++) {
int x = 1;
int y = 2;
int z = 3;
adder.add(x, y, z);
}
}
Long tEnd = System.currentTimeMillis();
System.out.println(tEnd - tStart);
}
}
Здесь у нас есть исполняющий скрипт, который можно вызвать после выполнения команды javac * .java :
#! /bin/sh echo "Empty" java Empty echo "Optimized" java Optimized echo "OptimizeThis" java OptimizeThis
И результат:
СТОП! Прежде чем открыть его, попробуйте оценить обоснованность между оптимизированной и неоптимизированной версией, а также насколько быстрее работает класс Empty . Если у вас есть ваша оценка, вы можете открыть результат:
verhasp:java verhasp$ ./testrun.sh Empty 1970 Optimized 1987 OptimizeThis 1970 verhasp:java verhasp$ ./testrun.sh Empty 1986 Optimized 2026 OptimizeThis 2001 verhasp:java verhasp$ ./testrun.sh Empty 1917 Optimized 1892 OptimizeThis 1899 verhasp:java verhasp$ ./testrun.sh Empty 1908 Optimized 1903 OptimizeThis 1899 verhasp:java verhasp$ ./testrun.sh Empty 1898 Optimized 1891 OptimizeThis 1892 verhasp:java verhasp$ ./testrun.sh Empty 1896 Optimized 1896 OptimizeThis 1897 verhasp:java verhasp$ ./testrun.sh Empty 1897 Optimized 1903 OptimizeThis 1897 verhasp:java verhasp$ ./testrun.sh Empty 1908 Optimized 1892 OptimizeThis 1900 verhasp:java verhasp$ ./testrun.sh Empty 1899 Optimized 1905 OptimizeThis 1904 verhasp:java verhasp$ ./testrun.sh Empty 1891 Optimized 1896 OptimizeThis 1896 verhasp:java verhasp$ ./testrun.sh Empty 1895 Optimized 1891 OptimizeThis 1904 verhasp:java verhasp$ ./testrun.sh Empty 1898 Optimized 1889 OptimizeThis 1894 verhasp:java verhasp$ ./testrun.sh Empty 1917 Optimized 1894 OptimizeThis 1898 verhasp:java verhasp$
Заключение? Прежде чем голосовать за первый выбор, прочитайте все возможные ответы!
Нажмите здесь, чтобы посмотреть опрос.
Тесты выполнялись на 8 ГБ памяти MacBook Pro7.1 с OS X 10.7.4, 7-й Java (вы могли заметить, что это уже была Java 7). Вот вывод java-версии :
verhasp:java verhasp$ java -version java version "1.7.0_04" Java(TM) SE Runtime Environment (build 1.7.0_04-b21) Java HotSpot(TM) 64-Bit Server VM (build 23.0-b21, mixed mode)