Search Unity

Official When, where, and why to put [BurstCompile], with mild under-the-hood explanation

Discussion in 'Entity Component System' started by elliotc-unity, Oct 4, 2022.

  1. elliotc-unity

    elliotc-unity

    Unity Technologies

    Joined:
    Nov 5, 2015
    Posts:
    230
    People are very often confused about where they should put [BurstCompile], and what gets bursted when they put it where, so I figured I'd write something up explaining everything in one place.

    The first two things to know, from which all else flows, but seems a bit surprising at first:
    1. Burst can only ever burst static methods, period, end of story. If you want this to happen outside of the special cases outlined below, just put [BurstCompile] on a static method and on its declaring type.* When you call this method, it will call the bursted version automatically, assuming burst compilation didn't fail.
    2. Unless you try really hard, all C# called from bursted code is bursted.

    If #1 above is true, what's the deal with IJobWhatever's Execute method (which is not static for any value of IJobWhatever), and ISystem's OnUpdate & friends (which are also not static), you ask?
    Answer: Different magic for each.

    In the case of jobs, each job interface is annotated with an attribute like [JobProducerType(typeof(SomeOtherSpecialSecretType))]

    For example, you can see here: https://github.com/Unity-Technologies/UnityCsReference/blob/2022.2/Runtime/Jobs/Managed/IJob.cs#L36

    that the definition of the IJob interface is tagged with [JobProducerType(typeof(IJobExtensions.JobStruct<>))]

    If you put [BurstCompile] on a type that implements an interface, Burst knows to look for this attribute on the interface, go find that other type (in this case IJobExtensions.JobStruct), make sure it's generic (it is), specialize it with the type of your specific job struct (making it IJobExtensions.JobStruct<YourPersonalJob>), and then compile the static Execute method on that other type. The static Execute method on that other type will call your personal Execute with some arguments and in some context, and your personal Execute will be bursted as part of that larger static function (see point 2 at the top of this post).

    This is also why you sometimes see code that isn't yours in the outer loop of the burst inspector.

    In the case of ISystem OnWhatever callbacks, we run an ILPostProcessor that specially generates static functions that take a void* for the this argument plus the ref SystemState, and we cast the void* back to the particular system type and then call the non-static OnUpdate on that. Burst then bursts our static wrappers, and we make sure to call the static wrappers when we update systems.

    Corollaries of this:

    - Non-Execute methods of job structs that have [BurstCompile] on the job struct are NOT bursted when called from non-bursted code (unless they're static and you put [BurstCompile] on them specifically as well as on the job struct). But, as mentioned, all C# called from bursted code is bursted.

    - The ISystem magic makes it seem like you can burst non-static functions because of those special cases, but you can't in general.

    Happy bursting!


    * The reason you need to put [BurstCompile] on the declaring type as well as the static method is to reduce the time Burst needs to take scanning for what to burst. If we didn't require it on the type, it would have to go to every method of every type checking for attributes, whereas this way it can just check the type itself and move on if the type doesn't have the attribute. [Edit 5/19/2023: This used to apply to ISystem's as well, but a touch of extra magic was added there so you only need it on the OnWhatever functions, not on the type itself. The extra magic is basically, we're processing all the ISystems for codegen anyway, so it's no extra bother for us to check the OnWhatever functions for [BurstCompile], and slap [BurstCompile] on the type for you while we're at it.]


    [edit shortly after]
    If you are ever worried about whether some particular code is being run bursted or unbursted, you can use the following pattern:

    Code (csharp):
    1. [BurstDiscard]
    2. void SetFalseIfUnBursted(ref bool val)
    3. {
    4.      val = false;
    5. }
    6.  
    7. bool IsBursted()
    8. {
    9.     bool ret = true;
    10.     SetFalseIfUnBursted(ref ret);
    11.     return ret;
    12. }
    It is perhaps obviously inadvisable to ever change behavior based on the value of this function, because then when you turn off burst your behavior will change and you will immediately go insane.
     
    Last edited: May 19, 2023
  2. optimise

    optimise

    Joined:
    Jan 22, 2014
    Posts:
    2,129
    Thanks for sharing. But still I hope official can figure out the better solution to further reduce typing like eliminate [BurstCompile] and remove ValueRO and ValueRW for foreach at ISystem while still offer same codegen and compilation speed.
     
  3. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    ISystem's .ForEach (and probably Query API alike) are burst compiled by default, unless manually stated otherwise. (which is not possible for ISystems, perhaps only partially via [BurstDiscard], which I haven't tested);

    Its the OnUpdate method that aren't bursted by default (at least, in 0.51 that is).
    However, it can be burst compiled by marking it manually via attribute. Not everything was compatible though as of 0.51 (jobs / collection disposal, safety system, and such).

    So TL;DR:
    - SystemBase / ISystem -> .Run / .Schedule / .ScheduleParallel - Burst Compiled by default, no need to mark static methods if those are called from ForEach / IJobs (in some cases would actually throw errors!). Unless .WithoutBurst()
    - Static methods can be burst compiled even when called outside Entities context when marked as [BurstCompile] (-> results in a function pointer)
    - Jobs can be marked with [BurstCompile] to be bursted from non-system context;
    - If in doubt - check Burst Inspector.


    Going forward (1.0+), I think it makes sense to make OnUpdate burst compilable by default as well. Though, I'm not sure how managed code in unmanaged systems should be handled in this case.
     
    Last edited: Oct 4, 2022
  4. elliotc-unity

    elliotc-unity

    Unity Technologies

    Joined:
    Nov 5, 2015
    Posts:
    230
    In 1.0, ISystem does not support Entities.ForEach. That said, Entities.ForEach I believe has always defaulted to burst on in both SystemBase and ISystem.

    ISystem methods are never bursted by default today in either 1.0 or 0.51; you have to put [BurstCompile] on both the type and each callback method you want bursted, which works via the aforementioned ILPostProcessor (ILPP).
     
    tmonestudio and xVergilx like this.
  5. Rupture13

    Rupture13

    Joined:
    Apr 12, 2016
    Posts:
    130
    In the spirit of clearing up confusion, I'd also like to ask what the difference is between the
    [BurstCompile]
    attribute, and the
    [BurstCompatible]
    attribute. Which should be used over the other in what scenarios?
     
  6. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,683
    One is direct marker of - "Hey, make this thing burstable" and also provides parameters of how to compile that (precision, synch, etc.) second is indicator that method is compatible (or not in case of NotBurstCompatible when containing type is burst compatible but something inside - not) with burst which also provides additional configuration of that capability like CompileTarget, RequiredUnityDefine, GenericTypeArguments, and generated test validates usages by that attribute and parameters. This second attribute doesn't "make" code bursted, only BurstCompileAttribute does (of course in bounds described by @elliotc-unity) :)
     
    Rupture13 likes this.
  7. Rupture13

    Rupture13

    Joined:
    Apr 12, 2016
    Posts:
    130
    Alright, so the
    [BurstCompile]
    attribute is the more important and more functional one, and is the one I should definitely use for all my static methods that I want to be bursted, if I understand correctly.

    Is it a good idea to always combine that one with a
    [BurstCompatible]
    attribute, for the generated validation? Or do you reckon it's not really useful to add it at all (except perhaps in rare cases where you'd actually make use of the attribute parameters) and just use
    [BurstCompile]
    alone?
     
  8. elliotc-unity

    elliotc-unity

    Unity Technologies

    Joined:
    Nov 5, 2015
    Posts:
    230
    In 1.0 I renamed BurstCompatible to what it actually means, which is GenerateTestsForBurstCompatibility.

    However, I think it is unfortunate that we had to expose it publicly, because the only way it gets actually enforced is if you run some hard-to-find code to get it to generate the tests, AND then build a player with those tests and see if the player build fails.

    So I don’t recommend its usage unless you’re really going to do all that schlep. If you just want to communicate to readers of your code that you intend a method to be burst compatible, I suggest a comment.

    For actually bursting stuff, [BurstCompile] is indeed all that matters.
     
  9. Qriva

    Qriva

    Joined:
    Jun 30, 2019
    Posts:
    1,307
    Is code called from bursted code compiled in some special way? If I do this:
    Code (CSharp):
    1. [BurstCompile]
    2. public static partial class MyBurstedMethods
    3. {      
    4.         [BurstCompile]
    5.         public static float3 BurstedMethod(float3 x, float3 y)
    6.         {
    7.             return x + y;
    8.         }
    9. }
    It will not compile as I use structs as parameters and returned type, meanwhile the same function called from some Execute will be completely fine.
     
  10. TheOtherMonarch

    TheOtherMonarch

    Joined:
    Jul 28, 2012
    Posts:
    866
    So Bakers cannot be [BurstCompile] correct?
     
  11. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,683
    Well, technically Baker<T> itself can't be bursted like ISystem, but, jobs is there, direct Burst calls is there, and you can make mainthreaded bursted jobs, multithreaded bursted jobs for some heavy calculations in Baker<T>, moreover BakingSystem is a system which can be SystemBase with bursted jobs or ISystem (and as result completely bursted)
    Code (CSharp):
    1. [WorldSystemFilter(WorldSystemFilterFlags.BakingSystem)]
    2.     [UpdateInGroup(typeof(PostBakingSystemGroup))]
    3.     [BurstCompile]
    4.     public partial struct PostBakeSystem : ISystem
    5.     {
    6.         public void OnCreate(ref SystemState state) { }
    7.  
    8.         public void OnDestroy(ref SystemState state) { }
    9.  
    10.         [BurstCompile]
    11.         public void OnUpdate(ref  SystemState state)
    12.         {
    13.  
    14.             foreach (var transformAspect in SystemAPI.Query<TransformAspect>())
    15.             {
    16.                 transformAspect.Position = -1;
    17.             }
    18.         }
    19.     }
    upload_2022-10-18_10-7-50.png

    Authoring:
    upload_2022-10-18_10-9-55.png

    Runtime (processed by bursted BakingSystem):
    upload_2022-10-18_10-10-21.png
     
    Elapotp, tmonestudio, Occuros and 4 others like this.
  12. Mortuus17

    Mortuus17

    Joined:
    Jan 6, 2020
    Posts:
    105
    Im just here to make the
    IsBursted
    function a one-liner, a.k.a. optimizing aesthetics:

    Code (CSharp):
    1. public static bool IsBursted => Unity.Burst.CompilerServices.Constant.IsConstantExpression(1);
    You can go on now :p
     
    apkdev, ThatDan123 and Deleted User like this.
  13. piginaust

    piginaust

    Joined:
    May 23, 2022
    Posts:
    14
    Sorry, I am still confused.

    I understand from examples that I need to put burstcompile in Isystem various places.

    How about IAspect? If I have a function in aspect:

    Public float3 GetPos()
    {...}

    Do I need to put [burstcomplile] above it? (I know i can do it with getter...just an example for discussion).

    Also, for helper/utility class, do I need to put burstcompile also?

    E.g. In a job,

    int value = GameUtility.GetABC() <----do I need to put burstcompile above "Public Static Class GameUtility{...} AND above "Public Static GetABC()" static method?

    Thank you.
     
  14. Spy-Master

    Spy-Master

    Joined:
    Aug 4, 2022
    Posts:
    585
    If you’re calling the methods from inside Burst-compiled jobs / systems / Burst-compiled static methods, there’s nothing that needs to be done. All the applicable code will have Burst variants prepared for you, because Burst code is basically viral and requires everything that gets executed from a starting point to be Burst-compiled.
    This doesn’t affect execution of the methods when run by normal managed code. You’ll effectively get two implementations for the method that run depending on whether it’s being called from a Burst-compiled job / system / other entry point or just normal managed code. If you need to make sure some specific algorithm (only static methods) works in Burst when directly called from some normal manager code, you can add BurstCompile to it which will let the framework patch the calls in the IL to the Burst version. However, it may not be worth doing this for small methods.
    further reading
    https://docs.unity3d.com/Packages/c...al/docs/AdvancedUsages.html#function-pointers
     
    Rupture13 and piginaust like this.
  15. jwvanderbeck

    jwvanderbeck

    Joined:
    Dec 4, 2014
    Posts:
    825
    Sorry for the necro, but was just reading this and curious if this still applies:

    * The reason you need to put [BurstCompile] on the declaring type as well as the static method is to reduce the time Burst needs to take scanning for what to burst. If we didn't require it on the type, it would have to go to every method of every type checking for attributes, whereas this way it can just check the type itself and move on if the type doesn't have the attribute. This applies to ISystem's as well.

    Because if so, does that mean that the scaffolding generated by Rider for EG ISystem is actually incorrect because it doesn't mark the type as well?

    Code (CSharp):
    1.     public partial struct TestSystem : ISystem
    2.     {
    3.         [BurstCompile]
    4.         public void OnCreate(ref SystemState state)
    5.         {
    6.            
    7.         }
    8.  
    9.         [BurstCompile]
    10.         public void OnUpdate(ref SystemState state)
    11.         {
    12.  
    13.         }
    14.  
    15.         [BurstCompile]
    16.         public void OnDestroy(ref SystemState state)
    17.         {
    18.  
    19.         }
    20.     }
    21.  
     
  16. elliotc-unity

    elliotc-unity

    Unity Technologies

    Joined:
    Nov 5, 2015
    Posts:
    230
    No, that's old. I'll edit the post to fix it now that it doesn't apply to ISystem (but does apply to normal static methods on non-special types).
     
    mariandev and jwvanderbeck like this.
  17. GuiVio

    GuiVio

    Joined:
    Jan 25, 2023
    Posts:
    3
    Doesn't this mean there could be a way to provide a "burst analysis" that spits out missing [BurstCompile] attributes? (and an option to show such warnings upon making builds for example)