Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

PInvoke, mono_add_internal_call, C and C#

Discussion in 'iOS and tvOS' started by Game-Whiz, Mar 5, 2013.

  1. Game-Whiz

    Game-Whiz

    Joined:
    Nov 10, 2011
    Posts:
    122
    Hi there,

    I'm posting this since it may be useful to someone else and also as a way of verifying my conclusions. This is in no way meant as a C is better or C# is better, it's just data that I used to make a decision.

    Based on the findings below, I decided to write a wrapper to the Chipmunk physics library, rather than port it to C# (the summary is that the calling overhead to C/Objective C is negligible, particularly when compared to the performance of C#, which is this case is two orders of magnitude below C).

    I also found out that Unity isn't using P/Invoke, or if it is, then its performance when compared to mono's internal call is very similar.

    So, here's the code I used.

    C#:
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System.Runtime.CompilerServices;
    5. using System.Runtime.InteropServices;
    6. using System;
    7.  
    8.  
    9. public class Hello
    10. {  
    11.     static int val=0;
    12.        
    13.     [MethodImplAttribute(MethodImplOptions.InternalCall)]
    14.     public extern static int RetInt(); 
    15.    
    16.     [DllImport ("__Internal")]
    17.     public extern static int RetIntSlow(); 
    18.    
    19.     [DllImport ("__Internal")]
    20.     public extern static void TestIntC(int millions);  
    21.    
    22.     public static int RetSqrtCSharp()
    23.     {
    24.         val++;
    25.         return (int)Mathf.Sqrt(val);   
    26.     }  
    27.    
    28. }
    29.  
    30.  
    31. public class InteropCallDemo : MonoBehaviour
    32. {  
    33.     delegate int IntegerRet();
    34.    
    35.     //Quick method to call an int return method some million times
    36.     void CallMillionTimes(int numMillions,IntegerRet method)
    37.     {
    38.         DateTime time=System.DateTime.Now;
    39.         int total=0;
    40.         for (int n=0;n<numMillions*1000000;n++)
    41.         {
    42.             total+=method();   
    43.         }
    44.         print("Total="+total); //Using total, so the compiler doesn't optimize and gets rid of the code above.
    45.         DateTime now=System.DateTime.Now;
    46.        
    47.         System.TimeSpan diff=now.Subtract(time);
    48.         print("Spent "+diff.TotalMilliseconds+" ms");
    49.     }
    50.    
    51.    
    52.     // Use this for initialization
    53.     void Start ()
    54.     {      
    55.         //Number of calls made to benchmark functions
    56.         int numMillion=100;
    57.        
    58.         //Test mono_internal_add_call method       
    59.         print("intFast performance for "+numMillion+" million calls");
    60.         CallMillionTimes(numMillion,Hello.RetInt);
    61.        
    62.         print("intSlow performance for "+numMillion+" million calls");     
    63.         CallMillionTimes(numMillion,Hello.RetIntSlow);
    64.        
    65.         print("Performance in C for a method that sqroots "+numMillion*100+" million times.");
    66.         Hello.TestIntC(1);
    67.            
    68.         print("\nPerformance in C# for a method that sqroots "+numMillion+" million times.");
    69.         CallMillionTimes(numMillion,Hello.RetSqrtCSharp);          
    70.        
    71.     }
    72.    
    73. }
    74.  

    C:

    Code (csharp):
    1.  
    2. extern "C" {
    3.    
    4.     int val=0;
    5.    
    6.     static int RetInt()
    7.     {
    8.         val++;
    9.         return val;
    10.     }
    11.    
    12.     int RetIntSlow()
    13.     {
    14.         val++;
    15.         return val;
    16.     }
    17.    
    18.     int RetSqrtC()
    19.     {
    20.         val++;
    21.         return sqrt(val);
    22.     }
    23.    
    24.     int diff_ms(timeval t1, timeval t2)
    25.     {
    26.         return (((t1.tv_sec - t2.tv_sec) * 1000000) +
    27.                 (t1.tv_usec - t2.tv_usec))/1000;
    28.     }
    29.    
    30.     void TestIntC(int numMillions)
    31.     {
    32.        
    33.         timeval start, finish;
    34.         gettimeofday(&start,NULL);
    35.        
    36.         int total=0;        
    37.         for (long count = 0; count <numMillions*1000000; count++)
    38.         {
    39.             for (long count2 = 0; count2 <100; count2++)
    40.             {
    41.                 total+=RetSqrtC();
    42.             }
    43.         }
    44.        
    45.         gettimeofday(&finish,NULL);
    46.         printf("\nTotal %d",total); //Using total, so the compiler doesn't optimize and gets rid of the code above.
    47.         printf("\nSpent %d ms",diff_ms(finish, start));        
    48.        
    49.     }
    50.    
    51.    
    52. };
    53.  
    54.  
    55. void mono_internal_initialize()
    56. {    
    57.     mono_add_internal_call("Hello::RetInt", (void*)RetInt);
    58. }
    59.  
    60.  



    The result:

    intFast performance for 100 million calls
    Spent 18765.762 ms

    intSlow performance for 100 million calls
    Spent 19340.145 ms

    Performance in C for a method that sqroots 10000 million times.
    Spent 15210 ms

    Performance in C# for a method that sqroots 100 million times.
    Spent 77821.272 ms


    The times above are for an iPhone 4, with a release version.


    So, 100 million calls to a C function took 19 seconds, which makes the calls negligible in cost, since the cost per call is around 0.00019 ms. This means that even if I called some C method 1000 times per frame, it would only take 0.19 ms.
    To make things even better, I'm using delegates here, which are most likely introducing a relevant overhead (haven't checked the CIL code or timed it).

    It was interesting to find that mono_add_internal_call is basically the same as the DllImport method, which I was assuming used P/Invoke.

    The last two times are a bit more indicative on the why I'm choosing to write a wrapper for Chipmunk. C# is two orders of magnitude slower in a simple sqroot method. I was actually surprised by this. So surprised that I had to tweak the C code to do 100 times more calculations (when it was the same as C# it took near 0 seconds).

    Now I'm going to find out about the performance of UnitySendMessage versus unmanaged to managed thunks. Has anyone done any testing here?
    Chipmunk has a lot of callbacks and I need to make sure there isn't a bottleneck in UnitySendMessage.

    Feel free to chime in, particularly if you disagree with any of my conclusions.

    Cheers,
    Alex
     
  2. Stephan-B

    Stephan-B

    Joined:
    Feb 23, 2011
    Posts:
    2,269
    Interesting!
     
  3. mu-kow

    mu-kow

    Joined:
    May 15, 2012
    Posts:
    106
    I'm very interested in using an external physics engine. I'm a bit unclear as to how to go about it though. I have one in c# that works in unity. I have no problem porting it to another language if need be. Do you need a pro licence to do it? Are there any resources you can point me towards on what wrapping is or how to do it?

    Thanks
     
  4. Game-Whiz

    Game-Whiz

    Joined:
    Nov 10, 2011
    Posts:
    122
    The problem with a C# physics engine is the massive overhead when calling Mathf methods. It's much more efficient to write a wrapper to a native physics engine (much like Unity did). From what I can remember, when calling a Mathf method, Mono (and most likely .Net) don't implement it natively, but they wrap it using P/Invoke. Now imagine the cost of calling any Mathf method in a tight loop...

    To see how Unity did it, you merely have to decompile the relevant Unity dlls. Use reflector or resharper to do that and you'll see that Unity extensively uses P/Invoke. Additionally you can use unsafe in C# to avoid data marshalling (see here: http://www.snowbolt.com/index.php/blog/28-tech/91-pinvoke ).

    A Pro license is not needed for iOS and Android, but it's needed for Windows and Mac.

    Finally, I'm glad I never started writing a Chipmunk wrapper, because the Chipmunks themselves are doing it :D

    Please pledge here to see what seems to be a very nice Chipmunk integration into Unity: http://www.kickstarter.com/projects/howlingmoonsoftware/chipmunk2d-for-unity

    Cheers,
    Alex
     
  5. darkhog

    darkhog

    Joined:
    Dec 4, 2012
    Posts:
    2,218
    Pro license required to use P/Invoke? I don't think so. I've built game with following code (built because I've heard Unity sometimes execute pro-only things in editor):

    Code (csharp):
    1.  
    2. public enum MessageBoxResult : uint
    3. {
    4.    Ok = 1,
    5.    Cancel,
    6.    Abort,
    7.    Retry,
    8.    Ignore,
    9.    Yes,
    10.    No,
    11.    Close,
    12.    Help,
    13.    TryAgain,
    14.    Continue,
    15.    Timeout = 32000
    16. }
    17. public enum MessageBoxOptions : uint
    18. {
    19.    OkOnly  = 0x000000,
    20.    OkCancel  = 0x000001,
    21.    AbortRetryIgnore  = 0x000002,
    22.    YesNoCancel  = 0x000003,
    23.    YesNo  = 0x000004,
    24.    RetryCancel  = 0x000005,
    25.    CancelTryContinue  = 0x000006,
    26.    IconHand  = 0x000010,
    27.    IconQuestion  = 0x000020,
    28.    IconExclamation  = 0x000030,
    29.    IconAsterisk  = 0x000040,
    30.    UserIcon  = 0x000080,
    31.    IconWarning  = IconExclamation,
    32.    IconError  = IconHand,
    33.    IconInformation  = IconAsterisk,
    34.    IconStop  = IconHand,
    35.    DefButton1  = 0x000000,
    36.    DefButton2  = 0x000100,
    37.    DefButton3  = 0x000200,
    38.    DefButton4  = 0x000300,
    39.    ApplicationModal  = 0x000000,
    40.    SystemModal  = 0x001000,
    41.    TaskModal  = 0x002000,
    42.    Help  = 0x004000,
    43.    NoFocus  = 0x008000,
    44.    SetForeground  = 0x010000,
    45.    DefaultDesktopOnly = 0x020000,
    46.    Topmost  = 0x040000,
    47.    Right  = 0x080000,
    48.    RTLReading  = 0x100000
    49. }
    50.  
    51. public class Test : MonoBehaviour {
    52.  
    53.    [DllImport("user32.dll", CharSet=CharSet.Auto)]
    54.    public static extern MessageBoxResult MessageBox(IntPtr hWnd, String text, String caption, int options);
    55.    
    56.    // Use this for initialization
    57.    void Start () {
    58.      MessageBox(IntPtr.Zero, "Text", "Caption", 0);
    59.    }
    60. }
    61.  
    Then put it on empty game object and it works both in editor and in build.

    P/Invoke is free. What's Pro is Unity plugins, completely different thing.
     
  6. kurroarashi

    kurroarashi

    Joined:
    Dec 22, 2018
    Posts:
    28
    How do you get the mono_add_internal_call function?
     
    ErnestSurys and MrDyrektor like this.