Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Multithreading with real-time constraints

Discussion in 'Scripting' started by BrightBit, Jan 14, 2015.

  1. BrightBit

    BrightBit

    Joined:
    Jan 22, 2013
    Posts:
    243
    How can I ensure that my main-thread will run in real-time, i.e. additional threads won't make the main thread stutter?

    Detailed Explanation:
    I've got a randomly generated world that is closely based on voxels. The world itself is divided into several smaller parts called Chunks. The geometry for those chunks is created in separate threads. The main thread takes the output of these threads (vertices) and puts them into Unity's Mesh objects. There is some additional information that needs to be generated, namely waypoints for NPC's. Those waypoints shall be calculated in separate threads as well.
    It's okay if the threads for the creation of the chunks and for the waypoints take a lot of time but I don't want them to slow down the main thread.

    Which options do I have to accomplish this? Can I force threads to run on certain CPU's only? Can I work with priorities? What's the best practice?

    So far I've already used C# threads with semaphores and the C# ThreadPool but neither of them kept the main thread running in real-time. Any ideas?
     
  2. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,385
    In Mono/.Net you can not select which cpu core a thread runs on. They are managed threads, and the framework will decide where to put the thread.

    I'm not sure how well the version of Mono, that Unity uses, is at selecting where to run the thread. .Net is usually pretty good at it though.

    Technically (you'll probably need the pro version), you could write some native code in C++, or try p/invoke, and have your own forced threading system. Note this will NOT integrate with the mono/.net threads at all, and has a lot of potential issues that could arise.
     
  3. BrightBit

    BrightBit

    Joined:
    Jan 22, 2013
    Posts:
    243
    Hi lordofduct and thanks for you answer,

    your hint led me to a post on stackoverflow: Set thread processor affinity in Microsoft .Net which in turn made me program the following static method that needs to be called from with the thread in question:

    Code (csharp):
    1.  
    2. public static void DoNotRunOnMainCPU()
    3. {
    4.     System.Threading.Thread.BeginThreadAffinity();
    5.  
    6. #pragma warning disable 618
    7.     int osThreadId = AppDomain.GetCurrentThreadId();
    8. #pragma warning restore 618
    9.  
    10.     ProcessThread thread = Process.GetCurrentProcess().
    11.         Threads.Cast<ProcessThread>().
    12.         Where(t => t.Id == osThreadId).Single();
    13.  
    14.     long cpuMask = 1;
    15.  
    16.     if (Environment.ProcessorCount > 1)
    17.     {
    18.         cpuMask = 0;
    19.  
    20.         for (int cpu = 1; cpu < Environment.ProcessorCount; ++cpu)
    21.         {
    22.             cpuMask |= 1L << cpu;
    23.         }
    24.     }
    25.  
    26.     thread.ProcessorAffinity = new IntPtr(cpuMask);
    27. }
    28.  
    However, it doesn't work for some reason. When I call DoNotRunInMainCPU() within a method of a thread the code after this method won't be executed. I will have to investigate it further.

    I really hope that you are wrong and that there is a way to accomplish what I need but your answer was valuable to me nonetheless.
     
  4. Manufacture43

    Manufacture43

    Joined:
    Apr 21, 2017
    Posts:
    140
    Resurrecting an old thread because I can't find one recent : does anybody found a way to lock a managed thread to a specific core with unity? It seems the mono version used by unity does not implement Process.Threads. The list is always empty. So I'm looking for a multiplatform workaround...
     
  5. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,294
    I don't think it's a wise decision. For the more generic and better approach try using new Jobs / ECS system.
    You won't be needing thread cpu switching if you partition your tasks accordingly.
     
  6. Manufacture43

    Manufacture43

    Joined:
    Apr 21, 2017
    Posts:
    140
    I'm looking into this right now and all the copying around frightens me off and it requires a lot more work for what will probably be a small win in the end. I'm currently optimizing an already finished game for the console ports. I would agree if it was to start a new project...

    EDIT: in my case, I was asking because I was rolling my own task scheduler (a simple parallel for actually). I was having huge 10ms random stalls on xbox which I believe are due to some tasks running on core 2 and FMOD overtaking the core from time to time.
     
  7. Manufacture43

    Manufacture43

    Joined:
    Apr 21, 2017
    Posts:
    140
    upload_2018-9-19_20-17-27.png

    As expected, with unity's job system, all the performance win is lost in the unfortunately necessary NativeArray to managed list conversion...
     
  8. BlackPete

    BlackPete

    Joined:
    Nov 16, 2016
    Posts:
    970
    If it's multiplatform code, then I'd be very very wary of trying to set the processor affinity, at least in the general sense. If it's xbox specific code, then that may be better, but still not completely desirable as that means specific code for each platform you support.

    If the game is already finished and it's just the 10ms spikes you're trying to get rid of, then I'd limit your focus to eliminating the spikes. Did your job system implementation get rid of the spikes at least?
     
  9. Manufacture43

    Manufacture43

    Joined:
    Apr 21, 2017
    Posts:
    140
    The spikes were coming from a multithreading optimisation I did while porting, they were not there in the first place. I have a lot to do aside from that.

    I already have platform specific code, I just tend to minimize it. I wouldn't mind having an a list of worker cores per platform...

    The screenshot you see is the result of the job system version of my same multithreading optimisation. It doesn't have any spikes. Judging from the 4 worker cores, I guess unity makes the jobs run on core 0, 3, 4, 5 and 6, just like I wanted it in the first place :)
    But there is that huge overhead from getting back the data from nativearray to managed, that completely makes the optimisation pointless... I'm going to try to find other ways of moving the data from native to managed. If I can't find a fast one, or find a way of starting managed threads on specific CPU cores, I'll keep the code single threaded and the game will never run at 60...
     
  10. XOuYang

    XOuYang

    Joined:
    Jun 17, 2021
    Posts:
    4
    hi bro,do you resolved the problem? i also be
    troubled by this problem,can you give me some tips?
     
  11. Manufacture43

    Manufacture43

    Joined:
    Apr 21, 2017
    Posts:
    140
    Yes, with a hack that enables me to run managed code with unity's job system. Bear in mind that all the safeguards that unity implemented to avoid race conditions and stuff will not work so you have to carefully write your code.
    Code (CSharp):
    1. using Unity.Jobs;
    2. using System;
    3. using System.Runtime.InteropServices;
    4.  
    5. // Usage:
    6. // struct MyJob : ManagedJob.IWork
    7. // {
    8. //     void Execute() { }
    9. // }
    10. // ...
    11. // ManagedJob job = new ManagedJob() { Work = new MyJob(); }
    12. // JobHandle handle = job.Schedule();
    13. // handle.Complete();
    14. // job.Dispose();
    15. struct ManagedJob<T> : IJob, IDisposable where T : class, ManagedJob<T>.IWork // T must be class so that Work is mutable
    16. {
    17.     public interface IWork
    18.     {
    19.         void Execute();
    20.     }
    21.  
    22.     GCHandle handle;
    23.     public T Work
    24.     {
    25.         set
    26.         {
    27.             handle = GCHandle.Alloc(value);
    28.         }
    29.         get
    30.         {
    31.             return (T)handle.Target;
    32.         }
    33.     }
    34.  
    35.     void IJob.Execute()
    36.     {
    37.         IWork task = (IWork)handle.Target;
    38.         task.Execute();
    39.     }
    40.  
    41.     public void Dispose()
    42.     {
    43.         handle.Free();
    44.     }
    45. }
    46.  
    47. struct ManagedParallelFor<T> : IJobParallelFor, IDisposable where T : class, ManagedParallelFor<T>.IWork
    48. {
    49.     public interface IWork
    50.     {
    51.         void Execute(int index);
    52.     }
    53.  
    54.     GCHandle handle;
    55.     public T Work
    56.     {
    57.         set
    58.         {
    59.             handle = GCHandle.Alloc(value);
    60.         }
    61.         get
    62.         {
    63.             return (T)handle.Target;
    64.         }
    65.     }
    66.  
    67.     void IJobParallelFor.Execute(int index)
    68.     {
    69.         IWork task = (IWork)handle.Target;
    70.         task.Execute(index);
    71.     }
    72.  
    73.     public void Dispose()
    74.     {
    75.         handle.Free();
    76.     }
    77. }
    78.  
     
  12. XOuYang

    XOuYang

    Joined:
    Jun 17, 2021
    Posts:
    4
    thanks
     
  13. XOuYang

    XOuYang

    Joined:
    Jun 17, 2021
    Posts:
    4
    hi bro, i'am use job to caculate my fight core,but the fight performance are same as using c# thread, Is there anything to watch out for? Shouldn't the big core run faster?