Иногда при профилировании Java-приложения необходимо понимать код сборки, сгенерированный JIT-компилятором Hotspot. Это может быть полезно при определении того, какие решения по оптимизации приняты и как изменения нашего кода могут повлиять на сгенерированный код сборки. Иногда полезно также знать, какие инструкции выдаются при отладке параллельного алгоритма, чтобы гарантировать, что правила видимости были применены, как и ожидалось. Таким образом, я обнаружил довольно много ошибок в различных JVM.
Этот блог иллюстрирует, как установить плагин дизассемблера, и предоставляет параметры командной строки для нацеливания на определенный метод.
Установка
Ранее необходимо было получить отладочную сборку для печати кода сборки, сгенерированного JIT Hotspot для JVM Oracle / SUN. Начиная с Java 7 стало возможным печатать сгенерированный код сборки, если подключаемый модуль дизассемблера установлен в стандартной JVM Oracle Hotspot. Чтобы установить плагин для 64-битного Linux, выполните следующие действия:
- Загрузите соответствующий двоичный файл или выполните сборку из исходного кода по адресу https://kenai.com/projects/base-hsdis/downloads.
- В Linux переименуйте linux-hsdis-amd64.so в libhsdis-amd64.so
- Скопируйте общую библиотеку в $ 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' InterThreadLatencyCompiled 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] = 8Decoding 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.