Pull to refresh

Comments 30

Грубо говоря, NOP не выкидывается при JIT совершенно сознательно, т.к. у него по сути всего 2 применения:

1. Alignment для того, чтобы упростить захват критических секций при синхронизации
2. Placeholder для того, чтобы было, куда поставить breakpoint

В первом случае — это внесение сознательной микрозадержки для того, чтобы не провоцировать лишний lockup на инструкциях, которыми будет производиться захват, во втором — я так понимаю, его можно было бы выкинуть, оставив только в байткоде, но тогда пришлось бы вводить две разных инструкции NOP, чтобы их отличать.
UFO just landed and posted this here
Не путаю, просто показать правдоподобный, а не искусственный пример будет достаточно сложно. Попробую перефразировать, а то и правда, видимо, создается впечатление, что я ляпнул первое, что пришло в голову, прочитав первые 2-3 абзаца из всей статьи.

В некоторых случаях применение конструкций типа synchronized породит nop в байткоде. В свою очередь в JIT-коде, если это место останется, там будет в x86_64, как правило, либо длинные nop'ами (nopl, nopw и т.п.), либо инструкциями «rep nop» (они же pause, они же F3 90). Делается это в надежде на то, что по этим инструкциям современные процессоры умеют понимать, что в этом месте происходит spinlock (активное ожидание) и неким образом оптимизировать выполнение тредов, которые крутятся в районе этого spinlock'а.

Показать пример можно легко, ну вот, скажем:
Скрытый текст
  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 для того, чтобы оно осталось и было четко видно, когда это происходит, будет довольно сложно.
И все-таки путаете.
  1. synchronized не порождает nop в байткоде;
  2. 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 со всеми возможными вариантами регионов, в результате чего этот метод вообще никогда не компилировался.
3. Меня настораживают «number of bytecode instructions» или «method size in bytecode instructions». Т.е. везде говорится именно про инструкции. Ни в одном месте не видел про байты. Хотя в исходниках jvm дальше не копался. Надо будет глянуть реально как эти константы используются.
UFO just landed and posted this here
UFO just landed and posted this here
От Интела и взялось. ;)
Кажется где-то до кловертаунов (могу немного ошибаться с микроархитектурой) не было спец обработки NOP и таки он пересылал из eax в eax. Точнее не пересылал, а делал ренайминг, а так как внешний регистр тот же — то ничего не делал. Но в RS попадал и power кушал (перформанс не кушал). Сейчас исчезает уже после декодера.
Остается инкремент eip на современных процессорах.
Откуда пошла версия про xchg. Опкод 0x90 (nop), строго говоря, и означает xchg ax, ax. Так было начиная с первых x86 где-то до 586, после этого инструкция стала хендлится процессором специальным образом.
Нет никакого инкремента IP. :)
ОК :) если уж совсем строго, то да, инкремент epi происходит раньше, сразу после получения инструкции, и nop просто съедает цикл процессора.
Даже цикла не съедает, за счет out-of-order. Съедается только время на чтение из памяти, и то один раз.
Надо будет почитать как out-of-order работает. Спасибо за уточнение.
Он означает «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.
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
Вовремя. Я уже думал на Caliper переходить из-за @Param. А планируется что-то типа казуального визуализатора по типу того же Caliper?
UFO just landed and posted this here
А в чём по факту причина запрета JIT для больших методов? Только задержка на время компиляции? Или есть какие-то подводные камни?

Очень любопытная информация про 8000 вообще. Кажется, мы из-за этого теряем очень много производительности…
UFO just landed and posted this here
Спасибо за статью, за JMH в Maven и за мотивацию наконец на него перейти.
что методы у нас виртуальные, значит они хранятся в таблице виртуальных методов.

В таблице хранятся только указатели на функции. Размер таблицы зависит лишь от количества методов и не зависит от самих методов.
Sign up to leave a comment.

Articles