Введение в IL2CPP
Unity продолжают совершенствовать технологию IL2CPP, а мы публикуем перевод статьи о том, как она работает.
Около года назад мы писали о будущем скриптинга в Unity. Новая технология скриптинга IL2CPP должна была обеспечить движок высокопроизводительной портативной виртуальной машиной. В январе мы выпустили нашу первую платформу на 64-битной iOS, использующую IL2CPP. С выходом Unity 5 появилась еще одна платформа – WebGL. При поддержке огромного сообщества пользователей мы выпустили множество патчей и обновлений для IL2CPP, постепенно оптимизируя компилятор и повышая быстродействие среды.
А пока мы продолжаем совершенствовать технологию IL2CPP, было бы неплохо рассказать о том, как она работает. Мы планируем написать серию статей, посвященных таким темам:
1. Основы – набор инструментов и аргументы командной строки (эта статья).
2. Экскурсия по генерируемому коду.
3. Советы по отладке генерируемого кода.
4. Вызовы методов: обычные методы, виртуальные методы и другие.
5. Реализация общего обмена.
6. Обёртки P/Invoke для типов и методов.
7. Интеграция сборщика мусора.
8. Тестирование и применение фреймворков.
В этих статьях мы обсудим некоторые особенности реализации IL2CPP. Надеюсь, эта информация вам пригодится.
Технология IL2CPP состоит из двух частей:
• компилятор Ahead-of-time (AOT);
• исполняемая библиотека для поддержки виртуальной машины.
Эта утилита принимает управляемые сборки, скомпилированные Mono-компилятором, поставляемым с Unity, и генерирует код C++, который мы передаем в компилятор C++ для конкретной платформы.
Инструментарий IL2CPP можно схематически представить так:
Вторая часть технологии IL2CPP – это исполняемая библиотека для поддержки виртуальной машины. Мы написали эту библиотеку почти полностью на C++ (в ней есть немного платформенного кода, но пусть это останется между нами) и назвали ее libil2cpp. Она поставляется в виде статической библиотеки, связанной с исполняемым файлом плеера, а ее простота и портативность являются одними из ключевых преимуществ технологии IL2CPP.
Вы можете получить более четкое представление об организации кода libil2cpp, глядя на файлы заголовков, поставляемых с Unity (их можно найти в директории Editor\Data\PlaybackEngines\webglsupport\BuildTools\Libraries\libil2cpp\include в Windows, или в директории Contents/Frameworks/il2cpp/libil2cpp – в OS X). Например, интерфейс между кодом C++, генерируемым il2cpp.exe, и средой libil2cpp находится в файле заголовка codegen/il2cpp-codegen.h.
Один из ключевых элементов среды – сборщик мусора. В комплект Unity 5 входит libgc, сборщик мусора Boehm-Demers-Weiser. Однако libil2cpp поддерживает и другие сборщики. Например, мы рассматриваем возможность интеграции с Microsoft GC – сборщиком с открытым исходным кодом в комплекте CoreCLR. В одной из следующих статей мы расскажем о нём подробнее.
Как выполняется il2cpp.exe?
Давайте рассмотрим на примере. Для этого я создам новый пустой проект в Unity 5.0.1 на Windows. Чтобы у нас был хотя бы один пользовательский скрипт, я добавлю к главной камере простой компонент MonoBehaviour:
При сборке для платформы WebGL я могу использовать Process Explorer, чтобы увидеть команду для запуска il2cpp.exe:
Это достаточно длинная строка, поэтому давайте разберем ее по частям. Сперва Unity запускает этот исполняемый файл:
Следующий аргумент – сама утилита il2cpp.exe.
Остальные аргументы передаются il2cpp.exe, а не mono.exe. Давайте разберем и их. Во-первых, Unity передает 5 флагов il2cpp.exe:
Стоит отметить, что этот набор аргументов командной строки для il2cpp.exe всё еще нестабилен и, скорее всего, изменится в последующих версиях.
Итак, в командной строке указаны 2 файла и 1 директория:
• «C:\Users\Josh Peterson\Documents\IL2CPP Blog Example\Temp\StagingArea\Data\Managed\Assembly-CSharp.dll»
• «C:\Users\Josh Peterson\Documents\IL2CPP Blog Example\Temp\StagingArea\Data\Managed\UnityEngine.UI.dll»
• «C:\Users\Josh Peterson\Documents\IL2CPP Blog Example\Temp\StagingArea\Data\il2cppOutput»
Утилита il2cpp.exe принимает список всех сборок IL, подлежащих конвертации. В нашем случае сюда входит мой простой скрипт MonoBehaviour, Assembly-CSharp.dll и UnityEngine.UI.dll. Обратите внимание, что здесь явно отсутствует несколько сборок. Очевидно, что мой скрипт ссылается на UnityEngine.dll, а значит нужен хотя бы mscorlib.dll, и, возможно, другие сборки. Но где они? Дело в том, что разрешение сборок происходит внутри il2cpp.exe. Их можно упомянуть в командной строке, но это необязательно. Получается, Unity нужно всего лишь указать корневые сборки (на которые не ссылаются другие сборки).
Чего не делает IL2CPP?
Хотелось бы обратить внимание на одну сложность, которую мы решили обойти стороной и таким образом избежали многих проблем. Мы не пытались переписать стандартную библиотеку C# под IL2CPP. При создании проекта Unity с использованием IL2CPP весь код стандартных библиотек C# (mscorlib.dll, System.dll и т. д.) является точной копией кода Mono-скриптов.
Мы используем код стандартных библиотек C#, потому что он уже успел хорошо зарекомендовать себя во многих проектах. Таким образом, если где-то возникает ошибка, мы можем быть уверены, что проблема в AOT-компиляторе или исполняемой библиотеке.
Разработка, тестирование и выпуск IL2CPP
С момента первого официального релиза IL2CPP версии 4.6.1p5 в январе мы выпустили 6 полноценных обновлений и 7 патчей (для Unity 4.6 и 5.0), в которых исправили более ста ошибок.
Чтобы обеспечить непрерывную оптимизацию, мы разрабатываем обновления на базе одной версии кода. Перед каждым релизом мы передаем все изменения в конкретную релизную ветку, проводим тестирование и проверяем, все ли ошибки были успешно исправлены. Наши команды QA и Sustained Engineering прикладывают максимум усилий, чтобы разработка велась в таком темпе, и ошибки исправлялись каждую неделю.
Наше сообщество оказало нам бесценную помощь, предоставив множество полезных сообщений об ошибках. Мы очень благодарны пользователям за все отзывы, которые помогают непрерывно улучшать IL2CPP.
Для разработчиков IL2CPP тестирование стоит на первом месте. Мы часто берем за основу технику разработки через тестирование и крайне редко отправляем запросы на включение, если код не был полностью протестирован. Эта стратегия хорошо работает для таких технологий, как IL2CPP, где есть четкие входы и выходы. Это означает, что подавляющее большинство ошибок, с которыми мы сталкиваемся, являются скорее частными случаями, чем неожиданным поведением системы (например, при использовании 64-битного IntPtr в качестве 32-битного индекса массива может произойти вылет с ошибкой компилятора С++). Таким образом, мы можем быстро определять и исправлять любые баги.
При поддержке нашего сообщества мы всё время делаем IL2CPP стабильнее и производительнее.
IL2CPP: экскурсия по генерируемому коду
Для этого мы будем использовать очень специфический код, который наверняка изменится в следующих версиях Unity. Но основные принципы останутся неизменными.
Для этого примера я буду использовать последнюю доступную версию Unity 5.0.1p1. Как и в предыдущей статье, создам новый пустой проект и добавлю один скрипт со следующим содержанием:
Я соберу этот проект под WebGL, используя редактор Unity на Windows. Чтобы получить относительно хорошие имена в генерируемом коде C++, я включил опцию Development Player в Build Settings. Кроме того, я установил значение Full для Enable Exceptions в WebGL Player Settings.
Обзор генерируемого кода
После завершения сборки сгенерированный код C++ можно найти в директории Temp\StagingArea\Data\il2cppOutput в папке проекта. Как только я закрою редактор, эта директория будет удалена, но, пока он открыт, можно внимательно изучить ее.
Утилита il2cpp.exe сгенерировала много файлов даже для такого маленького проекта: 4625 файлов заголовков и 89 файлов исходного кода C++. Для проверки такого количества кода я предпочитаю использовать текстовый редактор с поддержкой Exuberant CTags. Обычно CTags быстро генерирует файл тегов, что значительно упрощает навигацию по коду.
Вы можете заметить, что многие сгенерированные файлы C++ содержат не простой код из нашего скрипта, а преобразованный код стандартных библиотек, таких как mscorlib.dll. Как уже говорилось в предыдущей статье, скриптовый движок IL2CPP использует тот же код стандартных библиотек, что и Mono. Обратите внимание, что мы преобразовываем код mscorlib.dll и других стандартных библиотек при каждом запуске il2cpp.exe. Это может показаться ненужным, так как код не меняется.
Дело в том, что IL2CPP всегда очищает байт-код, чтобы уменьшить размер исполняемого файла. Следовательно, даже небольшие изменения в коде скрипта могут привести к тому, что различные части кода стандартной библиотеки будут использоваться или нет, в зависимости от обстоятельств. Поэтому mscorlib.dll должен быть преобразован при каждой сборке. Мы пытаемся усовершенствовать процесс инкрементальной сборки, но пока без особых успехов.
Отображение управляемого кода в генерируемом коде C++
Для каждого типа в управляемом коде il2cpp.exe генерирует 2 файла заголовков: для определения типа и объявления методов для этого типа. Например, давайте посмотрим на содержимое преобразованного типа UnityEngine.Vector3. Файл заголовка для этого типа называется UnityEngine_UnityEngine_Vector3.h. Имя создается на основе имени сборки (UnityEngine.dll), пространства имен и имени типа. Код выглядит следующим образом:
Утилита il2cpp.exe преобразует каждое из трех полей экземпляра и немного изменяет имена, используя начальные подчеркивания во избежание возможных конфликтов с зарезервированными словами. Мы используем зарезервированные имена в C++, но пока ни разу не видели, чтобы они конфликтовали с кодом стандартных библиотек.
Файл UnityEngine_UnityEngine_Vector3MethodDeclarations.h содержит объявления для всех методов в Vector3. Например, Vector3 переопределяет метод Object.ToString:
Обратите внимание на комментарий, в котором указан управляемый метод, представляющий исходное объявление. Это может пригодиться для поиска файлов на выходе по имени управляемого метода в данном формате, особенно для методов с общими именами, такими как ToString.
У методов, преобразованных il2cpp.exe, есть несколько интересных особенностей:
• Они не являются функциями-членами в C++, а представляют собой свободные функции с указателем this в качестве первого аргумента. Для первого аргумента статических функций в управляемом коде IL2CPP всегда передает значение NULL. Объявляя методы с указателем this в качестве первого аргумента, мы упрощаем генерацию кода в il2cpp.exe и вызов методов через другие методы (например делегаты) для генерируемого кода.
• Каждый метод имеет дополнительный аргумент типа MethodInfo*, содержащий метаданные о методе, которые могут использоваться, например, для вызова виртуального метода. Mono использует специфичные для платформы транспорты, чтобы передать эти метаданные. Но в случае IL2CPP мы решили не использовать их, чтобы улучшить переносимость.
• Все методы объявлены через extern «C», чтобы il2cpp.exe могла при необходимости обмануть компилятор C++ и рассматривать все методы, как если бы они имели один тип.
• Имена типов содержат суффикс «_t», имена методов — суффикс «_m». Конфликты имен решаются добавлением уникального номера для каждого имени. В случае любых изменений в коде пользовательского скрипта эти цифры тоже меняются, поэтому на них не стоит рассчитывать при переходе на новую сборку.
Первые 2 пункта подразумевают, что каждый метод имеет по крайней мере 2 параметра: указатель this и указатель MethodInfo. Добавляют ли эти параметры лишние затраты ресурсов? Да, добавляют, но это не влияет на производительность, как может показаться на первый взгляд. По крайней мере, так говорят результаты профилирования.
Перейдем к определению метода ToString с помощью Ctags. Оно находится в файле Bulk_UnityEngine_0.cpp. Код в этом определении метода не похож на код C# в методе Vector3::ToString(). Однако, если вы используете инструмент вроде ILSpy для просмотра кода метода Vector3::ToString(), вы можете заметить, что генерируемый код C++ очень похож на код IL.
Теперь вернемся к заголовочному файлу объявления методов и обратим внимание на строку в верхней части файла:
Файл il2cpp-codegen.h содержит интерфейс, с помощью которого генерируемый код получает доступ к среде libil2cpp. Позже мы обсудим несколько способов использования этой среды.
Давайте посмотрим на определение метода Vector3::ToString(), а именно на общий пролог, созданный il2cpp.exe для всех методов.
Вторая часть пролога запускает «ленивую» инициализацию типа метаданных для любого массива или универсальных типов, используемых в теле метода. Таким образом, ObjectU5BU5D_t4 – это имя типа System.Object[]. Эта часть пролога выполняется всего один раз и не делает ничего, если тип уже был инициализирован, поэтому никакого негативного влияния на производительность замечено не было.
А как же потоковая безопасность? Что если два потока вызывают Vector3::ToString() одновременно? Ничего страшного: весь код в среде libil2cpp, используемый для инициализации типа, безопасно вызывать из нескольких потоков. Скорее всего, функция il2cpp_codegen_class_from_type будет вызвана несколько раз, но фактически сработает только единожды, в одном потоке. Выполнение метода не возобновится до тех пор, пока не завершится инициализация. Поэтому этот пролог метода является потокобезопасным.
Проверки во время выполнения
Следующая часть метода создает массив объектов, сохраняет значение поля Х для Vector3 в локальную переменную, затем упаковывает эту переменную и добавляет ее в массив с нулевым индексом. Генерируемый код C++ (с комментариями) выглядит так:
Il2cpp.exe добавляет 3 проверки, отсутствующие в коде IL:
• При значении массива NULL проверка NullCheck выбрасывает исключение NullReferenceException.
• При неправильном индексе массива проверка IL2CPP_ARRAY_BOUNDS_CHECK выбрасывает исключение IndexOutOfRangeException.
• При неправильном типе элемента, добавляемого в массив, ArrayElementTypeCheck выбрасывает исключение ArrayTypeMismatchException.
Теперь, когда мы увидели, как выглядят поля экземпляра (на примере Vector3), давайте посмотрим, как преобразуются статические поля и как к ним осуществляется доступ. Сначала найдем определение метода HelloWorld_Start_m3, которое находится в файле Bulk_Assembly-CSharp_0.cpp в моей сборке, а затем перейдем к типу Important_t1 (в файле AssemblyU2DCSharp_HelloWorld_Important.h):
Обратите внимание, что il2cpp.exe создала отдельную структуру С++, чтобы предоставить статическое поле, доступное всем экземплярам этого типа. Таким образом, во время выполнения будет создан один экземпляр типа Important_t1_StaticFields, и все экземпляры типа Important_t1 будут использовать его как статическое поле. В генерируемом коде доступ к статическому полю осуществляется следующим образом:
Метаданные типа для Important_t1 содержат указатель на один экземпляр типа Important_t1_StaticFields, а также информацию о том, что этот экземпляр используется для получения значения статического поля.
Il2cpp.exe преобразует управляемые исключения в исключения C++. Мы выбрали такой подход, чтобы, опять же, не зависеть от конкретных платформ. Когда il2cpp.exe нужно сгенерировать код для создания управляемого исключения, она вызывает функцию il2cpp_codegen_raise_exception. Код вызова и перехвата управляемых исключений в нашем методе HelloWorld_Start_m3 выглядит так:
Все управляемые исключения заворачиваются в тип Il2CppExceptionWrapper. Когда генерируемый код перехватывает исключение такого типа, он распаковывает его C++ представление (имееющее тип Exception_t8). В данном случае мы ищем только InvalidOperationException, поэтому, если не найдем исключение этого типа, C++ снова выбросит его копию. Если же мы найдем исключение этого типа, код запустит обработчик перехвата и выведет сообщение об исключении.
Возникает интересный вопрос: что здесь делают метки и операторы goto? Эти конструкции необязательно использовать в структурном программировании. Дело в том, что в языке IL не используются принципы структурного программирования, такие как циклы и условные операторы. Это низкоуровневый язык, поэтому il2cpp.exe придерживается низкоуровневых концепций в генерируемом коде.
В качестве примера рассмотрим цикл for в методе HelloWorld_Start_m3:
Переменная V_2 является индексом цикла. В начале она имеет значение 0, затем увеличивается внизу цикла в этой строке:
Условие окончания цикла проверяется здесь:
Пока V_2 меньше трех, оператор goto переходит к метке IL_00af, которая является верхней частью тела цикла. Как вы уже могли догадаться, на данный момент il2cpp.exe генерирует код C++ непосредственно из IL без использования промежуточного абстрактного представления синтаксического дерева. Возможно, вы также заметили, что в разделе «Проверки во время выполнения» в коде есть такие фрагменты:
Очевидно, что переменная L_2 здесь лишняя. Несмотря на то, что в большинстве компиляторов C++ она отсеивается, нам бы хотелось вообще избежать ее появления в коде. Сейчас мы рассматриваем возможность использования абстрактного синтаксического дерева, чтобы лучше понять код IL и генерировать лучший код C++ для случаев, когда используются локальные переменные и циклы.
Мы затронули только малую часть кода C++, генерируемого IL2CPP для очень простого проекта. Теперь я рекомендую вам взглянуть на генерируемый код вашего собственного проекта. Имейте в виду, что в будущих версиях Unity код C++ будет выглядеть по-другому, так как мы продолжаем повышать качество и производительность технологии IL2CPP.
Благодаря преобразованию кода IL в C++ нам удалось добиться хорошего баланса между его переносимостью и производительностью. Мы получили много полезных для разработчиков функций управляемого кода, сохранив преимущества машинного кода, которые компилятор С++ обеспечивает для различных платформ.
В будущих постах мы поговорим о генерируемом коде подробнее: рассмотрим вызовы методов и распределение их реализаций и оберток для вызова нативных библиотек. А в следующий раз мы займемся отладкой генерируемого кода для 64-битной версии iOS с использованием Xcode.
Windows build support il2cpp что это
Мы не планируем прекращать поддержку и развитие IL2CPP, но мы подумали, что было бы неплохо сделать шаг в сторону и рассказать немного о том, как работает IL2CPP. В течение следующих нескольких месяцев мы планируем написать о следующих темах (возможно. и о других) в серии постов об IL2CPP:
Мы собираемся обсудить некоторые детали о реализации IL2CPP, что, несомненно, изменится в будущем. Надеюсь, мы все еще можем предоставить некоторую полезную и интересную информацию.
Что такое IL2CPP?
Технология, которую мы называем IL2CPP, состоит из двух отдельных частей:
AOT компилятор
Утилита Il2cpp.exe принимает управляемые сборки, собранные с компилятором Mono, который поставляется с Unity, и генерирует C++ код, который мы передаем в C++ компилятор конкретной платформы.
Вы можете думать об инструментах IL2CPP так:
Исполняемая библиотека
Вы можете найти некоторые подсказки о том, как код libil2cpp организован глядя на файлы заголовков для libil2cpp, которые поставляются с Unity (вы найдете их в директории Editor\Data\PlaybackEngines\webglsupport\BuildTools\Libraries\libil2cpp\include на Windows, или в Contents/Frameworks/il2cpp/libil2cpp на OSX). Например, интерфейс между генерацией C++ кода il2cpp.exe и libil2cpp находится в файле заголовка codegen/il2cpp-codegen.h.
Как выполняется il2cpp.exe?
Давайте рассмотрим на примере. Я буду использовать Unity 5.0.1 на Windows, и я начну с нового проекта. Так что у нас есть один пользовательский скрипт, я добавлю этот простой компонент MonoBehaviour к главной камере в сцене:
public class HelloWorld : MonoBehaviour <
void Start () <
Debug.Log(«Hello, IL2CPP!»);
>
>[/csharp]
Когда я соберу проект для WebGL, я могу использовать Process Explorer, чтобы увидеть команду Unity, используемую для запуска il2cpp.exe:
Это строка довольно длинная и страшная, так что давайте разберем ее. Во-первых, Unity запускает этот исполняемый файл:
Остальные аргументы передаются il2cpp.exe, а не mono.exe. Давайте посмотрим на них. Во-первых, Unity передает пять флагов il2cpp.exe:
Важно отметить, что эти аргументы могут быть и будут изменены в более поздних версиях. Ещё не то время, когда у нас есть стабильный и поддерживаемый набор аргументов для il2cpp.exe.
В итоге у нас есть два файла и одна директория:
Утилита il2cpp.exe принимает список всех сборок IL, которые следует конвертировать. В нашем случае это сборка, содержащая мой простой скрипт MonoBehaviour, Assembly-CSharp.dll и UnityEngine.UI.dll. Обратите внимание, что здесь есть несколько явно недостающих сборок. Очевидно, что мой скрипт ссылается на UnityEngine.dll, и, по крайней мере необходима mscorlib.dll, и, возможно, другие сборки. Где они? На самом деле, il2cpp.exe определяет эти сборки внутри. Они могут быть упомянуты в командной строке, но это необязательно. Unity явно нужно указать только главные сборки (те, на которые не ссылаются другие сборки).
Попробуйте изменить различные параметры в Player Settings WebGL или iOS. Вы увидите различные аргументы, передаваемые il2cpp.exe включающие различные этапы генерации кода. Например, изменение опции «Enable Exceptions» в Player Settings WebGL на «Full» добавляет аргументы –emit-null-checks, –enable-stacktrace, и –enable-array-bounds-check к вызову il2cpp.exe.
Чего не делает IL2CPP?
Я хотел бы указать на одну из проблем, которые мы не учли в IL2CPP. Мы не пытались переписать стандартную библиотеку C# в IL2CPP. При создании проекта Unity, который использует IL2CPP, весь код стандартных библиотек C# mscorlib.dll, System.dll и т.д. является точно таким же кодом, какой используется для написания скриптов с Mono.
Мы полагаемся на код стандартных библиотек C#, который уже известен пользователям и хорошо проверен в проектах Unity. Поэтому, когда мы исследуем ошибки, связанные с IL2CPP, мы можем быть достаточно уверены, что это ошибка в компиляторе AOT или в исполняемой библиотеки, и нигде больше.
Как мы разрабатываем, тестируем и выпускаем IL2CPP
С момента первоначального публичного релиза IL2CPP в версии 4.6.1p5 в январе, мы выпустили 6 полных релизов и 7 патчей (в разных версиях Unity 4.6 и 5.0). Мы исправили более 100 ошибок, упомянутых в примечаниях к выпуску.
Для того чтобы сделать это процесс совершенствования непрерывным, мы разрабатываем только одну версию кода IL2CPP. Перед каждым выпуском мы портируем изменения IL2CPP для конкретной ветки версий, запускаем наши тесты и проверяем, все ли исправления корректно работают в этой версии. Наши команды QA и Sustained Engineering проделали невероятную работу, чтобы сделать возможным выпуск обновлений так быстро. Это означает, что наши пользователи будут ждать не более одной недели с момента последних исправлений для IL2CPP.
Наше сообщество пользователей, оказалось бесценным, представив множество полезных сообщений об ошибках. Мы ценим все отзывы от наших пользователей, помогающие непрерывно улучшать IL2CPP, и мы с нетерпением ждем больше.
Команда разработчиков работает над IL2CPP имея настрой на сильный тест. Мы часто используем методы Test Driven Design, и редко отправляем запросы на слияние без хороших тестов. Эта стратегия хорошо работает для технологий, таких как IL2CPP, где у нас есть четкие входы и выходы. Это означает, что подавляющее большинство ошибок, которые мы видим, не неожиданное поведение, а неожиданные случаи (например, можно использовать 64-битный IntPtr как 32-битный индекс массива, в результате чего вылет с ошибкой компилятора С++, и в реальном коде на самом деле так делают!). Это различие позволяет нам быстро исправлять ошибки.
С помощью нашего сообщества мы работаем над тем, чтобы IL2CPP работал стабильно и быстро, насколько возможно. Кстати, если это интересно вам, у нас есть вакансия (просто говорю).
IL2CPP Overview
This type of compilation, in which Unity compiles code specifically for a target platform when it builds the native binary, is called ahead-of-time (AOT) compilation. The Mono backend compiles code at runtime, with a technique called just-in-time compilation (JIT).
IL2CPP can improve performance across a variety of platforms, but the need to include machine code in built applications increases both the build time and the size of the final built application. For more information, see How IL2CPP works and the blog series An introduction to IL2CPP internals.
IL2CPP supports the debugging of managed code in the same way as the Mono scripting backend. For more information, see Debugging C# code in Unity.
Building a project using IL2CPP
To build a project with IL2CPP, you need to have the backend installed in your Unity installation. You can select IL2CPP as an optional module when you first install a version of Unity, or add IL2CPP support to an existing installation through the Unity Hub. For more information, see Installing the Unity Hub and Adding modules to the Unity Editor.
You can change the scripting backend Unity uses to build your application in one of two ways:
Through the Player Settings Settings that let you set various player-specific options for the final game built by Unity. More info
See in Glossary menu in the Editor. Perform the following steps to change the scripting backend through the Player Settings menu:
You can also open the Player Settings menu from inside the Build Settings menu; go to File > Build Settings and click on the Player Settings button.
Through the Editor scripting API. Use the PlayerSettings.SetScriptingBackend property to change the scripting backend that Unity uses.
The Configuration section of the Player settings
How IL2CPP works
When you start a build using IL2CPP, Unity automatically performs the following steps:
IL2CPP enables Unity to pre-compile code for specific platforms. The binary file Unity produces at the end of this process already contains necessary machine code for the target platform, while Mono has to compile this machine code at runtime during execution. AOT compilation does increase build time, but it also improves compatibility with the target platform and can improve performance.
Both scripting backends require a new build for each platform you want to target. For example, to support both the Android and iOS Apple’s mobile operating system. More info
See in Glossary platforms, you need to build your application twice and produce two binary files.
The assembly stripping stage helps reduce the final binary size. Unity removes any bytecode that the final built application doesn’t use.
Optimizing IL2CPP build times
Project build times when using IL2CPP can be significantly longer than when using Mono. However, you can do several things to reduce build time.
Exclude your project from anti-malware software scans
You can exclude your Unity project folder and target build folders from anti-malware software scans before you build your project.
Store your project and target build folder on a solid-state drive (SSD)
Solid-state drives (SSDs) have faster read/write speeds than traditional hard disk drives (HDD). Converting IL code to C++ and compiling it involves a large number of read/write operations, so a faster storage device speeds up this process.
Enabling runtime checks using Il2CppSetOption
When you use the IL2CPP scripting backend, you can control how il2cpp.exe generates C++ code. You can use the Il2CppSetOption attribute to enable or disable the following runtime checks:
Property: | Description: | Default: |
---|---|---|
Null checks | If this option is enabled, the C++ code that IL2CPP generates contains null checks and throws managed NullReferenceException exceptions as necessary. If this option is disabled, IL2CPP doesn’t emit the null checks into the generated C++ code. For some projects, disabling this option might improve runtime performance. When this setting is disabled, Unity doesn’t prevent attempts to access null values in the generated code, which might lead to incorrect behavior. Your application is likely to crash soon after it dereferences the null value. Unity recommends that you don’t disable this option. | Enabled |
Array bounds checks | If this option is enabled, the C++ code that IL2CPP generates contains array bounds checks and throws managed IndexOutOfRangeException exceptions as necessary. If this option is disabled, IL2CPP doesn’t emit the array bounds checks into the generated C++ code. For some projects, disabling this option might improve runtime performance. However, when this option is disabled, Unity doesn’t prevent attempts to access an array with invalid indices in the generated code, which might lead to incorrect behavior, including reading from or writing to arbitrary memory locations. In most cases, these memory accesses occur without any immediate side effects, and can corrupt the state of the application with no obvious warning signs. This can make debugging these errors extremely difficult. Unity recommends that you keep this option enabled. | Enabled |
Divide by zero checks | If this option is enabled, C++ code generated by IL2CPP contains divide by zero checks for integer division and throw managed DivideByZeroException exceptions as necessary. If this option is disabled, IL2CPP doesn’t emit the divide by zero checks on integer division into the generated C++ code. These checks have an impact on performance at runtime. You should only enable this option if you need to run divide by zero checks; otherwise, leave it disabled. | Disabled |
To use the Il2CppSetOption attribute:
The below example describes how to use the Il2CppSetOption attribute:
You can apply Il2CppSetOption attribute to types, methods, and properties. Unity uses the attribute from the most local scope.