Модераторы: LSD, AntonSaburov

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Работаем с байткодом 
:(
    Опции темы
 
Была ли данная информация вам интересна?
Да [ 72 ]  [84.71%]
Нет [ 4 ]  [4.71%]
По барабану [ 9 ]  [10.59%]
Всего проголосовавших: 85
В этом опросе возможен один вариант ответа
Гости не могут голосовать 
Domestic Cat
Дата 27.11.2004, 07:24 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Экс. модератор
Сообщений: 5452
Регистрация: 3.5.2004
Где: Dallas, US

Репутация: 50
Всего: 172



В связи с постоянным интересом к оптимизации Java программ решил начать на форуме разговор о байткоде.

Для начала введем некоторые термины которые понадобятся в дальнейшем.
opcode, опкод - одна инструкция языка JVM
bytecode, байткод - набор таких инструкций
стек - структура данных типа LIFO (last in, first out)

Чтобы понять как работает байткод, нужно четко представлять что делает JVM в процессе исполнения этого кода.

1. Фреймы
Каждый тред получает в свою собственность часть стека. Когда JVM вызывает к-л метод, на стеке создается новый фрейм. Он состоит из двух частей:
a. Массив локальных переменных. Этот массив содержит:
- элемент номер 0 массива есть ссылка this, если метод является инстанс методом
- параметры, передаваемые методу
б. Стек операндов Это LIFO стек, на который помешаются / вынимаются перенные в процессе выполнения программы.
Размеры обеих структур определяются во время компиляции.
Фрейм также содержит ссылку на runtime constant pool, принадлежащий классу данного объекта.

2. Байткод.
Почему байткод? Потому что один опкод весит 1 байт smile
Опкод может иметь префикс, который указывает, чем оперирует опкод:
а - ссылка на объект
i - int
b - byte
d - double
f - float
l - long
s - short
c - char

3. Пример.
Здесь мы одновременно разберемся с основами байткода и примером оптимизации синхронизированного метода.
Имеем следующий класс:

Код

public class Test
{
private int i;
private Object lock = new Object();

public int doTest()
{
               i++;
               return i;
}

public synchronized int doSynchronizedTest1()
{
               i++;
               return i;
}


public int doSynchronizedTest2()
{
               synchronized(lock)
               {
                i++;
                return i;
               }
}
}


Как видим, есть три различных метода, выполняющих одну и ту же работы, за одним исключением: первый метод не синхронизирован, в то время как синхронизированы остальные два, причем на разных объектах - doSynchronizedTest1 синхронизирован на this, doSynchronizedTest2 - на объекте lock.
Будут ли они различаться по скорости? какой из них наиболее оптимален? На эти вопросы можно ответить, посмотрев байткод данного класса.
Скомпилируем класс:
Код

cat> javac Test.java

Далее воспользуемся стандартным сановским дизассемблером:
Код

cat> javap -c Test > test.txt

Мы получим дизассемблированный код, записанный в файле test.txt.
Я приведу его полностью, а затем разберу по частям.
Код

Compiled from "Test.java"
public class Test extends java.lang.Object{
public Test();
 Code:
  0: aload_0
  1: invokespecial #1; //Method java/lang/Object."<init>":()V
  4: aload_0
  5: new #2; //class Object
  8: dup
  9: invokespecial #1; //Method java/lang/Object."<init>":()V
  12: putfield #3; //Field lock:Ljava/lang/Object;
  15: return

public int doTest();
 Code:
  0: aload_0
  1: dup
  2: getfield #4; //Field i:I
  5: iconst_1
  6: iadd
  7: putfield #4; //Field i:I
  10: aload_0
  11: getfield #4; //Field i:I
  14: ireturn

public synchronized int doSynchronizedTest1();
 Code:
  0: aload_0
  1: dup
  2: getfield #4; //Field i:I
  5: iconst_1
  6: iadd
  7: putfield #4; //Field i:I
  10: aload_0
  11: getfield #4; //Field i:I
  14: ireturn

public int doSynchronizedTest2();
 Code:
  0: aload_0
  1: getfield #3; //Field lock:Ljava/lang/Object;
  4: dup
  5: astore_1
  6: monitorenter
  7: aload_0
  8: dup
  9: getfield #4; //Field i:I
  12: iconst_1
  13: iadd
  14: putfield #4; //Field i:I
  17: aload_0
  18: getfield #4; //Field i:I
  21: aload_1
  22: monitorexit
  23: ireturn
  24: astore_2
  25: aload_1
  26: monitorexit
  27: aload_2
  28: athrow
 Exception table:
  from   to  target type
    7    23    24   any
   24    27    24   any

}


3.1 Общие замечания
Ясно, что первая строка - название файла, содержащего класс; вторая строка описывает наследование.
Каждый метод содержится в байткод массиве, имеющем тип byte[]. (Каждой команде соответствует код типа byte). Индексы слева соответствуют элементам этого массива, (вспомним, что опкод занимает 1 байт).
Поскольку опкод может также иметь аргументы, нумерация идет с разрывами: например код
Код

putfield #4;

запишется так:
Код

{..., 181, 0, 4, ...}

Если 181 (putfield) - седьмой элемент массива, то вместе с аргументом он займет 3 байта.
Далее идет код конструкторов, а затем всех методов в алфавитном порядке.
3.2 Конструктор
Если вы помните, наш код не содержит конструктора вообще - а байткод конструктора есть!
Код

0: aload_0

Опкод грузит ссылку this (а) из нулевого элемента массива локальных переменных (поскольку это инстанс метод, она там есть) на стек
Код

1: invokespecial #1; //Method java/lang/Object."<init>":()V

Вызываем конструктор суперкласса (то есть Object()), #1 соответствует номеру этого конструктора в constant pool. Для верности дизассемблер добавил коммент с указанием, что это за метод smile Вызываем мы его естественно, на this, который сидел в стеке. При вызове эта ссылка была вынута из стека, и строка 4: aload_0 опять ложит ее на стек.
Далее идет инициализация инстанс переменной lock - оказывается, это делается в конструкторе. Она создается с помощью
Код

5: new #2; //class Object

создаетйся ее дубликат на стеке:
Код

8: dup

опять вызывается конструктор суперкласса, и готовый Объект ложится в поле класса Test:
Код

12: putfield #3; //Field lock:Ljava/lang/Object;

и выполняется return.

// Продолжение через полчаса smile

Это сообщение отредактировал(а) Domestic Cat - 27.11.2004, 09:41


--------------------

PM   Вверх
Domestic Cat
Дата 27.11.2004, 08:09 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Экс. модератор
Сообщений: 5452
Регистрация: 3.5.2004
Где: Dallas, US

Репутация: 50
Всего: 172



3.3 Метод doTest

Код

0: aload_0
1: dup

Грузим this на стек и делаем его дубликат.
Код

2: getfield #4; //Field i:I

Берем этот дубликат и вызываем на нем опкод getfield - т.е. ложим на стек поле i.
Код

5: iconst_1
6: iadd

Объявляем целую константу 1 (i) и прибавляем ее к полю i.
Код

7: putfield #4; //Field i:I

Ложим результат в i.
Код

10: aload_0
11: getfield #4; //Field i:I
14: ireturn

Грузим на стек this, берем его поле номер 4 и возвращаем его.

3.4 Метод doSynchronizedTest1
А теперь посмотрм на метод doSynchronizedTest1. Удивительно, но он совпадает с предыдущим (несинхронизированным) методом один в один!
Загадка разрешается просто : при синхронизации метода байткод остается прежним, но зато меняется флаг ACC_SYNCHRONIZED структуры method_info этого метода.

3.5 Метод doSynchronizedTest2
Здесь будет немного посложнее чем в предыдущем случае.
Мы берем this, и получаем ссылку на объект lock:
Код

0: aload_0
1: getfield #3; //Field lock:Ljava/lang/Object;

Далее, делается дубликат объекта, и ссылка на него (аstore) помещается в локальный массив как элемент номер 1
Код

4: dup
5: astore_1

Теперь метод входит в синхронизированную область (monitorenter) , в которой может находиться только 1 тред. Далее идет код, аналогичный предыдущим двум методам. Лок освобождается и результат возвращается в строках:
Код

22: monitorexit
23: ireturn

Заметьте, что метод требует лок сразу перед monitorenter, и вынимает его из локального массива сразу перед monitorexit.
А что же идет дальше? А дальше идет код на случай, если будет брошен иксепшн в синхронизированном блоке. Это легко видеть, если начать читать с конца:
Код

Exception table:
  from   to  target type
    7    23    24   any
   24    27    24   any

означает следующее:
Смотрим с опкода номер 7 по номер 23, если будет брошен любой (any) иксепшн, перейти на код номер 24. Если иксепшн будет брошен между 24 (включительно) и 27 (не включая) опкодами - перейти назад, на номер 24.
Код после 23 опкода простой: берем this и ложим его в локальный массив на индех 2; берем лок из локального массива (индекс 1) и пытаемся освободить лок (monitorexit). Берем this и бросаем иксепшн со ссылкой на него.
Грубо говоря, в случае иксепшна внутри синхронизованного блока программа до потери пульса будет пытаться высвободить лок объекта и выйти из блока.

Какая мораль следует из всего этого? Простая: синхронизировать метод оптимальнее, чем кусок этого метода! Потеря идет как в скорости (за счет манипуляций с локом), так и в размере кода.

Естественно, это не значит., что теперь только так и нужно писать. На самом деле, в многопоточной программе синхронизация блока может быть на много порядков выгоднее, чем синхронизация всего метода. А вот для данного случая, как оказалось, выигрыш дает синхронизированный метод. Просто в случаях, когда скорость суперважна, пользуйтесь дизассемблером. smile

ПС. Узнать все о байткоде можно здесь:

http://java.sun.com/docs/books/vmspec/2nd-...pecTOC.doc.html

Это сообщение отредактировал(а) Domestic Cat - 27.11.2004, 20:35


--------------------

PM   Вверх
Domestic Cat
Дата 28.11.2004, 01:37 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Экс. модератор
Сообщений: 5452
Регистрация: 3.5.2004
Где: Dallas, US

Репутация: 50
Всего: 172



Я решил добавить еще 1 пример с разбором байткода. Исходный текст такой:
Код

public class Test
{
  public static void main(String [] args)
  {
    long ll=45000;
    long ts = System.currentTimeMillis();
    for(int i=0;i<1000000;i++)
       {
          ll=45000;
          for (; ll > 1; )
           {
                 ll -= 5000;
           }
       }
       long te = System.currentTimeMillis();
       System.out.println("Division by subtraction " + (te-ts) + " milliseconds");
   }
}


Комментарии к байткоду я дам в самом коде в виде комментов. Я также привожу содержимое стека и массива локальных переменных, обозначая их s: и mlp: соответственно. Внутри циклов содержимое фрейма показано для одного прохода для каждого цикла. Самый нижний элемент стека и элемент массива с индексом 0 находятся слева. Я не привожу текста конструктора, т.к. он тривиален. ms1, ms2 - значения таймера в соответствующие моменты времени. Массив локальных переменных хранит long и double в 2х ячейках, такие места в млп я обозначаю "-".
Код

public static void main(java.lang.String[]);
 Code:
  0: ldc2_w #2; //long 45000l
/* s: 45000 */
  3: lstore_1
/* mlp: 0, 45000, -*/
  4: invokestatic #4; //Method java/lang/System.currentTimeMillis:()J
/* s: ms1; mlp: 0, 45000, - */
  7: lstore_3
/* mlp: 0, 45000, -, ms1, - */
  8: iconst_0
/* s: 0; mlp: 0, 45000, -, ms1, - */
  9: istore 5
/* mlp: 0, 45000, -, ms1, -, 0 */
  11: iload 5
/* s: 0; mlp: 0, 45000, -, ms1, -, 0 */
  13: ldc #5; //int 1000000
/* s: 0, 1000000; mlp: 0, 45000, -, ms1, -, 0 */
  15: if_icmpge 43
/* mlp: 0, 45000, -, ms1, -, 0
            Если 0 > 1000000, прыгнуть к 43: */
  18: ldc2_w #2; //long 45000l
/* s: 45000; mlp: 0, 45000, -, ms1, -, 0 */
  21: lstore_1
/* mlp: 0, 45000, -, ms1, -, 0 */
  22: lload_1
/* s: 45000; mlp: 0, 45000, -, ms1, -, 0 */
  23: lconst_1
/* s: 45000, 1; mlp: 0, 45000, -, ms1, -, 0 */
  24: lcmp
  25: ifle 37
/* mlp: 0, 45000, -, ms1, -, 0
        Если 45000 <= 1 прыгнуть к 37: */
  28: lload_1
/* s: 45000; mlp: 0, 45000, -, ms1, -, 0 */
  29: ldc2_w #6; //long 5000l
/* s: 45000, 5000; mlp: 0, 45000, -, ms1, -, 0 */
  32: lsub
/* s: 40000; mlp: 0, 45000, -, ms1, -, 0 */
  33: lstore_1
/* mlp: 0, 40000, -, ms1, -, 0 */
  34: goto 22
       /* Внутренний цикл завершен*/
  37: iinc 5, 1
/* mlp: 0, 0, -, ms1, -, 1
           - инкремент переменной на 5-м индксе в млп*/
  40: goto 11
        /*Внешний цикл завершен*/
  43: invokestatic #4; //Method java/lang/System.currentTimeMillis:()J
/* s: ms2; mlp: 0, 0, -, ms1, -, 1 */
  46: lstore 5
/* mlp: 0, 0, -, ms1, -, ms2, - */
  48: getstatic #8; //Field java/lang/System.out:Ljava/io/PrintStream;
/* s: PrintStream; mlp: 0, 0, -, ms1, -, ms2, - */
  51: new #9; //class StringBuffer
/* s: PrintStream, StringBuffer; mlp: 0, 0, -, ms1, -, ms2, - */
  54: dup
/* s: PrintStream, StringBuffer, StringBuffer; mlp: 0, 0, -, ms1, -, ms2, - */
  55: invokespecial #10; //Method java/lang/StringBuffer."<init>":()V
/* s: PrintStream, StringBuffer; mlp: 0, 0, -, ms1, -, ms2, - */
  58: ldc #11; //String Division by subtraction
/* s: PrintStream, StringBuffer, "Division by subtraction"; mlp: 0, 0, -, ms1, -, ms2, - */
  60: invokevirtual #12; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
/* s: PrintStream, StringBuffer; mlp: 0, 0, -, ms1, -, ms2, - */
  63: lload 5
/* s: PrintStream, StringBuffer, ms2; mlp: 0, 0, -, ms1, -, ms2, - */
  65: lload_3
/* s: PrintStream, StringBuffer, ms2, ms1; mlp: 0, 0, -, ms1, -, ms2, - */
  66: lsub
/* s: PrintStream, StringBuffer, ms2-ms1; mlp: 0, 0, -, ms1, -, ms2, - */
  67: invokevirtual #13; //Method java/lang/StringBuffer.append:(J)Ljava/lang/StringBuffer;
/* s: PrintStream, StringBuffer; mlp: 0, 0, -, ms1, -, ms2, - */
  70: ldc #14; //String  milliseconds
/* s: PrintStream, StringBuffer, " milliseconds"; mlp: 0, 0, -, ms1, -, ms2, - */
  72: invokevirtual #12; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
/* s: PrintStream, StringBuffer; mlp: 0, 0, -, ms1, -, ms2, - */
  75: invokevirtual #15; //Method java/lang/StringBuffer.toString:()Ljava/lang/String;
/* s: PrintStream, "Division by subtraction xxxx milliseconds"; mlp: 0, 0, -, ms1, -, ms2, - */
  78: invokevirtual #16; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
/* mlp: 0, 0, -, ms1, -, ms2, - */
  81: return

}




// попозже допишу о constant pool и LineNumberTable

Это сообщение отредактировал(а) Domestic Cat - 28.11.2004, 02:20


--------------------

PM   Вверх
Domestic Cat
Дата 28.11.2004, 03:54 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Экс. модератор
Сообщений: 5452
Регистрация: 3.5.2004
Где: Dallas, US

Репутация: 50
Всего: 172



Constant Pool

Пул констант JVM содержит информацию о всех полях, методах, интерфейсах и классах, используемых методами данного класса. Опкод может ссылаться на данную информацию через диез и номер строки: ldc #5.

Если использовать флаг -verbose, дизассемблер выводит информацию о пуле. Так, для примера выше получим:
Код

Compiled from "Test.java"
public class Test extends java.lang.Object
 SourceFile: "Test.java"
 minor version: 0
 major version: 0
 Constant pool:
const #1 = Method #18.#27; //  java/lang/Object."<init>":()V
const #2 = long 45000l;
const #4 = Method #28.#29; //  java/lang/System.currentTimeMillis:()J
const #5 = int 1000000;
const #6 = long 5000l;
const #8 = Field #28.#30; //  java/lang/System.out:Ljava/io/PrintStream;
const #9 = class #31; //  StringBuffer
const #10 = Method #9.#27; //  java/lang/StringBuffer."<init>":()V
const #11 = String #32; //  Division by subtraction
const #12 = Method #9.#33; //  java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
const #13 = Method #9.#34; //  java/lang/StringBuffer.append:(J)Ljava/lang/StringBuffer;
const #14 = String #35; //   milliseconds
const #15 = Method #9.#36; //  java/lang/StringBuffer.toString:()Ljava/lang/String;
const #16 = Method #37.#38; //  java/io/PrintStream.println:(Ljava/lang/String;)V
const #17 = class #39; //  Test1
const #18 = class #40; //  Object
const #19 = Asciz <init>;
const #20 = Asciz ()V;
const #21 = Asciz Code;
const #22 = Asciz LineNumberTable;
const #23 = Asciz main;
const #24 = Asciz ([Ljava/lang/String;)V;
const #25 = Asciz SourceFile;
const #26 = Asciz Test1.java;
const #27 = NameAndType #19:#20;//  "<init>":()V
const #28 = class #41; //  System
const #29 = NameAndType #42:#43;//  currentTimeMillis:()J
const #30 = NameAndType #44:#45;//  out:Ljava/io/PrintStream;
const #31 = Asciz java/lang/StringBuffer;
const #32 = Asciz Division by subtraction;
const #33 = NameAndType #46:#47;//  append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
const #34 = NameAndType #46:#48;//  append:(J)Ljava/lang/StringBuffer;
const #35 = Asciz  milliseconds;
const #36 = NameAndType #49:#50;//  toString:()Ljava/lang/String;
const #37 = class #51; //  PrintStream
const #38 = NameAndType #52:#53;//  println:(Ljava/lang/String;)V
const #39 = Asciz Test1;
const #40 = Asciz java/lang/Object;
const #41 = Asciz java/lang/System;
const #42 = Asciz currentTimeMillis;
const #43 = Asciz ()J;
const #44 = Asciz out;
const #45 = Asciz Ljava/io/PrintStream;;
const #46 = Asciz append;
const #47 = Asciz (Ljava/lang/String;)Ljava/lang/StringBuffer;;
const #48 = Asciz (J)Ljava/lang/StringBuffer;;
const #49 = Asciz toString;
const #50 = Asciz ()Ljava/lang/String;;
const #51 = Asciz java/io/PrintStream;
const #52 = Asciz println;
const #53 = Asciz (Ljava/lang/String;)V;

{
public Test();
 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 static void main(java.lang.String[]);
 Code:
  Stack=6, Locals=7, Args_size=1
  0: ldc2_w #2; //long 45000l
  3: lstore_1
  4: invokestatic #4; //Method java/lang/System.currentTimeMillis:()J
  7: lstore_3
  8: iconst_0
  9: istore 5
  11: iload 5
  13: ldc #5; //int 1000000
  15: if_icmpge 43
  18: ldc2_w #2; //long 45000l
  21: lstore_1
  22: lload_1
  23: lconst_1
  24: lcmp
  25: ifle 37
  28: lload_1
  29: ldc2_w #6; //long 5000l
  32: lsub
  33: lstore_1
  34: goto 22
  37: iinc 5, 1
  40: goto 11
  43: invokestatic #4; //Method java/lang/System.currentTimeMillis:()J
  46: lstore 5
  48: getstatic #8; //Field java/lang/System.out:Ljava/io/PrintStream;
  51: new #9; //class StringBuffer
  54: dup
  55: invokespecial #10; //Method java/lang/StringBuffer."<init>":()V
  58: ldc #11; //String Division by subtraction
  60: invokevirtual #12; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
  63: lload 5
  65: lload_3
  66: lsub
  67: invokevirtual #13; //Method java/lang/StringBuffer.append:(J)Ljava/lang/StringBuffer;
  70: ldc #14; //String  milliseconds
  72: invokevirtual #12; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
  75: invokevirtual #15; //Method java/lang/StringBuffer.toString:()Ljava/lang/String;
  78: invokevirtual #16; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
  81: invokestatic #4; //Method java/lang/System.currentTimeMillis:()J
  84: lstore_3
  85: return
 LineNumberTable:
  line 5: 0
  line 6: 4
  line 7: 8
  line 9: 18
  line 10: 22
  line 12: 28
  line 7: 37
  line 15: 43
  line 16: 48
  line 17: 81
  line 18: 85

}



Возьмем для примера ссылку #1:

Код

const #1 = Method #18.#27;

Понятно, что это метод,. причем он сам ссылается на пул: #18. Первая ссылка определяет к какому классу принадлежит этот метод, причем клас тоже дает ссылку на пул, но на сей раз на строку в аэски формате :
Код

const #18 = class #40;
...
const #40 = Asciz java/lang/Object;

Далее идет .#27, что переводится как:
Код

const #27 = NameAndType #19:#20;
....
const #19 = Asciz <init>;
const #20 = Asciz ()V;

NameAndTypе определяет аргументы и тип метода: мы видим, что это конструктор, не принимающий аргументов. То же самое нам и сказал дизассемблер в комменте к #1:
Код

const #1 = Method #18.#27; //  java/lang/Object."<init>":()V

Все определения классов в констант пуле должны быть такими же, как возвращает метод getClass().

2. LineNumberTable
Эта таблица находится в конце метода, и определяет соответствие между строками java-файла и индексами байткода.
Так, мы видим, что строка 9 файла Test.java :
Код

ll=45000;

имеет индекс 18 в байткоде:
Код

18: ldc2_w #2; //long 45000l



--------------------

PM   Вверх
Sardar
Дата 28.11.2004, 12:43 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Бегун
****


Профиль
Группа: Модератор
Сообщений: 6986
Регистрация: 19.4.2002
Где: Нидерланды, Groni ngen

Репутация: 4
Всего: 317



Похоже что простейший обфускатор/оптимизатор удаляет LineNumberTable в методах и изменяет имена в константном пуле. Обфускаторы посложнее перебирают код методов.

Интересно что обьекты понимаются на уровне vm как структуры(по крайней мере я так понял), т.е. можно сделать компилятор C и других структурно/ООП языков в байткод Ява.

Извиняюсь что влез посреди статьи. Если глаза мозолит, удалите пост smile


--------------------
 Опыт - сын ошибок трудных  © А. С. Пушкин
 Процесс написания своего велосипеда повышает профессиональный уровень программиста. © Opik
 Оценить мои качества можно тут.
PM   Вверх
Sleepy_PIP
Дата 28.11.2004, 17:29 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 512
Регистрация: 30.6.2004
Где: Moscow

Репутация: 4
Всего: 12



Цитата(Domestic @ 28.11.2004, 01:37)
Я решил добавить еще 1 пример с разбором байткода. Исходный текст такой:
Код

public class Test
{
  public static void main(String [] args)
  {
    long ll=45000;
    long ts = System.currentTimeMillis();
    for(int i=0;i<1000000;i++)
       {
          ll=45000;
          for (; ll > 1; )
           {
                 ll -= 5000;
           }
       }
       long te = System.currentTimeMillis();
       System.out.println("Division by subtraction " + (te-ts) + " milliseconds");
   }
}


Комментарии к байткоду я дам в самом коде в виде комментов. Я также привожу содержимое стека и массива локальных переменных, обозначая их s: и mlp: соответственно. Внутри циклов содержимое фрейма показано для одного прохода для каждого цикла. Самый нижний элемент стека и элемент массива с индексом 0 находятся слева. Я не привожу текста конструктора, т.к. он тривиален. ms1, ms2 - значения таймера в соответствующие моменты времени. Массив локальных переменных хранит long и double в 2х ячейках, такие места в млп я обозначаю "-".
Код

public static void main(java.lang.String[]);
 Code:
  0: ldc2_w #2; //long 45000l
/* s: 45000 */
  3: lstore_1
/* mlp: 0, 45000, -*/
  4: invokestatic #4; //Method java/lang/System.currentTimeMillis:()J
/* s: ms1; mlp: 0, 45000, - */
  7: lstore_3
/* mlp: 0, 45000, -, ms1, - */
  8: iconst_0
/* s: 0; mlp: 0, 45000, -, ms1, - */
  9: istore 5
/* mlp: 0, 45000, -, ms1, -, 0 */
  11: iload 5
/* s: 0; mlp: 0, 45000, -, ms1, -, 0 */
  13: ldc #5; //int 1000000
/* s: 0, 1000000; mlp: 0, 45000, -, ms1, -, 0 */
  15: if_icmpge 43
/* mlp: 0, 45000, -, ms1, -, 0
            Если 0 > 1000000, прыгнуть к 43: */
  18: ldc2_w #2; //long 45000l
/* s: 45000; mlp: 0, 45000, -, ms1, -, 0 */
  21: lstore_1
/* mlp: 0, 45000, -, ms1, -, 0 */
  22: lload_1
/* s: 45000; mlp: 0, 45000, -, ms1, -, 0 */
  23: lconst_1
/* s: 45000, 1; mlp: 0, 45000, -, ms1, -, 0 */
  24: lcmp
  25: ifle 37
/* mlp: 0, 45000, -, ms1, -, 0
        Если 45000 <= 1 прыгнуть к 37: */
  28: lload_1
/* s: 45000; mlp: 0, 45000, -, ms1, -, 0 */
  29: ldc2_w #6; //long 5000l
/* s: 45000, 5000; mlp: 0, 45000, -, ms1, -, 0 */
  32: lsub
/* s: 40000; mlp: 0, 45000, -, ms1, -, 0 */
  33: lstore_1
/* mlp: 0, 40000, -, ms1, -, 0 */
  34: goto 22
       /* Внутренний цикл завершен*/
  37: iinc 5, 1
/* mlp: 0, 0, -, ms1, -, 1
           - инкремент переменной на 5-м индксе в млп*/
  40: goto 11
        /*Внешний цикл завершен*/
  43: invokestatic #4; //Method java/lang/System.currentTimeMillis:()J
/* s: ms2; mlp: 0, 0, -, ms1, -, 1 */
  46: lstore 5
/* mlp: 0, 0, -, ms1, -, ms2, - */
  48: getstatic #8; //Field java/lang/System.out:Ljava/io/PrintStream;
/* s: PrintStream; mlp: 0, 0, -, ms1, -, ms2, - */
  51: new #9; //class StringBuffer
/* s: PrintStream, StringBuffer; mlp: 0, 0, -, ms1, -, ms2, - */
  54: dup
/* s: PrintStream, StringBuffer, StringBuffer; mlp: 0, 0, -, ms1, -, ms2, - */
  55: invokespecial #10; //Method java/lang/StringBuffer."<init>":()V
/* s: PrintStream, StringBuffer; mlp: 0, 0, -, ms1, -, ms2, - */
  58: ldc #11; //String Division by subtraction
/* s: PrintStream, StringBuffer, "Division by subtraction"; mlp: 0, 0, -, ms1, -, ms2, - */
  60: invokevirtual #12; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
/* s: PrintStream, StringBuffer; mlp: 0, 0, -, ms1, -, ms2, - */
  63: lload 5
/* s: PrintStream, StringBuffer, ms2; mlp: 0, 0, -, ms1, -, ms2, - */
  65: lload_3
/* s: PrintStream, StringBuffer, ms2, ms1; mlp: 0, 0, -, ms1, -, ms2, - */
  66: lsub
/* s: PrintStream, StringBuffer, ms2-ms1; mlp: 0, 0, -, ms1, -, ms2, - */
  67: invokevirtual #13; //Method java/lang/StringBuffer.append:(J)Ljava/lang/StringBuffer;
/* s: PrintStream, StringBuffer; mlp: 0, 0, -, ms1, -, ms2, - */
  70: ldc #14; //String  milliseconds
/* s: PrintStream, StringBuffer, " milliseconds"; mlp: 0, 0, -, ms1, -, ms2, - */
  72: invokevirtual #12; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
/* s: PrintStream, StringBuffer; mlp: 0, 0, -, ms1, -, ms2, - */
  75: invokevirtual #15; //Method java/lang/StringBuffer.toString:()Ljava/lang/String;
/* s: PrintStream, "Division by subtraction xxxx milliseconds"; mlp: 0, 0, -, ms1, -, ms2, - */
  78: invokevirtual #16; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
/* mlp: 0, 0, -, ms1, -, ms2, - */
  81: return

}




// попозже допишу о constant pool и LineNumberTable

эээ ... торможу. - а где собственно выситание?
Спасибо!



--------------------
--
Sleepy_PIP. Pavel Pryazhentsev (ex. 2:5020/141) "... Лучше быть нужным, чем
свободным ..."
PM MAIL ICQ   Вверх
Domestic Cat
Дата 28.11.2004, 17:32 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Экс. модератор
Сообщений: 5452
Регистрация: 3.5.2004
Где: Dallas, US

Репутация: 50
Всего: 172



Цитата(Sardar @ 28.11.2004, 03:43)
Интересно что обьекты понимаются на уровне vm как структуры(по крайней мере я так понял), т.е. можно сделать компилятор C и других структурно/ООП языков в байткод Ява.


Думаю, что можно. Хотя объекты понимаются именно как объекты smile - ведь объект - это структура + наследование + инкапсуляция. Каждый class-файл начинается с описания классов, полей и методов:
Код

ClassFile {
    u4 magic; // CA FE BA BE  :)
    u2 minor_version;
    u2 major_version;
    u2 constant_pool_count;  // количество записей в пуле
    cp_info constant_pool[constant_pool_count-1];
    u2 access_flags;
    u2 this_class;
    u2 super_class;
    u2 interfaces_count;
    u2 interfaces[interfaces_count];
    u2 fields_count;
    field_info fields[fields_count];
    u2 methods_count;
    method_info methods[methods_count];
    u2 attributes_count;
    attribute_info attributes[attributes_count];
   }

Тут определен суперкласс, модифаеры для полей, методов, и пр. Структура бы все этого не имела.

Вечерком допишу еще че-нибудь smile.

Добавлено @ 17:36
Цитата(Sleepy_PIP @ 28.11.2004, 08:29)
эээ ... торможу. - а где собственно выситание?
Спасибо!


Вот тут:

Код

  28:  lload_1
             /* Ложим 45000 на стек */
  29:  ldc2_w  #6; //long 5000l
             /* Теперь стек такой : 45000, 5000 */
  32:  lsub
             /* на стеке 40000 */
  33:  lstore_1
             /* ложим назад, в массив, под индексом 1*/




--------------------

PM   Вверх
Sleepy_PIP
Дата 28.11.2004, 17:45 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 512
Регистрация: 30.6.2004
Где: Moscow

Репутация: 4
Всего: 12



а еще-б разобрать примерчик с дерением а ? ... интересно все-ж во что преобразуется оператор деления ...
СПАСИБО!



--------------------
--
Sleepy_PIP. Pavel Pryazhentsev (ex. 2:5020/141) "... Лучше быть нужным, чем
свободным ..."
PM MAIL ICQ   Вверх
Domestic Cat
Дата 28.11.2004, 18:36 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Экс. модератор
Сообщений: 5452
Регистрация: 3.5.2004
Где: Dallas, US

Репутация: 50
Всего: 172



Цитата(Sleepy_PIP @ 28.11.2004, 08:45)
а еще-б разобрать примерчик с дерением а ? ... интересно все-ж во что преобразуется оператор деления ...
СПАСИБО!


Нет проблем:

Код

public class Test
{
public static void main(String [] args)
{
              long a = 20000;
              long b = 1000;
              double x = a / b;
}
}


Код

Compiled from "Test.java"
public class Test extends java.lang.Object
 SourceFile: "Test.java"
 minor version: 0
 major version: 0
 Constant pool:
const #1 = Method #7.#16; //  java/lang/Object."<init>":()V
const #2 = long 20000l;
const #4 = long 1000l;
const #6 = class #17; //  Test
const #7 = class #18; //  Object
const #8 = Asciz <init>;
const #9 = Asciz ()V;
const #10 = Asciz Code;
const #11 = Asciz LineNumberTable;
const #12 = Asciz main;
const #13 = Asciz ([Ljava/lang/String;)V;
const #14 = Asciz SourceFile;
const #15 = Asciz Test.java;
const #16 = NameAndType #8:#9;//  "<init>":()V
const #17 = Asciz Test;
const #18 = Asciz java/lang/Object;

{
public Test();
 Code:
  Stack=1, Locals=1, Args_size=1
  0: aload_0
  1: invokespecial #1; //Method java/lang/Object."<init>":()V
  4: return
 LineNumberTable:
  line 2: 0

public static void main(java.lang.String[]);
 Code:
  Stack=4, Locals=7, Args_size=1
  0: ldc2_w #2; //long 20000l       // Ложим на стек 20000
  3: lstore_1                                   // Ложим ее в локальный массив
  4: ldc2_w #4; //long 1000l         // Ложим на стек 1000
  7: lstore_3                                   // Ложим ее в локальный массив
  8: lload_1                                    // Ложим на стек 20000
  9: lload_3                                    // Ложим на стек 1000
  10: ldiv                                         // Делим
  11: l2d                                         // Поскольку результат деления long,
                                                       // конвертируем в double
  12: dstore 5                               // Ложим результат в локальный массив
  14: return
 LineNumberTable:
  line 6: 0
  line 7: 4
  line 8: 8
  line 9: 14




Это сообщение отредактировал(а) Domestic Cat - 28.11.2004, 18:37


--------------------

PM   Вверх
Sardar
Дата 28.11.2004, 20:42 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Бегун
****


Профиль
Группа: Модератор
Сообщений: 6986
Регистрация: 19.4.2002
Где: Нидерланды, Groni ngen

Репутация: 4
Всего: 317



Не понял смысла команды l2d, ведьмы уже поделили, на стеке должно лежать double.
И что то слишком много движений, из констант в стек, затем в локальный массив, затем опятьв стек и только потом делим.
В локальный массив по моему не нужно ложить значения, я понимаю что это переменные a и b, но оптимизатор должен был отбросить их!


--------------------
 Опыт - сын ошибок трудных  © А. С. Пушкин
 Процесс написания своего велосипеда повышает профессиональный уровень программиста. © Opik
 Оценить мои качества можно тут.
PM   Вверх
Domestic Cat
Дата 29.11.2004, 01:30 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Экс. модератор
Сообщений: 5452
Регистрация: 3.5.2004
Где: Dallas, US

Репутация: 50
Всего: 172



Цитата(Sardar @ 28.11.2004, 11:42)
Не понял смысла команды l2d, ведьмы уже поделили, на стеке должно лежать double.


Результатом деления 2х лонгов является лонг, а мы хотим получить дабл. Для этого и нужно сконвертировать.
Цитата(Sardar @ 28.11.2004, 11:42)
В локальный массив по моему не нужно ложить значения, я понимаю что это переменные a и b, но оптимизатор должен был отбросить их!


РАз ты объявил локальные переменные, JVM всегда будет совершать подобные телодвижения, она ведь смысла не понимает. Может, через 20 строк ты захочешь их вывести на консоль - а их нету !
Кстати, перемещения константы-> стек->массив->стек вполне нормальная вещь, если помнить, что со стеком можно сделать только 2 вещи: положить в него, и взять оттуда. Любая операция, не ложащая че-нибудь на стек, забирает оттуда переменную.


--------------------

PM   Вверх
Sardar
Дата 29.11.2004, 01:46 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Бегун
****


Профиль
Группа: Модератор
Сообщений: 6986
Регистрация: 19.4.2002
Где: Нидерланды, Groni ngen

Репутация: 4
Всего: 317



Цитата(Domestic @ 29.11.2004, 00:30)
Результатом деления 2х лонгов является лонг, а мы хотим получить дабл. Для этого и нужно сконвертировать.

Как можно получить результат деления в long, и при этом не потерять дробную часть? Ведь оператор деления возвратит double, от которого можно взять целое, но не наоборот!

Цитата(Domestic @ 29.11.2004, 00:30)
РАз ты объявил локальные переменные, JVM всегда будет совершать подобные телодвижения, она ведь смысла не понимает. Может, через 20 строк ты захочешь их вывести на консоль - а их нету !

Эти переменные локальные, т.е. я точно знаю время их жизни, вообще под локальный код можо точно посчитать занимаемое пространство в памяти, длину стека, минимально количество переменных, необходимых для работы.
Оптимизировать убирая "пустые" переменные - есть работа компилятора без указания всяких флагов. Просто привык что в микроконтроллере каждый байт памяти дорог(пишу на асме и C), теперь читаю байткод явы и вижу такую ересь...

Уверен что компиляторы сторонних разработчиков генерят более эффективный код...


--------------------
 Опыт - сын ошибок трудных  © А. С. Пушкин
 Процесс написания своего велосипеда повышает профессиональный уровень программиста. © Opik
 Оценить мои качества можно тут.
PM   Вверх
Domestic Cat
Дата 29.11.2004, 02:50 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Экс. модератор
Сообщений: 5452
Регистрация: 3.5.2004
Где: Dallas, US

Репутация: 50
Всего: 172



Цитата(Sardar @ 28.11.2004, 16:46)
Как можно получить результат деления в long, и при этом не потерять дробную часть? Ведь оператор деления возвратит double, от которого можно взять целое, но не наоборот!


Оператор деления лонгов возвращает лонг, как и оператор деления интов возвращает инт smile


Цитата(Sardar @ 28.11.2004, 16:46)
Эти переменные локальные, т.е. я точно знаю время их жизни, вообще под локальный код можо точно посчитать занимаемое пространство в памяти, длину стека, минимально количество переменных, необходимых для работы.
Оптимизировать убирая "пустые" переменные - есть работа компилятора без указания всяких флагов. Просто привык что в микроконтроллере каждый байт памяти дорог(пишу на асме и C), теперь читаю байткод явы и вижу такую ересь...


Память-то здесь тратится только стековая, и то на малый промежуток времени. Но все равно : ведь а и b используются при вычислении x. Такие вещи - проблема программиста, задача компилятора - скомпилировать, а оптимизацией займется JIT (just in time compiler). Нужен супероптимальный код - посмотри байткод и исправь smile Хотя выигрыш тут будет никакой.

Это сообщение отредактировал(а) Domestic Cat - 29.11.2004, 07:50


--------------------

PM   Вверх
Domestic Cat
Дата 29.11.2004, 04:33 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Экс. модератор
Сообщений: 5452
Регистрация: 3.5.2004
Где: Dallas, US

Репутация: 50
Всего: 172



Теперь немного о том, на чем я остановился прошлый раз: Constant Pool.

Грамматика дескрипторов

Все методы в констант пуле описываются загадочными строками типа:
Код

java/io/PrintStream.println:(Ljava/lang/String;)V

Эта строка называется дескриптором и однозначно описывает сигнатуру метода. Большая буква означает следующее:
Код

B  byte
C  char
D  double
F  float
I  int
J  long
L<classname>;  ссылка на класс <classname>
S  short  
Z  boolean
[  массив (одна размерность)
V   void


Примеры
1. Массив
Код

long[][][][][] array;

будет иметь дескриптор
Код

[[[[[J

Кстати, максимальнoe количество размерностeй массива - 255.

2. Метод
Дескриптор для метода имеет форму:
( ParameterDescriptor* ) ReturnDescriptor

Например
Код

void getIt(int i)

описывается дескриптором
Код

(I)V


Если один из параметров или возвращаемео значение - объект, указывается полное имя класса:
Код

Integer doIt(int i, Object o)

дескриптор
Код

(ILjava/lang/Object;)Ljava/lang/Integer;

Почему java/lang/Integer а не более привычное java.lang.Integer ? По историческим причинам: для совместимости со старыми JVM.

Конструктор обозначается через <init>:
Код

java/lang/Object."<init>":()V

- типичный пример "пустого" конструктора для класса, наследующего от Object.


Это сообщение отредактировал(а) Domestic Cat - 29.11.2004, 05:53


--------------------

PM   Вверх
Domestic Cat
Дата 29.11.2004, 07:07 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Экс. модератор
Сообщений: 5452
Регистрация: 3.5.2004
Где: Dallas, US

Репутация: 50
Всего: 172



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

Компиляция вызовов методов

Любой вызов метода означает создание нового фрейма на стеке. Текущая (выполняемая в данный момент) инструкция, точнее ее адрес, сидит в специальном регистре - pc register (program counter).
Естественно, в новый фрейм, в массив локальных переменных нужно передать аргументы и ссылку на объект, на котором был вызван метод.
Делается это так: на стек ложится ссылка на объект, поверх нее кладутся аргументы в нужном порядке, и вызывается один из методов invoke*****.
Рассмотрим на примере:
Код

public class Test
{
public void doIt()
{
 doThat(5, 10);
 doPublic();
 doStatic();
}
public void doPublic()
{}

private void doThat(int x, int y)
{}

public static void doStatic()
{}
}


Байткод:
Код

Compiled from "Test.java"
public class Test extends java.lang.Object
 SourceFile: "Test.java"
 minor version: 0
 major version: 0
 Constant pool:
const #1 = Method #6.#18; //  java/lang/Object."<init>":()V
const #2 = Method #5.#19; //  Test.doThat:(II)V
const #3 = Method #5.#20; //  Test.doPublic:()V
const #4 = Method #5.#21; //  Test.doStatic:()V
const #5 = class #22; //  Test
const #6 = class #23; //  Object
const #7 = Asciz <init>;
const #8 = Asciz ()V;
const #9 = Asciz Code;
const #10 = Asciz LineNumberTable;
const #11 = Asciz doIt;
const #12 = Asciz doPublic;
const #13 = Asciz doThat;
const #14 = Asciz (II)V;
const #15 = Asciz doStatic;
const #16 = Asciz SourceFile;
const #17 = Asciz Test.java;
const #18 = NameAndType #7:#8;//  "<init>":()V
const #19 = NameAndType #13:#14;//  doThat:(II)V
const #20 = NameAndType #12:#8;//  doPublic:()V
const #21 = NameAndType #15:#8;//  doStatic:()V
const #22 = Asciz Test;
const #23 = Asciz java/lang/Object;

{
public Test();
 Code:
  Stack=1, Locals=1, Args_size=1
  0: aload_0
  1: invokespecial #1; //Method java/lang/Object."<init>":()V
  4: return
 LineNumberTable:
  line 2: 0

public void doIt();
 Code:
  Stack=3, Locals=1, Args_size=1
  0: aload_0
  1: iconst_5
  2: bipush 10
  4: invokespecial #2; //Method doThat:(II)V
  7: aload_0
  8: invokevirtual #3; //Method doPublic:()V
  11: invokestatic #4; //Method doStatic:()V
  14: return
 LineNumberTable:
  line 6: 0
  line 7: 7
  line 8: 11
  line 9: 14

public void doPublic();
 Code:
  Stack=0, Locals=1, Args_size=1
  0: return
 LineNumberTable:
  line 11: 0

public static void doStatic();
 Code:
  Stack=0, Locals=0, Args_size=0
  0: return
 LineNumberTable:
  line 17: 0
}

Начнем с вызова метода doThat(). На стек ложится this, затем константа 5. Для оптимизации константы -1, 0, 1, 2, 3, 4, 5 в байткоде переносятся на стек инструксией iconst_x. Эта инструксия занимает 1 байт, тогда как аналогичный опкод bipush 10 весит 2 байта. Далее следует вызов метода. Что происходит при этом, см. на приаттаченном рисунке. Массив локальных переменных нового фрейма получает содержимое стека предыдущего фрейма.
invokespecial "опустошает" стек, поэтому когда тред возвращается к старому фрейму, стек пуст.
Всего есть 4 типа опкодов для вызова методов:
1. invokespecial. Эта инструкция используется для вызова конструкторов, приватных методов, и методов суперкласса (super()). Примеры: в конструкторе класса Test, вызов приватного метода doThat
2. invokestatic. Вызов статик метода.
3. invokeinterface Вызов методов интерфейса.
4. invokevirtual. Вызов любых других методов. Какой это будет метод, определяется в момент работы программы, из содержимого Constant Pool.


Это сообщение отредактировал(а) Domestic Cat - 5.3.2005, 11:08

Присоединённый файл ( Кол-во скачиваний: 31 )
Присоединённый файл  diag.jpg.jpg


--------------------

PM   Вверх
Ответ в темуСоздание новой темы Создание опроса
Правила форума "Java"
LSD   AntonSaburov
powerOn   tux
javastic
  • Прежде, чем задать вопрос, прочтите это!
  • Книги по Java собираются здесь.
  • Документация и ресурсы по Java находятся здесь.
  • Используйте теги [code=java][/code] для подсветки кода. Используйтe чекбокс "транслит", если у Вас нет русских шрифтов.
  • Помечайте свой вопрос как решённый, если на него получен ответ. Ссылка "Пометить как решённый" находится над первым постом.
  • Действия модераторов можно обсудить здесь.
  • FAQ раздела лежит здесь.

Если Вам помогли, и атмосфера форума Вам понравилась, то заходите к нам чаще! С уважением, LSD, AntonSaburov, powerOn, tux, javastic.

 
1 Пользователей читают эту тему (1 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Java: Общие вопросы | Следующая тема »


 




[ Время генерации скрипта: 0.1568 ]   [ Использовано запросов: 23 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.