Мы обычно говорим, что 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)