Бросание исключений и Burst компилятор (2024)

(Russian translation from English by Maxim Voloshin)

Burst компилятор Unity навязывает использование интересного подмножества C#. Правило “никаких управляемых объектов” не всегда справедливо. Сегодня мы посмотрим на исключения (Exception), которые являются управляемыми объектами, но частично поддерживаются Burst. Что же разрешено, а что запрещено? Читайте дальше, чтобы узнать.

Документация Burst гласит:

Burst работает с подмножеством .NET, которое не допускает использования любых управляемых объектов/ссылочных типов в вашем коде (классы в C#).

Позже указан список запрещенных типов:

string так как это управляемый тип

И еще раз позже:

Любые методы, относящиеся к управляемым объектам (например, методы, использующие string и т.д.)

Атрибут [BurstDiscard] может быть полезен, когда вы хотите использовать эти самые управляемые объекты

При запуске некоторого кода на полноценном C# (не внутри кода, компилируемого через Burst) Вы можете захотеть использовать некоторые управляемые объекты, но при этом не компилировать эти части кода при использовании Burst.

Все выглядит так, словно исключения, которые являются классами и, стало быть, управляемыми объектами еще и принимающие как аргумент строку с сообщением, не могут быть использованы в коде, скомпилированном Burst. Однако, оказывается есть исключение (извините за каламбур) из этих правил, которое позволяет исключениям и строкам частично работать.

Давайте попробуем написать несколько тестовых задач в Job System, которые используют исключения и строки

using System;using Unity.Burst;using Unity.Jobs;using UnityEngine;class TestScript : MonoBehaviour{ [BurstCompile] struct ExceptionJob : IJob { public void Execute() { throw new ArgumentException("boom"); } } [BurstCompile] struct BeginExceptionJob : IJob { public int I; public void Execute() { throw new ArgumentException("boom" + I); } } [BurstCompile] struct EndExceptionJob : IJob { public int I; public void Execute() { throw new ArgumentException(I + "boom"); } } [BurstCompile] struct MiddleExceptionJob : IJob { public int I; public void Execute() { throw new ArgumentException(I + "boom" + I); } } void Start() { new ExceptionJob().Schedule().Complete(); new BeginExceptionJob { I = 10 }.Schedule().Complete(); new EndExceptionJob { I = 10 }.Schedule().Complete(); new MiddleExceptionJob { I = 10 }.Schedule().Complete(); }}

Теперь мы запустим это в Unity 2019.1.3f1 и Burst 1.0.0 на macOS и посмотрим на результат. Во-первых, это прекрасно компилируется в редакторе и билд для macOS тоже работает. Никаких ошибок или предупреждений. Если запустить macOS приложение, то мы увидим в консоли следующее:

System.ArgumentException: boomThis Exception was thrown from a job compiled with Burst, which has limited exception support. Turn off burst (Jobs -> Enable Burst Compiler) to inspect full exceptions & stacktraces.(Filename: Line: -1)System.ArgumentException: boomThis Exception was thrown from a job compiled with Burst, which has limited exception support. Turn off burst (Jobs -> Enable Burst Compiler) to inspect full exceptions & stacktraces.(Filename: Line: -1)System.ArgumentException: boomThis Exception was thrown from a job compiled with Burst, which has limited exception support. Turn off burst (Jobs -> Enable Burst Compiler) to inspect full exceptions & stacktraces.(Filename: Line: -1)System.ArgumentException: boomThis Exception was thrown from a job compiled with Burst, which has limited exception support. Turn off burst (Jobs -> Enable Burst Compiler) to inspect full exceptions & stacktraces.(Filename: Line: -1)

Обратите внимание, что все четыре задачи бросили исключение System.ArgumentException, содержащее сообщение boom. Ни одна из конкатенаций не отработала и мы остались только со строкой "boom".

Чтобы выяснить почему, давайте откроем Burst панель в Инспекторе и посмотрим на сгенерированный ассемблерный код для каждой из задач:

; ExceptionJobmovabs rax, offset .Lburst_abort_Ptrmov rax, qword ptr [rax]movabs rdi, offset .Lburst_abort.error.idmovabs rsi, offset .Lburst_abort.error.messagejmp rax; BeginExceptionJobmovabs rax, offset .Lburst_abort_Ptrmov rax, qword ptr [rax]movabs rdi, offset .Lburst_abort.error.idmovabs rsi, offset .Lburst_abort.error.messagejmp rax; EndExceptionJobmovabs rax, offset .Lburst_abort_Ptrmov rax, qword ptr [rax]movabs rdi, offset .Lburst_abort.error.idmovabs rsi, offset .Lburst_abort.error.messagejmp rax; MiddleExceptionJobmovabs rax, offset .Lburst_abort_Ptrmov rax, qword ptr [rax]movabs rdi, offset .Lburst_abort.error.idmovabs rsi, offset .Lburst_abort.error.messagejmp rax

Все четыре задачи были скомпилированы в абсолютно одинаковый код. Отсутствуют инструкции чтения значения переменной I и его конкатенации со строкой "boom". Вместо этого мы видим, что .Lburst_abort.error.id и .Lburst_abort.error.message выводятся вследствие возникновения исключения, и программа переходит в .Lburst_abort_Ptr. Для того чтобы узнать больше об этих символах, давайте посмотрим на следующий ассемблерный код:

.Lburst_abort.error.id: .asciz "System.ArgumentException" .size .Lburst_abort.error.id, 25 .type .Lburst_abort.error.message,@object.Lburst_abort.error.message: .asciz "boom" .size .Lburst_abort.error.message, 5 .type .Lburst_abort_Ptr,@object .local .Lburst_abort_Ptr .comm .Lburst_abort_Ptr,8,8 .type .Lburst_abort.function.string,@object.Lburst_abort.function.string: .asciz "burst_abort" .size .Lburst_abort.function.string, 12 .section .debug_str,"MS",@progbits,1

Здесь мы видим, что .Lburst_abort.error.id содержит ASCII (.asciz) строку "System.ArgumentException", размер которой (.size) равен 25, что соответствует количеству символов в строке плюс символ конца строки NUL.

.Lburst_abort.error.message содержит ASCII строку "boom", размер которой равен 5 все по тем же причинам.

.Lburst_abort_Ptr внутри .Lburst_abort.error.message это адрес в памяти, по которому перейдет выполнение программы во время бросания исключения.

Таким образом, когда бросается исключение в коде, скомпилированном через Burst, указатель на строку с типом исключения (System.ArgumentException, в данном случае) и указатель на строку с сообщением (boom) будут записаны в определенные регистры и выполнение программы перейдет по адресу.Lburst_abort_Ptr, где исключение, по всей видимости, обрабатывается чтением из этих регистров.

При этом не происходит абсолютно никаких выделений памяти. Только хранение указателей на строковые литералы, сохраненные в сегменте данных. Это возможная причина того, почему не сработала конкатенация строк. Так как это потребует дополнительных аллокаций, в перспективе динамически растущих, и последующее освобождения памяти, когда исключение будет обработано.

Так что не стесняйтесь использовать исключения в Job System с Burst компилятором до тех пор, пока сообщение является строковым литералом или конкатенации не нужны. Только имейте в виду, что catch и finally нельзя использовать с Burst, следовательно, бросание исключения всегда будет приводить к фатальной ошибке.

Бросание исключений и Burst компилятор (2024)

References

Top Articles
Latest Posts
Recommended Articles
Article information

Author: Errol Quitzon

Last Updated:

Views: 6503

Rating: 4.9 / 5 (59 voted)

Reviews: 90% of readers found this page helpful

Author information

Name: Errol Quitzon

Birthday: 1993-04-02

Address: 70604 Haley Lane, Port Weldonside, TN 99233-0942

Phone: +9665282866296

Job: Product Retail Agent

Hobby: Computer programming, Horseback riding, Hooping, Dance, Ice skating, Backpacking, Rafting

Introduction: My name is Errol Quitzon, I am a fair, cute, fancy, clean, attractive, sparkling, kind person who loves writing and wants to share my knowledge and understanding with you.