Лог компиляции что это
Регистрация: 20.08.2005
Если пользоваться «Администрирование/Периодические операции/Глобальная компиляция», то там есть код, который сохраняет отчет о компиляции (папка «Мои документы» пользователя, под которым вошли в Axapta, имя файла «AxCompileAll.html»).
Тут есть пара но.
1. Если в параметрах пользователя для компилятора установить носитель «Окно сообщений», то сохраняться ничего не будет (в этом случае infoLog.compilerOutput() возвращает NULL). Это касается так же и кода Максима
2. Если запускать глобальную компиляцию из контрольного списка, появляющегося при старте Axapta’ы, то так же не будет сохраняться
__________________
Axapta v.3.0 sp5 kr2
Удобный лог не роскошь, а средство отладки, или как подключить dll при помощи h файла

Не один программист, приступая к разработке приложения, не проходит мимо вопроса о логах. Вроде бы простой вопрос, но перебирая уже существующие варианты, понимаешь, что в каждом что-то неудобно: нет run-time отключения лога (только при компиляции), иногда нужно перенаправить лог в файл, иногда в communication port или еще куда-нибудь и т.д. и т.п. Писать полноценный вариант не хватает времени, а создавать наспех еще одну реализацию — рука не поднимается. И получается, как говорится, сапожник без сапог, даже еще хуже, ведь логи это инструмент разработки… А что если подойти к этому вопросу не спеша? Как разработчику мне бы хотелось видеть инструмент отладки таким:
- Легким и простым в использовании — чтобы можно было по умолчанию включить один h файл в проект и все заработало будь то старое или новое приложение.
- Расширяемым — чтобы добавив один h файл в проект, можно было нарастить функциональность настолько, насколько вам необходимо, не затрагивая при этом самого приложения (ведь часто приложение уже работает у клиента и трогать его не желательно).
- Конфигурируемым в полном объеме — разработчик в отличии от пользователя должен контролировать инструмент разработки в полной мере.
Расширяемость
Один из основных принципов расширяемости, это обеспечение максимальных возможностей изменения, при сведении к минимуму воздействия на существующие функции системы. Во первых, это означает, что если мы хотим сделать лог расширяемым, мы должны сделать из него систему, т.е. отделить его от приложения. Таким механизмом в windows являются dynamic link library: приложению все равно с какой библиотекой оно работает, если библиотека предоставляет необходимый интерфейс. Т.е. все требования к библиотеке, сводятся к требованиям к интерфейсу. Расширяемости интерфейса можно добиться, используя механизм интерфейсов с++ (на этом построена Component Object Model). Для этого в dll нужно определить всего две функции:
int GetLogInterfaceVersion(); ILog* CreateLogObject();
Где ILog является требуемым интерфейсом и определяется как:
interface ILog < virtual void Log( unsigned int messageId, char *fmt, . ) = 0; >;
В случае если, нам необходимо добавить новую функцию в наш интерфейс:
interface ILog < virtual void Log( unsigned int messageId, char *fmt, . ) = 0; virtual void RedirectLog( void (*log) (char *)) = 0; >;
Нам нужно просто последовательно добавить ее в интерфейс и увеличить версию интерфейса, при этом старые приложения будут совместимы с новой версией библиотеи. Таким образом, вся функциональность логера скрывается за абстрактным интерфейсом ILog и что делает функция Log — пишет данные в файл или во flash память приложению совершенно все равно.
Во вторых, если мы хотим сделать лог расширяемым, мы должны организовать его внутреннюю структуру, таким образом, чтобы добавление новой, не заставляло нас менять уже существующей функциональности внутри библиотеки. Для этого нужно разделить изменяемую и неизменяемую функциональность. Показано, что для логов в с++, это хорошо удается сделать, применяя для неизменяемой части шаблоны классов, которые принимают в качестве параметров стратегии — классы, которые реализуют изменяемую часть. Причем, стратегией может быть не только стратегия вывода в лог (LogPolicy), но и конфигурирование (LogConfigPolicy), так как ее тоже легко можно параметризовать:
template < class LogConfigPolicy, class LogPolicy >class TLog : public LogConfigPolicy, public LogPolicy, public ILog < TLog() : LogConfigPolicy(), LogPolicy( this ) // стратегия конфигурирования имеет фиксированный интерфейс и передается в стратегию вывода в лог < defaultFilterLevel = LOG_DEBUG; if( GetString("common","filterLevel",out,sizeof(out),"debug") ) // GetString – метод стратегии конфигурирования, режим вывод в лог может быть, например, прочитан из файла или из реестра … >;
Таким образом можно легко менять не только то, куда мы выводим лог, но и откуда читается конфигурация из реестра или из файла. Функция CreateLogObject будет выглядеть следующим образом:
ILog* CreateLogObject() < try < #if defined(LOG_REG_CONFIG_POLICY) && defined(LOG_DEBUG_POLICY) return new TLog< LogRegConfigPolicy, LogDebugPolicy >(); #elif defined(LOG_REG_CONFIG_POLICY) && defined(LOG_FILE_POLICY) return new TLog< LogFileConfigPolicy, LogFilePolicy >(); #elif defined(LOG_FILE_CONFIG_POLICY) && defined(LOG_DEBUG_POLICY) return new TLog< LogRegConfigPolicy, LogDebugPolicy >(); #elif defined(LOG_FILE_CONFIG_POLICY) && defined(LOG_FILE_POLICY) return new TLog< LogFileConfigPolicy, LogFilePolicy >(); #else #error Log policies weren't defined #endif > catch(. ) < return NULL; >>
Теперь, комбинируя определения препроцессора можно получить библиотеку логера log.dll с теми или иными свойствами.
Простота использования
Загрузить dll в приложение не такая уж сложная задача, но если это делать в каждом проекте, это может и надоесть, а главное, в уже существующих приложениях маловероятно, чтобы кто-то при использовании лога, пользовался библиотекой… А можно ли сделать это автоматически? Очевидно, что нужны какие-то средства языка. Паттерн Singleton — первое, что напрашивается для реализации лога (при этом dll загружается в конструкторе Singleton). Необходимость использования данного паттерна диктуется не только удобством, но и необходимостью, ведь, обращаться к логу могут и глобальные объекты, а порядок их создания не определен. Попытка реализовать Singleton в виде шаблона класса со стратегиями, привела меня к интересному факту — инстанционирование статической переменной класса производится в h файле! Получается, что весь логер, на стороне приложения, можно реализовать всего в одном h файле, не добавляя ни cpp ни lib:
template class TSingleton: public CreatePolicy < private: static TSingleton *instance; TSingleton():CreatePolicy()<>TSingleton( const TSingleton& )<> TSingleton& operator=( TSingleton& )<> virtual ~TSingleton()<> public: static MainInterface* GetSingletonObject() < if(!instance) < NamedMutexObject mutex( CreatePolicy::GetMutexName() ); if(!instance) instance = new TSingleton(); >return instance->mainInterface; > static void ReleaseSingletonObject() < if(instance) < NamedMutexObject mutex( CreatePolicy::GetMutexName() ); if(instance) < delete instance; instance = NULL; >> > >; template TSingleton < CreatePolicy, MainInterface, NamedMutexObject >* TSingleton< CreatePolicy, MainInterface, NamedMutexObject >::instance = 0;
CreatePolicy — стратегия, которая, в нашем случае, загружает библиотеку log.dll и создает объект логера, сохраняя его интерфейс в mainInterface. Интерфейс MainInterface, это ILog, NamedMutexObject — это объект синхронизации, используемый при создании объекта instance. Макрос записи ошибки в лог будет выглядеть следующим образом:
#define log_err(fmt. ) TSingleton::GetSingletonObject()->Log( LOG_ERROR, fmt, ##__VA_ARGS__)
Таким образом, приложение example.cpp, использующее данный логер, будет иметь вид:
#include "log.h" int main(int argc, char* argv[])
Команда сборки будет выглядеть следующим образом:
Конфигурирование
Полнота возможностей конфигурирования обеспечивается принципом предоставления разработчику возможности конфигурирования на всех уровнях, будь, то уровень компиляции или run-time.
Настройки компилятора определяют, включен ли, тот или иной кусок кода библиотеки или приложения. Как было показано, для библиотеки логера log.dll (см. реализацию функции CreateLogObject) настройки определяют, какие стратегии используются в шаблонном классе логера. Таким образом, создавая необходимые стратегии для логера и комбинируя их при помощи настрек компилятора, можно реализовать в библиотеке логера необходимую функциональность, не изменяя при этом уже существующий код.
Для приложения настройки компилятора определены таким образом, что программисту не надо добавлять ни каких настроек в приложение, чтобы лог заработал:
- LOG_DISABLE — запрещает вывод в лог всех типов сообщений;
- LOG_DISABLE_INFO — запрещает сообщения информационного типа;
- LOG_DISABLE_WARNING — запрещает сообщения о предупреждениях,
- LOG_DISABLE_ERROR — запрещает сообщения об ошибках.
[common]
filterLevel = info
Теперь, когда программисту даны все возможности по отладке, а если их нет то их легко добавить, можно приступать к разработке самого приложения, ведь, добавив всего один h файл к приложению, вы сможете потом легко изменить лог, не меняя самого приложения.
- c++
- программирование
- разработка под windows
- Программирование
- C++
- Разработка под Windows
Лог компиляции что это
Здесь будет описана краткая информация о тестирующей системе fsystem, которую мы используем на сайте.
Для того, чтобы попасть в тестирующую систему проще всего воспользоваться этой прямой ссылкой (раз уж вы здесь). При входе требуется указать логин и пароль. Они совпадают с логином и паролем собственно на сайте. Также при входе в систему можно выбрать нужный контест. Если при получении логина вам было сказано выбирать контест при входе, сделать это обязательно. Если вы регистрировались через сайт — этого делать не нужно. На экране входа будут отображены все контесты в системе, не убранные в архив. Для того, чтобы иметь возможность попасть в какой-либо контест, на него надо зарегистрироваться (на странице регистрации). Если вы не зарегистрированы на контест, но тем не менее выберете его, ничего не произойдет, увы.
Заметьте, что для корректной работы тестирующей системы требуется включить javascript в вашем браузере.
После входа в тестирующую систему в левой части будет меню, в котором представлены основные действия, которые вы можете сделать. Ссылки из левого меню мы рассмотрим чуть позже. В правой верхней части страницы расположена информация о логине, под которым вы вошли в систему, времени на сервере и контесте, который у вас сейчас выбран. Все действия, которые вы делаете в системе выполняются по умолчанию в том контесте, который у вас выбран (и соответственно отображается справа вверху). Если вы отправляете решение задачи на проверку, оно отправляется в выбранный контест. Если смотрите таблицу результатов — то тоже. Выбранный контест можно сменить, кликнув по его названию.
Теперь о ссылках в левом меню.
«Информация о контесте» -на этой странице отображается время начала и завершения контеста. Также там представлен список задач с информацией об ограничениях по времени и памяти, входных и выходных файлах, ссылками на условия и отправку решения задачи. Если контест виртуальный, а вы на него зарегистрированы, но еще не участвовали, на этой странице появляется кнопка «Начать виртуальный контест». После ее нажатия, контест для вас начнётся, потому не стоит ей пользоваться, если вы не уверены в том, что хотите порешать. Также на этой странице есть ссылка на условия контеста целиком. Иногда может случиться так, что они недоступны, но тем не менее доступны условия отдельных задач. Такое бывает, это значит что составитель контеста посчитал отдельные условия для каждой задачи вполне достаточными.
«Отправить решение» — на этой странице находится форма для отправки решения на проверку. В ней вы можете выбрать задачу (если задач в контесте много, то вместо списка для выбора будет поле, куда нужно вписать ID задачи в контесте). ID задачи в контесте можно посмотреть на странице «Информация о контесте» либо непосредственно в условиях задач. Также вы можете выбрать компилятор, а также прицепить файл с задачей для отправки либо скопировать исходный код. Будьте аккуратны с решениями на Java. Если вы отправляете решение методом загрузки файла, главный класс должен называться так же, как и файл с исходным кодом (то есть отправляя Main.java убедитесь, что основной класс называется Main). Если же вы отправляете решение на Java копируя код в поле отправки, основной класс должен называться solution.
«Результаты тестирования» — на этой странице отображаются решения, отосланные вами и результаты их проверки. Они представлены в виде таблицы:
| ID | Идентификатор посылки в системе |
| Login | Login отправившего пользователя |
| Team | Псевдоним пользователя в контесте |
| Contest | ID контеста |
| Time | Время отправки (в зависимости от правил оценки контеста либо в формате прошедшего времени с начала контеста либо просто дата и время отправки) |
| Problem | ID задачи в контесте |
| Compiler | ID компилятора |
| Result | Код результата проверки (о них ниже) |
| Test# | Номер теста (сообщается для некоторых кодов) |
| Run time | Использованное процессорное время |
| Memory | Использованная память |
| Log | Ссылка на протокол тестирования (доступен в IOI-контестах, а также если вы получили Compilation Error, в таком случае содержит вывод компилятора) |
| Src | Ссылка для просмотра исходного кода решения. |
Коды результата проверки бывают следующими:
| Код | Расшифровка | Описание |
|---|---|---|
| OK | Принято | Решение принято. В случае ACM-контестов это означает, что вы сдали на одну задачу больше. В случае IOI-контестов это означает, что решение прошло предварительные тесты и принято на основное тестирование. |
| WA | Неверный ответ | Неверный ответ. Ваше решение выдает ответ, являющийся неверным по условию задачи. Например, вместо 10 выведено 11. |
| PE | Ошибка вывода | Ошибка представления. Проверяющая программа не смогла понять, что же вы вывели. Это бывает, если вывести вместо числа строку, вместо целого числа — вещественно или вместо чего угодно ничего не вывести. |
| RE | Ошибка времени выполнения | Ошибка времени выполнения. Ваша программа завершилась с ошибкой. Это бывает, если вы делите на ноль, извлекаете корень из отрицательных чисел, переполняете стек, пользуетесь неверными указателями или выходите из программы с ненулевым кодом возврата. |
| TL | Превышено ограничение по времени | Превышение ограничения по времени. Ваша программа работает слишком долго, зацикливается, бесконечно долго ожидает ввода данных итп. |
| ML | Превышено ограничение по памяти | Превышение ограничения по памяти. Ваша программа пытается использовать слишком большой объём памяти. Заметьте, что если вы попробуете выделить памяти больше, чем в адресном пространстве одного процесса (2Гб), вы получите Run-time Error, а не Memory Limit. |
| CE | Ошибка компиляции | Ошибка компиляции. Ваш исходный код не компилируется выбранным компилятором. В этом случае ссылка «Log» на странице результатов тестирования отображает вывод компилятора и вы можете отловить ошибку. |
| CO | Компилируется | Ваше решение находится в процессе компиляции. |
| RU | Выполняется | Ваше решение тестируется. В столбце с номером теста отображается, на каком именно тесте оно сейчас тестируется. |
| NT | Не протестировано | Ваше решение ожидает начала своего тестирования. |
| FT | Сбой тестирования | Во время тестирования вашего решения произошла какая-то ошибка. Это происходит либо при сбоях системы, либо при ошибках в проверяющих программах. При получении этого кода разумно сообщить администратору/вашему преподавателю. |
| QU | В очереди | Ваше решение находится в очереди на компилирование и тестирование. |
| OB | Устарело | Этот код встречается в игровых контестах. Он означает, что после отправки этого решения вы отправили другое решение по этой задаче, а стало быть это решение тестироваться уже не будет. |
«Положение команд» — на этой странице вы можете видеть текущее положение команд в контесте (с учётом именно вашего времени в виртуальных контестах). В контестах, оцениваемых по правилам ProblemSet названия участников контеста являются ссылками на страницу статистики этого участника. В контестах, оцениваемых по правилам ACM и IOI можно использовать кнопки клавиатуры «Left», «Right», «Space», «Home» и «End» для простмотра истории контеста.
«Сообщения» — на этой странице отображаются сообщения от системы и администраторов контеста. Также вы можете задать вопрос, отправив сообщение администратору. Если у вас есть непрочитанные сообщения, рядом со ссылкой в скобках будет отображено их количество. Во время олимпиад не забывайте периодически проверять сообщения.
«Распечатать» — на олимпиадах, где разрешена печать, с этой страницы можно будет распечатывать свои решения.
«Настройки» — здесь вы можете изменить свой пароль, предпочитаемый компилятор и тему оформления. При смене темы оформления после сохранения настроек пройдите на любую страницу для применения новой темы. Оформление меняется только в тестирующей системе, на сайте такая возможность не предусмотрена.
«Выход» — выход из тестирующей системы. Не забывайте выходить из системы, когда у вас закончилось занятие.
Algo & DMA: технологии биржевой торговли
Биржевые технологии, алгоритмическая торговля, Java и программирование

Как посмотреть, что и как компилирует JIT-компилятор?
В данной статье я кратко описываю диагностические флаги JVM HotSpot, с помощью которых можно посмотреть на результаты работы JIT-компиляторов JVM. Эта информация поможет в оптимизации приложения для low-latency и в поиске узких мест.
PrintCompilation
Чтобы посмотреть, как и когда JIT-компилятор JVM компилирует код, надо запустить приложение с флагом
-XX:+PrintCompilation
Через определенный промежуток времени в консоли вы увидите что-то примерно такое:
139 1 3 java.lang.String::charAt (25 bytes) 140 2 3 java.lang.StringLatin1::charAt (28 bytes) 184 4 3 java.lang.Object:: (1 bytes) 184 6 3 java.lang.StringLatin1::hashCode (42 bytes) 185 5 3 java.lang.String::isLatin1 (19 bytes) 185 3 3 java.lang.String::hashCode (60 bytes) 185 7 3 java.util.ImmutableCollections$SetN::probe (56 bytes) 186 8 3 java.lang.Math::floorMod (20 bytes) 186 10 3 java.util.ImmutableCollections$SetN::hashCode (46 bytes) 186 9 3 java.lang.String::equals (50 bytes) 186 11 3 java.util.Objects::equals (23 bytes)
Эти строки говорят, какой метод какого класса был обработан JIT-компилятором.
Когда код хорошо прогрет, т.е. весь «горячий код» обработан JIT-компилятором и превращен в машинный нативный код, эти строки перестают появляться в консоли. Это служит хорошим сигналом, что ваше приложение достигло «нирваны» и работает на полной скорости.
Подробный лог компиляции
Если требуется более подробный лог процесса компиляции, вам поможет флаг
-XX:+UnlockDiagnosticVMOptions -XX:+LogCompilation
Этот флаг — диагностический и для его включения требуется дополнительный флаг -XX:+UnlockDiagnosticVMOptions. При запуске приложения с этим флагом, логи компиляции будут писаться в лог-файл, который по умолчанию будет называться
hotspot_pidNNNNNN.log
где NNNNNN — это цифры идентификатора Java-процесса. Название лог-файла можно указать в дополнительном параметре:
-XX:LogFile=foo.log
Лог-файл компиляции представляет собой XML-файл весьма внушительного размера, который лучше всего смотреть с помощью специальной утилиты JITWatch, которую я упоминаю в отдельной статье.
PrintInlining
Еще один флаг, позволяющий проследить оптимизации, выполняемые компилятором, называется
-XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining
При запуске приложения с этим флагом вы увидите в консоли примерно следующее:
@ 1 java.lang.String::isLatin1 (19 bytes) inline @ 12 java.lang.StringLatin1::charAt (28 bytes) inline @ 15 java/lang/StringIndexOutOfBoundsException:: (not loaded) not inlineable@ 21 java.lang.StringUTF16::charAt (11 bytes) inline @ 2 java.lang.StringUTF16::checkIndex (9 bytes) inline @ 2 java.lang.StringUTF16::length (5 bytes) inline @ 5 java.lang.String::checkIndex (48 bytes) callee is too large @ 7 java.lang.StringUTF16::getChar (60 bytes) intrinsic @ 17 java.lang.String::isLatin1 (19 bytes) inline
Это записи о том, какие методы компилятор заинлайнил, а какие — нет. Как вы заметили, не вес методы были заинлайнены. Некоторые методы оказались для компилятора слишком большими и он их игнорирует. Вы можете задать компилятору максимальный размер метода, который он должен инлайнить флагами:
-XX:C1MaxInlineSize=900 -XX:MaxInlineSize=900
где число — это размер в байтах. В данном примере я задал макс. размер метода для инлайнинга — 900 байт для компилятора C1 и для компилятора C2.
Машинный код
Еще один диагностический флаг
-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly
позволяет увидеть машинный код, генерируемый JIT-компилятором.
Флаг PrintAssembly был добавлен в Java 6. Версии Java до 6-й его не узнают и не признают. Для распечатки машинного кода требовалась библиотека hsdis. Начиная с Java 9 кое-какой код выводится без hsdis, но вывод сопровождается строкой:
Could not load hsdis-amd64.dll; library not loadable; PrintAssembly is disabled
При запуске приложения с этим флагом в консоли вы увидите нечто такое:
============================= C2-compiled nmethod ============================== ----------------------------------- Assembly ----------------------------------- Compiled method (c2) 574 45 % 4 Demo::main @ 10 (40 bytes) total in heap [0x0000027787ad4190,0x0000027787ad45b0] = 1056 relocation [0x0000027787ad42f0,0x0000027787ad4320] = 48 main code [0x0000027787ad4320,0x0000027787ad4400] = 224 stub code [0x0000027787ad4400,0x0000027787ad4438] = 56 oops [0x0000027787ad4438,0x0000027787ad4440] = 8 metadata [0x0000027787ad4440,0x0000027787ad4458] = 24 scopes data [0x0000027787ad4458,0x0000027787ad44b8] = 96 scopes pcs [0x0000027787ad44b8,0x0000027787ad4568] = 176 dependencies [0x0000027787ad4568,0x0000027787ad4570] = 8 handler table [0x0000027787ad4570,0x0000027787ad45a0] = 48 nul chk table [0x0000027787ad45a0,0x0000027787ad45b0] = 16 [Constant Pool (empty)] [MachCode] [Verified Entry Point] # 'main' '([Ljava/lang/String;)V' in 'Demo' 0x0000027787ad4320: cc66 6666 | 0f1f 8400 | 0000 0000 | 6666 6690 | 8984 2400 | 90ff ff55 | 4883 ec40 | 488b 6a08 0x0000027787ad4340: 448b 2a48 | 8bca 49ba | 607f 2915 | fe7f 0000 | 41ff d244 | 8b5d 0866 | 0f1f 8400 | 0000 0000 0x0000027787ad4360: ;0x0000027787ad4360: 4181 fbf2 | be01 000f | 8547 0000 | 0048 896c | 2420 418b | edeb 2366 | 0f1f 8400 0x0000027787ad437c: ;*iload_2 ; - Demo::main@10 (line 6)
Для нетренированного глаза эти строки выглядят как китайская грамота. -XX:+PrintAssembly распечатывает ВСЁ, но смотреть ВЕСЬ машинный код ВСЕХ методов ВСЕГО приложения не имеет большого смысла. Чтобы увидеть машинный код только критического участка, надо воспользоваться другим диагностическим флагом-параметром:
-XX:CompileCommand
Например, я хочу посмотреть машинный код метода addNumbers в моем классе Demo. Параметр запуска будет выглядеть так:
-XX:CompileCommand=print,Demo.addNumbers
Метод вызывается 1 миллион раз. Таким образом, его сначала его компилирует компилятор C1, а затем — компилятор C2. В результате в консоли я увижу:
CompileCommand: print Demo.addNumbers ============================= C1-compiled nmethod ============================== ----------------------------------- Assembly ----------------------------------- Compiled method (c1) 65 73 3 Demo::addNumbers (6 bytes) total in heap [0x000001c3ec2e9b10,0x000001c3ec2e9de8] = 728 relocation [0x000001c3ec2e9c70,0x000001c3ec2e9c98] = 40 main code [0x000001c3ec2e9ca0,0x000001c3ec2e9d40] = 160 stub code [0x000001c3ec2e9d40,0x000001c3ec2e9d78] = 56 oops [0x000001c3ec2e9d78,0x000001c3ec2e9d80] = 8 metadata [0x000001c3ec2e9d80,0x000001c3ec2e9d88] = 8 scopes data [0x000001c3ec2e9d88,0x000001c3ec2e9da0] = 24 scopes pcs [0x000001c3ec2e9da0,0x000001c3ec2e9de0] = 64 dependencies [0x000001c3ec2e9de0,0x000001c3ec2e9de8] = 8 [Constant Pool (empty)] [MachCode] [Verified Entry Point] # 'addNumbers' '(II)I' in 'Demo' # parm0: rdx = int # parm1: r8 = int # [sp+0x40] (sp of caller) 0x000001c3ec2e9ca0: 8984 2400 | 90ff ff55 | 4883 ec30 0x000001c3ec2e9cac: ;0x000001c3ec2e9cac: 48b8 5006 | e3fe c301 | 0000 8bb0 | 0401 0000 | 83c6 0289 | b004 0100 | 0081 e6fe | 0700 0083 0x000001c3ec2e9ccc: fe00 0f84 0x000001c3ec2e9cd0: ;*iload_0 ; - Demo::addNumbers@0 (line 15) 0x000001c3ec2e9cd0: 1600 0000 | 4103 d048 | 8bc2 4883 | c430 5d4d | 8b97 1001 0x000001c3ec2e9ce4: ; 0x000001c3ec2e9ce4: 0000 4185 0x000001c3ec2e9ce8: ; 0x000001c3ec2e9ce8: 02c3 49ba | 7804 e3fe | c301 0000 | 4c89 5424 | 0848 c704 | 24ff ffff 0x000001c3ec2e9d00: ; 0x000001c3ec2e9d00: ffe8 7a5e 0x000001c3ec2e9d04: ; ImmutableOopMap <> ;*synchronization entry ; - Demo::addNumbers@-1 (line 15) 0x000001c3ec2e9d04: b4ff ebcc | 9090 498b | 8700 0400 | 0049 ba00 | 0000 0000 | 0000 004d | 8997 0004 | 0000 49ba 0x000001c3ec2e9d24: 0000 0000 | 0000 0000 | 4d89 9708 | 0400 0048 | 83c4 305d 0x000001c3ec2e9d38: ; 0x000001c3ec2e9d38: e943 f5b3 | fff4 f4f4 [Exception Handler] 0x000001c3ec2e9d40: ; 0x000001c3ec2e9d40: e83b 28b4 0x000001c3ec2e9d44: ; 0x000001c3ec2e9d44: ff48 b9e0 | eaa5 15fe | 7f00 0048 0x000001c3ec2e9d50: ; 0x000001c3ec2e9d50: 83e4 f049 | baa0 ae76 | 15fe 7f00 | 0041 ffd2 0x000001c3ec2e9d60: ; 0x000001c3ec2e9d60: f449 ba61 | 9d2e ecc3 | 0100 0041 0x000001c3ec2e9d6c: ; 0x000001c3ec2e9d6c: 52e9 2e42 | a9ff f4f4 | f4f4 f4f4 [/MachCode] ============================= C2-compiled nmethod ============================== ----------------------------------- Assembly ----------------------------------- Compiled method (c2) 70 80 4 Demo::addNumbers (6 bytes) total in heap [0x000001c3f3805990,0x000001c3f3805bc0] = 560 relocation [0x000001c3f3805af0,0x000001c3f3805af8] = 8 main code [0x000001c3f3805b00,0x000001c3f3805b40] = 64 stub code [0x000001c3f3805b40,0x000001c3f3805b58] = 24 oops [0x000001c3f3805b58,0x000001c3f3805b60] = 8 metadata [0x000001c3f3805b60,0x000001c3f3805b68] = 8 scopes data [0x000001c3f3805b68,0x000001c3f3805b78] = 16 scopes pcs [0x000001c3f3805b78,0x000001c3f3805bb8] = 64 dependencies [0x000001c3f3805bb8,0x000001c3f3805bc0] = 8 [Constant Pool (empty)] [MachCode] [Verified Entry Point] # 'addNumbers' '(II)I' in 'Demo' # parm0: rdx = int # parm1: r8 = int # [sp+0x20] (sp of caller) 0x000001c3f3805b00: 4881 ec18 | 0000 0048 0x000001c3f3805b08: ;*synchronization entry ; - Demo::addNumbers@-1 (line 15) 0x000001c3f3805b08: 896c 2410 | 8bc2 4103 0x000001c3f3805b10: ;*iadd ; - Demo::addNumbers@2 (line 15) 0x000001c3f3805b10: c048 83c4 | 105d 4d8b | 9710 0100 0x000001c3f3805b1c: ; 0x000001c3f3805b1c: 0041 8502 | c3f4 f4f4 | f4f4 f4f4 | f4f4 f4f4 | f4f4 f4f4 | f4f4 f4f4 | f4f4 f4f4 | f4f4 f4f4 0x000001c3f3805b3c: f4f4 f4f4 [Exception Handler] 0x000001c3f3805b40: ; 0x000001c3f3805b40: e93b 3d62 | f8e8 0000 | 0000 4883 0x000001c3f3805b4c: ; 0x000001c3f3805b4c: 2c24 05e9 | 4c84 57f8 | f4f4 f4f4 [/MachCode]
Я запускал свое приложение на JDK 15 на Intel Core i-5-6400T. У вас машинный код может получиться другой.
Ассемблерный код
Все равно, выдаваемый результат не имеет никакого смысла. Ведь все, что мы видим на экране это hex-коды машинного кода с адресами памяти. Для того, чтобы преобразовать их в ассемблерные инструкции, требуется дополнительная библиотека hsdis. Эта библиотека поставляется в исходниках jdk. Она не распространяется отдельно. Ее можно собрать самостоятельно для windows или для linux. Если поместить эту библиотеку в bin папку jdk, и запустить программу с вышеуказанными параметрами, на экране выведется полноценный читаемый ассемблерный код с комментариями. Например,
0x0000025ed5024723: mov rcx,QWORD PTR [r15+0x348] ; ImmutableOopMap ;*if_icmplt ; - (reexecute) Main::intStride1@22 (line 19) 0x0000025ed502472a: test DWORD PTR [rcx],eax ; 0x0000025ed502472c: cmp ebx,r11d 0x0000025ed502472f: jge 0x0000025ed5024786 0x0000025ed5024735: mov ecx,r11d 0x0000025ed5024738: sub ecx,ebx 0x0000025ed502473a: cmp r11d,ebx 0x0000025ed502473d: cmovl ecx,r8d 0x0000025ed5024741: cmp ecx,0x1f40 0x0000025ed5024747: cmova ecx,r9d 0x0000025ed502474b: add ecx,ebx 0x0000025ed502474d: data16 xchg ax,ax 0x0000025ed5024750: add r12,QWORD PTR [rdi+rbx*8+0x10] 0x0000025ed5024755: movsxd rdx,ebx 0x0000025ed5024758: add r12,QWORD PTR [rdi+rdx*8+0x18] 0x0000025ed502475d: add r12,QWORD PTR [rdi+rdx*8+0x20] 0x0000025ed5024762: add r12,QWORD PTR [rdi+rdx*8+0x28] 0x0000025ed5024767: add r12,QWORD PTR [rdi+rdx*8+0x30] 0x0000025ed502476c: add r12,QWORD PTR [rdi+rdx*8+0x38] 0x0000025ed5024771: add r12,QWORD PTR [rdi+rdx*8+0x40] 0x0000025ed5024776: add r12,QWORD PTR [rdi+rdx*8+0x48] 0x0000025ed502477b: add ebx,0x8 0x0000025ed502477e: xchg ax,ax 0x0000025ed5024780: cmp ebx,ecx 0x0000025ed5024782: jl 0x0000025ed5024750 0x0000025ed5024784: jmp 0x0000025ed5024723 0x0000025ed5024786: cmp ebx,r10d 0x0000025ed5024789: jge 0x0000025ed5024798 0x0000025ed502478b: nop 0x0000025ed502478c: add r12,QWORD PTR [rdi+rbx*8+0x10] 0x0000025ed5024791: inc ebx 0x0000025ed5024793: cmp ebx,r10d 0x0000025ed5024796: jl 0x0000025ed502478c 0x0000025ed5024798: mov edx,0xffffff45 0x0000025ed502479d: mov rbp,r12 0x0000025ed50247a0: mov DWORD PTR [rsp+0x24],ebx 0x0000025ed50247a4: mov DWORD PTR [rsp+0x28],r10d
Этот листинг уже может прочитать человек, и выяснить, что именно сделал JIT компилятор. Это пригодится для отладки какого-то критического участка вашего приложения, где вы точно хотите быть уверены, что генерируемый код соответствует вашим ожиданиям.
Кэш скопилированного кода
Код, который генерируют компиляторы C1 и C2 во время своей работы кэширется во внутренней структуре JVM, которая называется CodeCache.
Кэш имеет определенный размер. Посмотреть размеры кэша по умолчанию и другие параметры связанные с эшем можно запустив приложение с флагом -XX:+PrintFlagsFinal и отфильтровав строки по подстроке CodeCache.
По мере работы приложения и «прогрева кода» CodeCache заполняется, и в конце концов в кэше может не остаться места. В этом случае JVM сбрасывает ненужный код и на освободившееся место записывает новый. Этот процесс называется CodeCacheFlushing. Эта информация важна для достижения low-latency:
- На сброс кода требуется время, что может вызвать задержки
- Сброшенный «старый код» может оказаться важным для какой-то задачи через несколько минут. В этом случае ваш код будет исполняться в режиме интерпретации, т.е. значительно медленнее, чем хотелось бы. А после какого-то времени JIT-компилятор начнет компилировать этот участок кода, а потом снова замещать интерпретированный участок на компилированный. На это тоже уйдет время и может вызвать нежелательные задержки при работе программы.
Поэтому в приложениях, где latency критично, рекомендуется отключать сброс кэша кода с помощью флага -XX:-UseCodeCacheFlushing. В этом случае, все что накомпилируют компиляторы C1 и C2 навечно останется в кэше. Если место к кэше кончится и свободного места для нового кода не будет, компиляция прекратится и оставшийся неоткомпиллированный код будет исполняться в режиме интерпретации. Чтобы этого не произошло, выставляйте размер кэша кода как можно большим, подбирая оптимальный размер экспериментальным путем с помощью флагов -XX:InitialCodeCacheSize и -XX:ReservedCodeCacheSize.
Начиная с Java 8 в JVM включена ступенчатая, поэтапная компиляция (tiered compilation). Если она включена, область памяти под кэш кода должна быть по-больше. Если ступенчатая компиляция отключена — по-меньше. Изменение размера делается автоматически в JVM, если размер кэша не выставлен явно с помощью параметров.
Посмотреть на размер и использование кэша можно с помощью этих флагов:
- -XX:+PrintCodeCache — распечатает общую информацию о кэше кода при завершении работы приложения
- -XX:+PrintCodeCacheOnCompilation — будет выводить информацию о состоянии кэша кода при каждой компиляции
Ссылки
- Документация в Wiki на сайте OpenJDK
- Профилирование с точностью до ассемблерных инструкций