(Russian translation from English by Maxim Voloshin)
Обычно, задачи(job), Ñкомпилированные Burst компилÑтором, не могут иÑпользовать управлÑемые маÑÑивы, но еÑÑ‚ÑŒ иÑключение Ð´Ð»Ñ static readonly
полей. Ðо, иногда, Ñто может быть опаÑно, когда именно мы узнаем ÑегоднÑ.
Как Ñто предполагалоÑÑŒ
Давайте поÑмотрим на пример допуÑтимого иÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ static readonly
управлÑемых маÑÑивов в Burst: таблицу замен. Создадим проÑтую задачу, ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ Ñчитывает целые чиÑла из маÑÑива:
[BurstCompile]struct ReadManagedArrayJob : IJob{ public static readonly int[] Array = { 1, 2, 3 }; public int Index; public NativeArray<int> Result; public void Execute() Result[0] = Array[Index];}
ВоÑпользуемÑÑ Burst инÑпектором, и поÑмотрим на Ñгенерированный аÑÑемблерный x86 код:
mov rax, qword ptr [rdi + 8]movsxd rcx, dword ptr [rdi]movabs rdx, offset ".LSystem.Int32[] ReadManagedArrayJob::Array"mov ecx, dword ptr [rdx + 4*rcx]mov dword ptr [rax], ecx<pre>Ðто проÑÑ‚Ð°Ñ Ð¸Ð½Ð´ÐµÐºÑÐ°Ñ†Ð¸Ñ Ð² Ñекции данных иÑполнÑемого файла <code>.LSystem.Int32[] ReadManagedArrayJob::Array</code>, которую можно увидеть ниже:<pre lang="asm">".LSystem.Int32[] ReadManagedArrayJob::Array": .long 1 .long 2 .long 3 .size ".LSystem.Int32[] ReadManagedArrayJob::Array", 12 .section .debug_str,"MS",@progbits,1
Обратите внимание, что ÑÐµÐºÑ†Ð¸Ñ Ñодержит только то, что мы помеÑтили в маÑÑив: 1
, 2
, и 3
.
Writing to the Array
Теперь поÑмотрим, что произойдет еÑли мы нарушим правила и попробуем что-нибудь запиÑать в управлÑемый маÑÑив:
[BurstCompile]struct WriteManagedArrayJob : IJob{ public static readonly int[] Array = { 1, 2, 3 }; public int Index; public NativeArray<int> Result; public void Execute() Array[Index] = Result[0];}
КонÑоль Unity не Ñодержит ошибок, но Burst инÑпектор говорит об ошибке компилÑции:
/path/to/project/Assets/TestScript.cs(31,3): error: Accessing a managed array is not supported by burst at WriteManagedArrayJob.Execute(WriteManagedArrayJob* this) (at /path/to/project/Assets/TestScript.cs:31) at Unity.Jobs.IJobExtensions.JobStruct`1.Execute(ref WriteManagedArrayJob data, System.IntPtr additionalPtr, System.IntPtr bufferRangePatchData, ref Unity.Jobs.LowLevel.Unsafe.JobRanges ranges, int jobIndex) (at /Users/builduser/buildslave/unity/build/Runtime/Jobs/Managed/IJob.cs:30) While compiling job: System.Void Unity.Jobs.IJobExtensions/JobStruct`1::Execute(T&,System.IntPtr,System.IntPtr,Unity.Jobs.LowLevel.Unsafe.JobRanges&,System.Int32)
Мы получим туже Ñамую ошибку еÑли попытаемÑÑ Ñобрать проект, но в тоже времÑ, можно легко иÑпользовать задачу в редакторе не Ð¿Ð¾Ð´Ð¾Ð·Ñ€ÐµÐ²Ð°Ñ Ð¾ наличии каких-либо проблем. Кроме того, Ñообщение об ошибке не очень информативно, Ñ‚.к. доÑтуп к управлÑемому маÑÑиву поддерживаетÑÑ Burst, но не Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñи, как пытаетÑÑ Ñделать Ñтот код.
Передача маÑÑивов
Теперь нарушим другое правило и попробуем передать маÑÑив в функцию как параметр:
[BurstCompile]struct PassManagedArrayJob : IJob{ public static readonly int[] Array = { 1, 2, 3 }; public int Index; public NativeArray<int> Result; public void Execute() Do(Array); public void Do(int[] a) Result[0] = a[Index];}
Ð’ данном Ñлучае ошибки также отÑутÑтвуют в конÑоли, но приÑутÑтвуют в Burst инÑпекторе и во Ð²Ñ€ÐµÐ¼Ñ Ñборки проекта:
/path/to/project/Assets/TestScript.cs(45,3): error: The managed function `PassManagedArrayJob.Do(PassManagedArrayJob* this, int[] a)` is not supported by burst at PassManagedArrayJob.Execute(PassManagedArrayJob* this) (at /path/to/project/Assets/TestScript.cs:45) at Unity.Jobs.IJobExtensions.JobStruct`1.Execute(ref PassManagedArrayJob data, System.IntPtr additionalPtr, System.IntPtr bufferRangePatchData, ref Unity.Jobs.LowLevel.Unsafe.JobRanges ranges, int jobIndex) (at /Users/builduser/buildslave/unity/build/Runtime/Jobs/Managed/IJob.cs:30) While compiling job: System.Void Unity.Jobs.IJobExtensions/JobStruct`1::Execute(T&,System.IntPtr,System.IntPtr,Unity.Jobs.LowLevel.Unsafe.JobRanges&,System.Int32)
Ðто Ñообщение не говорит почему Ñ„ÑƒÐ½ÐºÑ†Ð¸Ñ Ð½Ðµ поддерживаетÑÑ Burst, но из Ñигнатуры функции должно быть понÑтно, что она иÑпользует запрещенный управлÑемый маÑÑив.
ЗапиÑÑŒ извне
Ð ÑÐµÐ¹Ñ‡Ð°Ñ Ð¼Ñ‹ нарушим правило, глаÑÑщее, что маÑÑив Ð½ÐµÐ»ÑŒÐ·Ñ Ð¼ÐµÐ½ÑÑ‚ÑŒ Ñнаружи задачи:
class TestScript : MonoBehaviour{ void Start() ReadManagedArrayJob.Array[0] = 100; NativeArray<int> array = new NativeArray<int>(1, Allocator.TempJob); new ReadManagedArrayJob { Result = array, Index = 0 }.Run(); print(array[0]); array.Dispose();}
ЗдеÑÑŒ мы перезапиÑываем 1
в первом Ñлементе маÑÑива и, затем, запуÑкаем задачу, ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ Ñ‡Ð¸Ñ‚Ð°ÐµÑ‚ первый Ñлемент маÑÑива и выводит его значение в конÑоль. Что будет выведено?
100
Ð£Ñ‡Ð¸Ñ‚Ñ‹Ð²Ð°Ñ Ð°ÑÑемблерный код, приведенный выше, Ñто не имеет ÑмыÑла. Задача будет Ñчитывать данные из конÑтантного маÑÑива, раÑположенного в Ñекции данных иÑполнÑемого файла. Как он может быть изменен во Ð²Ñ€ÐµÐ¼Ñ Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ TestScript.Start
?
Теперь, мы Ñоберем проект, запуÑтим его в macOS и получим результат:
1
Мы видим другой результат! Задача, ÑÐºÐ¾Ð¼Ð¿Ð¸Ð»Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð½Ð°Ñ Burst и иÑÐ¿Ð¾Ð»ÑŒÐ·ÑƒÐµÐ¼Ð°Ñ Ð² билде, отличаетÑÑ Ð¿Ð¾ функционалу от верÑии задачи, иÑпользуемой в редакторе. ОпÑÑ‚ÑŒ-таки, может пройти какое-то времÑ, прежде чем мы поймем, что допуÑтили ошибку. Хуже вÑего, что в редакторе нет ни предупреждений компилÑтора, ни ошибок, ни даже Ñообщений во Ð²Ñ€ÐµÐ¼Ñ Ñборки билда, говорÑщих о проблеме. ОÑторожнее!
МаÑÑив Ñтруктур
Давайте попробуем иÑпользовать маÑÑив Ñтруктур:
struct TestStruct{ public int Value; public TestStruct(int value) Value = value;}[BurstCompile]struct ManagedArrayOfStructsJob : IJob{ public static readonly TestStruct[] Array = { new TestStruct(1), new TestStruct(2) }; public int Index; public NativeArray<int> Result; public void Execute() Result[0] = Array[Index].Value;}
Ðтот функционал Ñквивалентен ReadManagedArrayJob
и мы видим точно такой же аÑÑемблерный код в Burst инÑпекторе:
mov rax, qword ptr [rdi + 8]movsxd rcx, dword ptr [rdi]movabs rdx, offset ".LTestStruct[] ManagedArrayOfStructsJob::Array"mov ecx, dword ptr [rdx + 4*rcx]mov dword ptr [rax], ecx
Секции данных, тоже выглÑдÑÑ‚ похожим образом:
".LTestStruct[] ManagedArrayOfStructsJob::Array": .long 1 .long 2 .size ".LTestStruct[] ManagedArrayOfStructsJob::Array", 8 .section .debug_str,"MS",@progbits,1
Таким образом, при иÑпользовании Ñтруктур и, в чаÑтноÑти, чтении данных из них, нет дополнительных затрат производительноÑти.
Структуры Ñ ÑƒÑловиÑми
Давайте вернемÑÑ Ðº правилам, которые мы нарушали, и воÑпользуемÑÑ ÑƒÑловиÑми, в данном Ñлучае тернарным оператором внутри конÑтруктора нашей Ñтруктуры:
struct TestStructWithConditional{ public int Value; public TestStructWithConditional(bool use1) Value = use1 ? 1 : 2;}[BurstCompile]struct ManagedArrayOfStructsWithConditionalJob : IJob{ public static readonly TestStructWithConditional[] Array = { new TestStructWithConditional(true), new TestStructWithConditional(false) }; public int Index; public NativeArray<int> Result; public void Execute() Result[0] = Array[Index].Value;}
Burst инÑпектор Ñодержит четыре ошибки:
/path/to/project/Assets/TestScript.cs(104,3): error: Accessing a managed array is not supported by burst at ManagedArrayOfStructsWithConditionalJob.Execute(ManagedArrayOfStructsWithConditionalJob* this) (at /path/to/project/Assets/TestScript.cs:104) at Unity.Jobs.IJobExtensions.JobStruct`1.Execute(ref ManagedArrayOfStructsWithConditionalJob data, System.IntPtr additionalPtr, System.IntPtr bufferRangePatchData, ref Unity.Jobs.LowLevel.Unsafe.JobRanges ranges, int jobIndex) (at /Users/builduser/buildslave/unity/build/Runtime/Jobs/Managed/IJob.cs:30) While compiling job: System.Void Unity.Jobs.IJobExtensions/JobStruct`1::Execute(T&,System.IntPtr,System.IntPtr,Unity.Jobs.LowLevel.Unsafe.JobRanges&,System.Int32)/path/to/project/Assets/TestScript.cs(94,2): error: Creating a managed array `TestStructWithConditional[]` is not supported by burst at ManagedArrayOfStructsWithConditionalJob..cctor() (at /path/to/project/Assets/TestScript.cs:94) at ManagedArrayOfStructsWithConditionalJob.Execute(ManagedArrayOfStructsWithConditionalJob* this) (at /path/to/project/Assets/TestScript.cs:103) at Unity.Jobs.IJobExtensions.JobStruct`1.Execute(ref ManagedArrayOfStructsWithConditionalJob data, System.IntPtr additionalPtr, System.IntPtr bufferRangePatchData, ref Unity.Jobs.LowLevel.Unsafe.JobRanges ranges, int jobIndex) (at /Users/builduser/buildslave/unity/build/Runtime/Jobs/Managed/IJob.cs:30) While compiling job: System.Void Unity.Jobs.IJobExtensions/JobStruct`1::Execute(T&,System.IntPtr,System.IntPtr,Unity.Jobs.LowLevel.Unsafe.JobRanges&,System.Int32)/path/to/project/Assets/TestScript.cs(94,2): error: Accessing a managed array is not supported by burst at ManagedArrayOfStructsWithConditionalJob..cctor() (at /path/to/project/Assets/TestScript.cs:94) at ManagedArrayOfStructsWithConditionalJob.Execute(ManagedArrayOfStructsWithConditionalJob* this) (at /path/to/project/Assets/TestScript.cs:103) at Unity.Jobs.IJobExtensions.JobStruct`1.Execute(ref ManagedArrayOfStructsWithConditionalJob data, System.IntPtr additionalPtr, System.IntPtr bufferRangePatchData, ref Unity.Jobs.LowLevel.Unsafe.JobRanges ranges, int jobIndex) (at /Users/builduser/buildslave/unity/build/Runtime/Jobs/Managed/IJob.cs:30) While compiling job: System.Void Unity.Jobs.IJobExtensions/JobStruct`1::Execute(T&,System.IntPtr,System.IntPtr,Unity.Jobs.LowLevel.Unsafe.JobRanges&,System.Int32)/path/to/project/Assets/TestScript.cs(94,2): error: Accessing a managed array is not supported by burst at ManagedArrayOfStructsWithConditionalJob..cctor() (at /path/to/project/Assets/TestScript.cs:94) at ManagedArrayOfStructsWithConditionalJob.Execute(ManagedArrayOfStructsWithConditionalJob* this) (at /path/to/project/Assets/TestScript.cs:103) at Unity.Jobs.IJobExtensions.JobStruct`1.Execute(ref ManagedArrayOfStructsWithConditionalJob data, System.IntPtr additionalPtr, System.IntPtr bufferRangePatchData, ref Unity.Jobs.LowLevel.Unsafe.JobRanges ranges, int jobIndex) (at /Users/builduser/buildslave/unity/build/Runtime/Jobs/Managed/IJob.cs:30) While compiling job: System.Void Unity.Jobs.IJobExtensions/JobStruct`1::Execute(T&,System.IntPtr,System.IntPtr,Unity.Jobs.LowLevel.Unsafe.JobRanges&,System.Int32)
Ð’Ñе ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ñодержат не ÑовÑем точные опиÑаниÑ. Три из них говорÑÑ‚, что управлÑемые маÑÑивы недоÑтупны Ð´Ð»Ñ Burst, четвертое, что управлÑемые маÑÑивы не могут быть Ñозданы Burst. Ðа Ñамом деле могут, но не тогда, когда их Ñлементы ÑвлÑÑŽÑ‚ÑÑ Ñтруктурами Ñ ÐºÐ¾Ð´Ð¾Ð¼ уÑловий.
И Ñнова, ошибки отÑутÑтвуют в конÑоли редактора. Ðти ошибки приÑутÑтвуют только в Burst инÑпекторе или при Ñборке проекта, и могут оÑтатьÑÑ Ð½ÐµÐ·Ð°Ð¼ÐµÑ‡ÐµÐ½Ð½Ñ‹Ð¼Ð¸.
Структуры Ñ Ð˜ÑключениÑми(Exceptions)
И, наконец, попробуем броÑить иÑключение внутри конÑтруктора Ñтруктуры:
struct TestStructWithException{ public int Value; public TestStructWithException(int value) Value = value; throw new Exception("boom");}[BurstCompile]struct ManagedArrayOfStructsWithExceptionJob : IJob{ public static readonly TestStructWithException[] Array = { new TestStructWithException(1), new TestStructWithException(2) }; public int Index; public NativeArray<int> Result; public void Execute() Result[0] = Array[Index].Value;}
Можно было ожидать такой же результат, как и в Ñлучае Ñ ÑƒÑловиÑми, но Ñто не так:
mov rax, qword ptr [rdi + 8] movsxd rcx, dword ptr [rdi] movabs rdx, offset ".LTestStructWithException[] ManagedArrayOfStructsWithExceptionJob::Array" mov ecx, dword ptr [rdx + 4*rcx] mov dword ptr [rax], ecx
И ÑÐµÐºÑ†Ð¸Ñ Ð´Ð°Ð½Ð½Ñ‹Ñ…
".LTestStructWithException[] ManagedArrayOfStructsWithExceptionJob::Array": .long 1 .long 2 .size ".LTestStructWithException[] ManagedArrayOfStructsWithExceptionJob::Array", 8 .type .Lburst_abort.function.string,@object .section .rodata,"a",@progbits
Ð’Ñе Ñледы иÑключений были удалены при компилÑции. Как будто мы вообще не пиÑали Ñтроку Ñо throw
.
Что же произойдет, еÑли мы попытаемÑÑ Ð·Ð°Ð¿ÑƒÑтить Ñто?
class TestScript : MonoBehaviour{ void Start() NativeArray<int> array = new NativeArray<int>(1, Allocator.TempJob); new ManagedArrayOfStructsWithExceptionJob { Result = array, Index = 0 }.Run(); print(array[0]); array.Dispose();}
Ð’ конÑоли редактора мы увидим брошенное иÑключение:
Exception: boomTestStructWithException..ctor (System.Int32 value) (at Assets/TestScript.cs:115)ManagedArrayOfStructsWithExceptionJob..cctor () (at Assets/TestScript.cs:122)Rethrow as TypeInitializationException: The type initializer for 'ManagedArrayOfStructsWithExceptionJob' threw an exception.Unity.Jobs.IJobExtensions+JobStruct`1[T].Execute (T& data, System.IntPtr additionalPtr, System.IntPtr bufferRangePatchData, Unity.Jobs.LowLevel.Unsafe.JobRanges& ranges, System.Int32 jobIndex) (at /Users/builduser/buildslave/unity/build/Runtime/Jobs/Managed/IJob.cs:30)Unity.Jobs.LowLevel.Unsafe.JobsUtility:Schedule_Injected(JobScheduleParameters&, JobHandle&)Unity.Jobs.LowLevel.Unsafe.JobsUtility:Schedule(JobScheduleParameters&)Unity.Jobs.IJobExtensions:Run(ManagedArrayOfStructsWithExceptionJob) (at /Users/builduser/buildslave/unity/build/Runtime/Jobs/Managed/IJob.cs:43)TestScript:Start() (at Assets/TestScript.cs:147)
Ð’ Ñборке Ð´Ð»Ñ macOS мы получим результат:
1
ОпÑÑ‚ÑŒ же никаких предупреждений и ошибок компилÑтора ни во Ð²Ñ€ÐµÐ¼Ñ ÐºÐ¾Ð¼Ð¿Ð¸Ð»Ñции, ни при запуÑке, ни при Ñборке проекта. Будьте внимательны!
Заключение
УправлÑемые маÑÑивы могут быть иÑпользованы ÑовмеÑтно Ñ Burst, но Ñ Ð¼Ð½Ð¾Ð³Ð¸Ð¼Ð¸ ограничениÑми. Ðти Ð¾Ð³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¾ÑвлÑÑŽÑ‚ ÑÐµÐ±Ñ Ñлучайным образом. Иногда мы получаем ошибки компилÑции когда Ñобираем проект или Ñмотрим в Burst инÑпектор, но иногда нет. Иногда поведение в редакторе и в билде различны, без каких-либо намеков от Unity о проблемах в коде задачи. Таким образом, важно знать правила иÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ ÑƒÐ¿Ñ€Ð°Ð²Ð»Ñемых маÑÑивов в Burst и поддерживать выÑокий уровень диÑциплины при их иÑпользовании!