Комментарии 30
Грубо говоря, NOP не выкидывается при JIT совершенно сознательно, т.к. у него по сути всего 2 применения:
1. Alignment для того, чтобы упростить захват критических секций при синхронизации
2. Placeholder для того, чтобы было, куда поставить breakpoint
В первом случае — это внесение сознательной микрозадержки для того, чтобы не провоцировать лишний lockup на инструкциях, которыми будет производиться захват, во втором — я так понимаю, его можно было бы выкинуть, оставив только в байткоде, но тогда пришлось бы вводить две разных инструкции NOP, чтобы их отличать.
1. Alignment для того, чтобы упростить захват критических секций при синхронизации
2. Placeholder для того, чтобы было, куда поставить breakpoint
В первом случае — это внесение сознательной микрозадержки для того, чтобы не провоцировать лишний lockup на инструкциях, которыми будет производиться захват, во втором — я так понимаю, его можно было бы выкинуть, оставив только в байткоде, но тогда пришлось бы вводить две разных инструкции NOP, чтобы их отличать.
НЛО прилетело и опубликовало эту надпись здесь
Не путаю, просто показать правдоподобный, а не искусственный пример будет достаточно сложно. Попробую перефразировать, а то и правда, видимо, создается впечатление, что я ляпнул первое, что пришло в голову, прочитав первые 2-3 абзаца из всей статьи.
В некоторых случаях применение конструкций типа synchronized породит nop в байткоде. В свою очередь в JIT-коде, если это место останется, там будет в x86_64, как правило, либо длинные nop'ами (nopl, nopw и т.п.), либо инструкциями «rep nop» (они же pause, они же F3 90). Делается это в надежде на то, что по этим инструкциям современные процессоры умеют понимать, что в этом месте происходит spinlock (активное ожидание) и неким образом оптимизировать выполнение тредов, которые крутятся в районе этого spinlock'а.
Показать пример можно легко, ну вот, скажем:
Только вот подобрать минимальный пример и наборы опций javac/java JIT для того, чтобы оно осталось и было четко видно, когда это происходит, будет довольно сложно.
В некоторых случаях применение конструкций типа synchronized породит nop в байткоде. В свою очередь в JIT-коде, если это место останется, там будет в x86_64, как правило, либо длинные nop'ами (nopl, nopw и т.п.), либо инструкциями «rep nop» (они же pause, они же F3 90). Делается это в надежде на то, что по этим инструкциям современные процессоры умеют понимать, что в этом месте происходит spinlock (активное ожидание) и неким образом оптимизировать выполнение тредов, которые крутятся в районе этого spinlock'а.
Показать пример можно легко, ну вот, скажем:
Скрытый текст
Разумеется, xchg %ax, %ax — это кусок того же nop.
0x00007f7e9ffd41e0: mov 0x8(%rsi),%r10d
0x00007f7e9ffd41e4: shl $0x3,%r10
0x00007f7e9ffd41e8: cmp %r10,%rax
0x00007f7e9ffd41eb: jne 0x00007f7e9ffada60 ; {runtime_call}
0x00007f7e9ffd41f1: xchg %ax,%ax ; <===========
0x00007f7e9ffd41f4: nopl 0x0(%rax,%rax,1) ; <===========
0x00007f7e9ffd41fc: xchg %ax,%ax ; <===========
0x00007f7e9ffd4200: mov %eax,-0x14000(%rsp)
0x00007f7e9ffd4207: push %rbp
0x00007f7e9ffd4208: sub $0x10,%rsp ;*synchronization entry
0x00007f7e9ffd420c: mov %rsi,%rbp
0x00007f7e9ffd420f: mov %r12d,0x10(%rsi) ;*putfield offset
Разумеется, xchg %ax, %ax — это кусок того же nop.
Только вот подобрать минимальный пример и наборы опций javac/java JIT для того, чтобы оно осталось и было четко видно, когда это происходит, будет довольно сложно.
И все-таки путаете.
- synchronized не порождает nop в байткоде;
- JIT иногда генерирует nop в машинном коде исключительно в целях выравнивания, а именно:
a) выравнивания backward branch targets в горячих циклах;
b) выравнивания точки входа в метод (т.н. verified entry) для возможности атомарного патчинга, поскольку запись машинного слова в память на x86 гарантировано атомарна только по выровненному адресу.
ой, где вы такое взяли?
Пожимаю руку: хороший анализ и правильные выводы. Многие после первого же теста сделали бы вывод, что байткод NOP выполняется N наносекунд :)
По вопросам:
1. Сложно представить, во что вообще JIT может скомпилировать NOP, кроме пустого места. Разве что в машинную инструкцию nop, на которую, в свою очередь, даже процессор не тратит ни такта.
2. Виртуальные методы ни при чем. Во-первых, в данном случае вызов будет девиртуализован. Во-вторых, размер таблицы виртуальных методов зависит только от количества методов, но не от их размера.
3. 8000 и 325 — размеры все-таки в байтах, а не в инструкциях. Почему именно такие, наверное, уже никто не вспомнит — за последние лет 7 они не менялись.
Кстати говоря, я на эти лимиты наталкивался в реальном коде. Исследуя, почему зверски тормозит GeoIP библиотека MaxMind, обнаружил, что в методе regionNameByCode есть огромный switch со всеми возможными вариантами регионов, в результате чего этот метод вообще никогда не компилировался.
По вопросам:
1. Сложно представить, во что вообще JIT может скомпилировать NOP, кроме пустого места. Разве что в машинную инструкцию nop, на которую, в свою очередь, даже процессор не тратит ни такта.
2. Виртуальные методы ни при чем. Во-первых, в данном случае вызов будет девиртуализован. Во-вторых, размер таблицы виртуальных методов зависит только от количества методов, но не от их размера.
3. 8000 и 325 — размеры все-таки в байтах, а не в инструкциях. Почему именно такие, наверное, уже никто не вспомнит — за последние лет 7 они не менялись.
Кстати говоря, я на эти лимиты наталкивался в реальном коде. Исследуя, почему зверски тормозит GeoIP библиотека MaxMind, обнаружил, что в методе regionNameByCode есть огромный switch со всеми возможными вариантами регионов, в результате чего этот метод вообще никогда не компилировался.
3. Меня настораживают «number of bytecode instructions» или «method size in bytecode instructions». Т.е. везде говорится именно про инструкции. Ни в одном месте не видел про байты. Хотя в исходниках jvm дальше не копался. Надо будет глянуть реально как эти константы используются.
НЛО прилетело и опубликовало эту надпись здесь
От Интела и взялось. ;)
Кажется где-то до кловертаунов (могу немного ошибаться с микроархитектурой) не было спец обработки NOP и таки он пересылал из eax в eax. Точнее не пересылал, а делал ренайминг, а так как внешний регистр тот же — то ничего не делал. Но в RS попадал и power кушал (перформанс не кушал). Сейчас исчезает уже после декодера.
Кажется где-то до кловертаунов (могу немного ошибаться с микроархитектурой) не было спец обработки NOP и таки он пересылал из eax в eax. Точнее не пересылал, а делал ренайминг, а так как внешний регистр тот же — то ничего не делал. Но в RS попадал и power кушал (перформанс не кушал). Сейчас исчезает уже после декодера.
Остается инкремент eip на современных процессорах.
Откуда пошла версия про xchg. Опкод 0x90 (nop), строго говоря, и означает xchg ax, ax. Так было начиная с первых x86 где-то до 586, после этого инструкция стала хендлится процессором специальным образом.
Откуда пошла версия про xchg. Опкод 0x90 (nop), строго говоря, и означает xchg ax, ax. Так было начиная с первых x86 где-то до 586, после этого инструкция стала хендлится процессором специальным образом.
Нет никакого инкремента IP. :)
Он означает «xchg ax, ax» только на 8086. На 80386 в 32битном режиме это уже «xchg eax, eax», а вот в 64-битном режиме это уже «NOP, просто NOP», потому что по всем канонам «xchg eax, eax» должна бы обнулить старшую половину rax (и «длинная версия» «87 c0» таки это и сделает). Странно видеть ситуацию когда инструкция получает специальную поддержку декодера, но при этом не выкидывается в дальнейшем, честно говоря, но врать не буду, может так оно и есть, я так глубоко я не копал.
Это закодирован он как exchange eax,eax, а выполняется — как удобнее процессору.
«Разве что в машинную инструкцию nop, на которую, в свою очередь, даже процессор не тратит ни такта.»
Ну это если у нас бедный одинокий NOP. :)
А вот если забить NOP-ами весь fetch line (16 bytes), то придется потратится. Немного если fetch line в iCache, и много в противном случае. ;)
PS Да, речь про Intel.
Ну это если у нас бедный одинокий NOP. :)
А вот если забить NOP-ами весь fetch line (16 bytes), то придется потратится. Немного если fetch line в iCache, и много в противном случае. ;)
PS Да, речь про Intel.
НЛО прилетело и опубликовало эту надпись здесь
Может, пригодится кому:
stas-blogspot.blogspot.de/2011/07/most-complete-list-of-xx-options-for.html
stas-blogspot.blogspot.de/2011/07/most-complete-list-of-xx-options-for.html
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Вовремя. Я уже думал на Caliper переходить из-за
@Param
. А планируется что-то типа казуального визуализатора по типу того же Caliper?А в чём по факту причина запрета JIT для больших методов? Только задержка на время компиляции? Или есть какие-то подводные камни?
Очень любопытная информация про 8000 вообще. Кажется, мы из-за этого теряем очень много производительности…
Очень любопытная информация про 8000 вообще. Кажется, мы из-за этого теряем очень много производительности…
Спасибо за статью, за JMH в Maven и за мотивацию наконец на него перейти.
что методы у нас виртуальные, значит они хранятся в таблице виртуальных методов.
В таблице хранятся только указатели на функции. Размер таблицы зависит лишь от количества методов и не зависит от самих методов.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Поиск причин странной производительности