Search Unity

Question Can you call a burst compiled function from native code?

Discussion in 'Burst' started by rohan-zatun, Mar 18, 2021.

  1. rohan-zatun

    rohan-zatun

    Joined:
    Jan 13, 2021
    Posts:
    14
    I'm working on a native plugin for Unity, and I was wondering if it is possible to call a burst compiled function from native code?
    Something like
    Code (CSharp):
    1.  
    2.  
    3. public delegate byte SomeDelegate(float A, float B);
    4.  
    5. [BurstCompile]
    6. public class SomeClass
    7. {
    8.     [BurstCompile, AOT.MonoPInvokeCallback(SomeDelegate)]
    9.     public static byte PerformSomeCalculations(float A, float B)
    10.     {
    11.         // do something with it
    12.         return output;
    13.     }
    14. }
    15.  
    16. public class SomeOtherClass
    17. {
    18.     [DllImport("MyPlugin")]
    19.     public static extern void InitializeNativeFunction([In] FunctionPointer<SomeDelegate> callbackFunction);
    20.  
    21.     public static void InitializeSomeOtherClass()
    22.     {
    23.         var fnPtr = BurstCompiler.CompileFunctionPointer<SomeDelegate>(SomeClass.PerformSomeCalculations);
    24.  
    25.         InitializeNativeFunction(fnPtr);
    26.         // FunctionPointer struct itself only contains a read-only IntPtr
    27.         // which I assume is the actual burst-compiled function, because
    28.         // the "Invoke" property (yes, property, not a function, uses
    29.         // Marshal.GetDelegateForFunctionPointer to convert it into a delegate.
    30.     }
    31. }
    32.  
    My main aim with this kind of setup is to provide C# extensibility to my native code, but while typing this, another question popped in my head. So, in the end, I have two questions.

    1. Is this doable? Has someone done it before? Any gotchas I should be careful about?
    2. Will this actually reduce interop costs? Or end up increasing them instead?

    As some more context, this is from burst docs:
    And it's what made me think of whether there would be an interop cost in
     
    Last edited: Mar 18, 2021
  2. rohan-zatun

    rohan-zatun

    Joined:
    Jan 13, 2021
    Posts:
    14
    Okay, I did some basic tests, and I think it works, at least on Windows.

    Here's the source code:

    Code (CSharp):
    1.  
    2. using System.Globalization;
    3. using System.Runtime.InteropServices;
    4. using System.Security;
    5. using Unity.Burst;
    6. using UnityEngine;
    7.  
    8. namespace DefaultNamespace
    9. {
    10.     public delegate ulong CalculateFactorialDelegate(ulong input);
    11.  
    12.     public class BurstInteropTester : MonoBehaviour
    13.     {
    14.         [DllImport("MyDLL", CallingConvention = CallingConvention.StdCall), SuppressUnmanagedCodeSecurity]
    15.         private static extern ulong SomethingAddFactorials(ulong a, ulong b);
    16.  
    17.         [DllImport("MyDLL", CallingConvention = CallingConvention.StdCall), SuppressUnmanagedCodeSecurity]
    18.         private static extern void InitSomethingCalculateFactorialManagedOverride(CalculateFactorialDelegate function);
    19.  
    20.         [DllImport("MyDLL", CallingConvention = CallingConvention.StdCall), SuppressUnmanagedCodeSecurity]
    21.         private static extern void InitSomethingCalculateFactorialManagedOverride(FunctionPointer<CalculateFactorialDelegate> function);
    22.  
    23.         [SerializeField] private ulong a = 0;
    24.         [SerializeField] private ulong b = 0;
    25.         [SerializeField] private bool useBurst = false;
    26.         [SerializeField] private ulong calculationCount = 100;
    27.  
    28.         private void Awake()
    29.         {
    30.             InitSomethingCalculateFactorialManagedOverride(Library.CalculateFactorial);
    31.             a = 2;
    32.             b = 2;
    33.             useBurst = false;
    34.             calculationCount = 1000000;
    35.         }
    36.  
    37.         private void OnGUI()
    38.         {
    39.             using (new GUILayout.HorizontalScope())
    40.             {
    41.                 if (ulong.TryParse(GUILayout.TextField(a.ToString(CultureInfo.InvariantCulture)), out var aTemp))
    42.                 {
    43.                     a = aTemp;
    44.                 }
    45.  
    46.                 if (ulong.TryParse(GUILayout.TextField(b.ToString(CultureInfo.InvariantCulture)), out var bTemp))
    47.                 {
    48.                     b = bTemp;
    49.                 }
    50.             }
    51.  
    52.             var newValue = GUILayout.Toggle(useBurst, "Use Burst");
    53.             if (useBurst != newValue)
    54.             {
    55.                 useBurst = newValue;
    56.                 if (useBurst)
    57.                 {
    58.                     var function = BurstCompiler.CompileFunctionPointer<CalculateFactorialDelegate>(Library.CalculateFactorial);
    59.                     InitSomethingCalculateFactorialManagedOverride(function);
    60.                 }
    61.                 else InitSomethingCalculateFactorialManagedOverride(Library.CalculateFactorial);
    62.             }
    63.  
    64.             if (ulong.TryParse(GUILayout.TextField(calculationCount.ToString(CultureInfo.InvariantCulture)), out var calcCountTemp))
    65.             {
    66.                 calculationCount = calcCountTemp;
    67.             }
    68.  
    69.             ulong result = 0;
    70.             for (ulong i = 0; i < calculationCount; i++)
    71.             {
    72.                 result = SomethingAddFactorials(a, b);
    73.             }
    74.  
    75.             GUILayout.Label($"Result: {result}");
    76.             GUILayout.Label($"FrameTime: {Time.deltaTime * 1000} milliseconds");
    77.         }
    78.  
    79.         [BurstCompile]
    80.         public static class Library
    81.         {
    82.             [BurstCompile]
    83.             [AOT.MonoPInvokeCallback(typeof(CalculateFactorialDelegate))]
    84.             public static ulong CalculateFactorial(ulong input)
    85.             {
    86.                 var output = input;
    87.                 for (var i = input - 1; i > 1; --i)
    88.                 {
    89.                     output *= i;
    90.                 }
    91.  
    92.                 return output;
    93.             }
    94.         }
    95.     }
    96. }
    97.  
    Code (C++):
    1.  
    2. struct Something
    3. {
    4.     static unsigned long long CalculateFactorial(unsigned long long Input);
    5.     static unsigned long long AddFactorials(unsigned long long A, unsigned long long B);
    6. };
    7.  
    8. unsigned long long (__stdcall*SomethingCalculateFactorialManagedOverride)(unsigned long long Input) = nullptr;
    9.  
    10. extern "C" void __stdcall InitSomethingCalculateFactorialManagedOverride(unsigned long long (__stdcall*InDelegate)(unsigned long long Input))
    11. {
    12.     SomethingCalculateFactorialManagedOverride = InDelegate;
    13. }
    14.  
    15. unsigned long long Something::CalculateFactorial(unsigned long long Input)
    16. {
    17.     return SomethingCalculateFactorialManagedOverride(Input);
    18. }
    19.  
    20. extern "C" unsigned long long __stdcall SomethingAddFactorials(const unsigned long long A, const unsigned long long B)
    21. {
    22.     return Something::AddFactorials(A, B);
    23. }
    24.  
    25. unsigned long long Something::AddFactorials(const unsigned long long A, const unsigned long long B)
    26. {
    27.     return CalculateFactorial(A) + CalculateFactorial(B);
    28. }
    29.  
    Results (in frame-time, rough averages):
    • Editor (Win64):
      • useBurst = false: 66ms
      • useBurst = true: 33ms
    • Standalone Player (Mono-Win64):
      • useBurst = false: 60ms
      • useBurst = true: 30ms
    • Standalone Player (IL2CPP-Win64):
      • useBurst = false: 30ms
      • useBurst = true: 30ms

    Thinking about it, it's not terribly surprising that IL2CPP does not get affected, but there's still a significant improvement for Mono/Editor.