Статьи

Печать сгенерированного кода сборки из JIT-компилятора Hotspot

Иногда при профилировании Java-приложения необходимо понимать код сборки, сгенерированный JIT-компилятором Hotspot. Это может быть полезно при определении того, какие решения по оптимизации приняты и как изменения нашего кода могут повлиять на сгенерированный код сборки. Иногда полезно также знать, какие инструкции выдаются при отладке параллельного алгоритма, чтобы гарантировать, что правила видимости были применены, как и ожидалось. Таким образом, я обнаружил довольно много ошибок в различных JVM.

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

Установка

Ранее необходимо было получить отладочную сборку для печати кода сборки, сгенерированного JIT Hotspot для JVM Oracle / SUN. Начиная с Java 7 стало возможным печатать сгенерированный код сборки, если подключаемый модуль дизассемблера установлен в стандартной JVM Oracle Hotspot. Чтобы установить плагин для 64-битного Linux, выполните следующие действия:

  1. Загрузите соответствующий двоичный файл или выполните сборку из исходного кода по адресу https://kenai.com/projects/base-hsdis/downloads.
  2. В Linux переименуйте linux-hsdis-amd64.so в libhsdis-amd64.so
  3. Скопируйте общую библиотеку в $ JAVA_HOME / jre / lib / amd64 / server

Теперь у вас установлен плагин!

Тестовая программа

Чтобы протестировать плагин, нам нужен код, который интересен программисту и исполняется достаточно быстро, чтобы его можно было оптимизировать с помощью JIT. Некоторые подробности того, когда JIT будет оптимизирован, можно найти здесь . Приведенный ниже код можно использовать для измерения средней задержки между двумя потоками путем чтения и записи изменяемых полей. Эти изменчивые поля интересны тем, что для соблюдения модели памяти Java требуются соответствующие аппаратные ограждения .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import static java.lang.System.out;
 
public class InterThreadLatency
{
    private static final int REPETITIONS = 100 * 1000 * 1000;
 
    private static volatile int ping = -1;
    private static volatile int pong = -1;
 
    public static void main(final String[] args)
        throws Exception
    {
        for (int i = 0; i < 5; i++)
        {
            final long duration = runTest();
 
            out.printf("%d - %dns avg latency - ping=%d pong=%d\n",
                       i,
                       duration / (REPETITIONS * 2),
                       ping,
                       pong);
        }
    }
 
    private static long runTest() throws InterruptedException
    {
        final Thread pongThread = new Thread(new PongRunner());
        final Thread pingThread = new Thread(new PingRunner());
        pongThread.start();
        pingThread.start();
 
        final long start = System.nanoTime();
        pongThread.join();
 
        return System.nanoTime() - start;
    }
 
    public static class PingRunner implements Runnable
    {
        public void run()
        {
            for (int i = 0; i < REPETITIONS; i++)
            {
                ping = i;
 
                while (i != pong)
                {
                    // busy spin
                }
            }
        }
    }
 
    public static class PongRunner implements Runnable
    {
        public void run()
        {
            for (int i = 0; i < REPETITIONS; i++)
            {
                while (i != ping)
                {
                    // busy spin
                }
 
                pong = i;
            }
        }
    }
}

Печать кода сборки

Можно напечатать весь сгенерированный код сборки с помощью следующего оператора.

1
java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly InterThreadLatency

Однако это может привести к тому, что вы не сможете увидеть лес за деревьями. Как правило, гораздо полезнее ориентироваться на конкретный метод. Для этого теста метод run () будет оптимизирован и дважды сгенерирован Hotspot. Один раз для версии OSR, а затем снова для стандартной версии JIT. Стандартная версия JIT следует.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
java -XX:+UnlockDiagnosticVMOptions '-XX:CompileCommand=print,*PongRunner.run' InterThreadLatency
 
Compiled method (c2)   10531    5             InterThreadLatency$PongRunner::run (30 bytes)
 total in heap  [0x00007fed81060850,0x00007fed81060b30] = 736
 relocation     [0x00007fed81060970,0x00007fed81060980] = 16
 main code      [0x00007fed81060980,0x00007fed81060a00] = 128
 stub code      [0x00007fed81060a00,0x00007fed81060a18] = 24
 oops           [0x00007fed81060a18,0x00007fed81060a30] = 24
 scopes data    [0x00007fed81060a30,0x00007fed81060a78] = 72
 scopes pcs     [0x00007fed81060a78,0x00007fed81060b28] = 176
 dependencies   [0x00007fed81060b28,0x00007fed81060b30] = 8
Decoding compiled method 0x00007fed81060850:
Code:
[Entry Point]
[Constants]
  # {method} 'run' '()V' in 'InterThreadLatency$PongRunner'
  #           [sp+0x20]  (sp of caller)
  0x00007fed81060980: mov    0x8(%rsi),%r10d
  0x00007fed81060984: shl    $0x3,%r10
  0x00007fed81060988: cmp    %r10,%rax
  0x00007fed8106098b: jne    0x00007fed81037a60  ;   {runtime_call}
  0x00007fed81060991: xchg   %ax,%ax
  0x00007fed81060994: nopl   0x0(%rax,%rax,1)
  0x00007fed8106099c: xchg   %ax,%ax
[Verified Entry Point]
  0x00007fed810609a0: sub    $0x18,%rsp
  0x00007fed810609a7: mov    %rbp,0x10(%rsp)    ;*synchronization entry
                                                ; - InterThreadLatency$PongRunner::run@-1 (line 58)
  0x00007fed810609ac: xor    %r11d,%r11d
  0x00007fed810609af: mov    $0x7ad0fcbf0,%r10  ;   {oop(a 'java/lang/Class' = 'InterThreadLatency')}
  0x00007fed810609b9: jmp    0x00007fed810609d0
  0x00007fed810609bb: nopl   0x0(%rax,%rax,1)   ; OopMap{r10=Oop off=64}
                                                ;*goto
                                                ; - InterThreadLatency$PongRunner::run@15 (line 60)
  0x00007fed810609c0: test   %eax,0xaa1663a(%rip)        # 0x00007fed8ba77000
                                                ;*goto
                                                ; - InterThreadLatency$PongRunner::run@15 (line 60)
                                                ;   {poll}
  0x00007fed810609c6: nopw   0x0(%rax,%rax,1)   ;*iload_1
                                                ; - InterThreadLatency$PongRunner::run@8 (line 60)
  0x00007fed810609d0: mov    0x74(%r10),%r9d    ;*getstatic ping
                                                ; - InterThreadLatency::access$000@0 (line 3)
                                                ; - InterThreadLatency$PongRunner::run@9 (line 60)
  0x00007fed810609d4: cmp    %r9d,%r11d
  0x00007fed810609d7: jne    0x00007fed810609c0
  0x00007fed810609d9: mov    %r11d,0x78(%r10)
  0x00007fed810609dd: lock addl $0x0,(%rsp)     ;*putstatic pong
                                                ; - InterThreadLatency::access$102@2 (line 3)
                                                ; - InterThreadLatency$PongRunner::run@19 (line 65)
  0x00007fed810609e2: inc    %r11d              ;*iinc
                                                ; - InterThreadLatency$PongRunner::run@23 (line 58)
  0x00007fed810609e5: cmp    $0x5f5e100,%r11d
  0x00007fed810609ec: jl     0x00007fed810609d0  ;*if_icmpeq
                                                ; - InterThreadLatency$PongRunner::run@12 (line 60)
  0x00007fed810609ee: add    $0x10,%rsp
  0x00007fed810609f2: pop    %rbp
  0x00007fed810609f3: test   %eax,0xaa16607(%rip)        # 0x00007fed8ba77000
                                                ;   {poll_return}
  0x00007fed810609f9: retq                      ;*iload_1
                                                ; - InterThreadLatency$PongRunner::run@8 (line 60)
  0x00007fed810609fa: hlt   
  0x00007fed810609fb: hlt   
  0x00007fed810609fc: hlt   
  0x00007fed810609fd: hlt   
  0x00007fed810609fe: hlt   
  0x00007fed810609ff: hlt   
[Exception Handler]
[Stub Code]
  0x00007fed81060a00: jmpq   0x00007fed8105eaa0  ;   {no_reloc}
[Deopt Handler Code]
  0x00007fed81060a05: callq  0x00007fed81060a0a
  0x00007fed81060a0a: subq   $0x5,(%rsp)
  0x00007fed81060a0f: jmpq   0x00007fed81038c00  ;   {runtime_call}
  0x00007fed81060a14: hlt   
  0x00007fed81060a15: hlt   
  0x00007fed81060a16: hlt   
  0x00007fed81060a17: hlt   
OopMapSet contains 1 OopMaps
 
#0
OopMap{r10=Oop off=64}

Интересное наблюдение

Выделенные красным цветом строки кода сборки очень интересны. Когда
поле volatile записывается, в соответствии с моделью памяти Java запись должна быть последовательно согласованной , т.е. не должна переупорядочиваться из-за обычно применяемых оптимизаций, таких как постановка записи в буфер хранилища . Это может быть достигнуто путем вставки соответствующих барьеров памяти. В вышеприведенном случае Hotspot выбрал принудительное упорядочение, выполнив инструкцию MOV (регистр по адресу памяти — т.е. запись), за которой следует инструкция LOCK ADD (без операции указатель стека в качестве идиомы ограждения), которая имеет семантику упорядочения. Это не идеально для процессора x86. То же действие можно было бы выполнить более эффективно и правильно с помощью одной инструкции LOCK XCHG для записи. Это заставляет меня задуматься о том, есть ли какие-то существенные компромиссы в JVM для того, чтобы сделать его переносимым на многие архитектуры, а не быть лучшим на x86.