БроÑание иÑключений и Burst компилÑтор
(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: boom This 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 This 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 This 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 This 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 панель в ИнÑпекторе и поÑмотрим на Ñгенерированный аÑÑемблерный код Ð´Ð»Ñ ÐºÐ°Ð¶Ð´Ð¾Ð¹ из задач:
; ExceptionJob movabs rax, offset .Lburst_abort_Ptr mov rax, qword ptr [rax] movabs rdi, offset .Lburst_abort.error.id movabs rsi, offset .Lburst_abort.error.message jmp rax ; BeginExceptionJob movabs rax, offset .Lburst_abort_Ptr mov rax, qword ptr [rax] movabs rdi, offset .Lburst_abort.error.id movabs rsi, offset .Lburst_abort.error.message jmp rax ; EndExceptionJob movabs rax, offset .Lburst_abort_Ptr mov rax, qword ptr [rax] movabs rdi, offset .Lburst_abort.error.id movabs rsi, offset .Lburst_abort.error.message jmp rax ; MiddleExceptionJob movabs rax, offset .Lburst_abort_Ptr mov rax, qword ptr [rax] movabs rdi, offset .Lburst_abort.error.id movabs rsi, offset .Lburst_abort.error.message jmp 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, Ñледовательно, броÑание иÑÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ð²Ñегда будет приводить к фатальной ошибке.