Search Unity

  1. Calling all beginners! Join the FPS Beginners Mods Challenge until December 13.
    Dismiss Notice
  2. It's Cyber Week at the Asset Store!
    Dismiss Notice

[C#] Optimized and Advanced Classic Threads for Unity [Option with SetThreadAffinityMask]

Discussion in 'Scripting' started by ZJP, Mar 28, 2017.

  1. ZJP

    ZJP

    Joined:
    Jan 22, 2010
    Posts:
    2,646
    Hi,

    One of the most frustrating things when using modern processors is the unexploitation of all the cores.
    I have (like everyone else) tried to implement various solutions of Threading / Parallelism under Unity with more or less happiness, without however finding an answer adapted to my pre requisites (Solution using the BASE API of Csharp, thus No Thread Pool or library more or less evolved, no use for example of the Lambdas - because old dev of the years 80-90 mastering that 10% of the language In-Side :p -, Solution optimized for the Desktop etc etc ...), while avoiding the Gaps and difficulties of multi-threading.


    Postulates:

    - Starting Threads s..k. !!! Sometimes, starting up takes more resources than the work requested. So, re-start them after the tasks performed is a VERY BAD IDEA.

    - Synchronize Threads s..k !!! Even with advanced APIs (Mutex, Thread.Join () and Cie). Some (as here : http://answers.unity3d.com/questions/486584/how-to-create-a-parallel-for-in-unity.html ) have found a solution (which I prefer) based on "Variable-Cumulative-Threads-Finished" but the concept exploited in the example dysfunction with performing CPUs performing short jobs: the famous variable being "Shared", its integrity is no longer guaranteed, several Threads simultaneously seeking to increment it ... ... that ... s..k ... really. The "restart" of the Jobs is interrupted because the "END-OF-JOBS" test is no longer verified.

    - Stopping an application with current Threads (especially with heavy spots) this can ... suck too. Try for example to interrupt a "Play" and then restart it with Threads busy with jobs that last several seconds / minutes.

    - Automatic distribution of Threads to CPUs is not very efficient. Launch for example 512 (or even 8) Threads under Unity with my FX8350 in exploit the 8 Cores it fears. This postulate is the most important from my point of view because:

    A) This is where the freezes / blockages come from in RunTime (in addition to the launch / re-launch of Threads)
    B) Because solutions, at least for the Desktop under Unity (to my knowledge I have not found) do not run the streets.



    The solution proposed here thus overcomes the aforementioned postulates:

    - It's basic code (really) easy to understand and use.

    - Threads (whatever their number) are launched ONCE, with a complete and detailed setup.

    - The method used for the synchronization of Threads is functional even with powerful CPUs running MINIMALIST tasks. Moreover, the durations of the jobs NEVER have any impact on the main Unit loop.

    - A "circuit breaker" allows (if it is correctly placed) the immediate shutdown and SYSTEMATIC (I have not taken it until now) Threads whatever the heaviness (or not) of the task to be accomplished .

    - It is possible to "spare" CPUs / COREs (the First and / or Second) which NEVER will be used by the Threads and this regardless of their number: it is the guarantee that the main processes of 'Unity will continue to run without backGound spots. Use of the Windows 32/64 API ( GetCurrentThread, SetThreadAffinityMask et GetCurrentProcessorNumber ). So "Desktop Only" for this part.


    No problem with mobiles and mono-core (successfully tested on an Acer eMachines E430 Cpu Sempron - other 6 years old). The method of "raising" Threads retains its advantages.


    What we must understand (and appreciate) with the "ventilation" of the Threads and the "spared" Cores is that we can "stuff" as many Threads as we want (within the limit of the system Of course) with very VERY heavy jobs (or VERY short in the end): this does not really matter for the main Unit Thread.


    Possible use:
    Since Matrix4x4 arrays are accessible in Threads this opens a good perspective with Graphics.DrawMeshInstanced


    Sorry. French in-side. :D


    Code (csharp):
    1.  
    2. // ************************************************************************************************************
    3. //  (c) ZJP                                                                               Parallele Thread Test
    4. // ************************************************************************************************************
    5.  
    6. using System.Collections;
    7. using System.Collections.Generic;
    8. using UnityEngine;
    9. using System.Threading;
    10. using System.Runtime.InteropServices; // DLL !!!
    11.  
    12. public class testTHREAD_JOB_NEW : MonoBehaviour {
    13.  
    14.    public int threadsToLaunch = 512;
    15.    [Range(0,2)] public int CoreToPreserv = 1; // Number of not used firts Cores
    16.    [Range(0.05f, 0.5f)] public float chekAllJobs = 0.1f; // 20fps to 0.5fps (for example ^^ )
    17.    private bool[] abortTH; //
    18.    private int[] jobEndTH; //
    19.    private int[] cpuMask;  // Binary Mask of used Cores
    20.    private int[] cpuID;    // Ralationnal array Thread => used Cores
    21.    private int MaxCoreToUse = 0;     // Number of used Cores
    22.    private int countThreadEnded = 0; // Count of terminated Threads
    23.  
    24.    // ****************************************************************************************** Windows 32/64
    25.    [DllImport("kernel32.dll")] static extern int GetCurrentThread();
    26.    [DllImport("kernel32.dll")] static extern int SetThreadAffinityMask(int hThread, int dwThreadAffinityMask);
    27.    [DllImport("kernel32.dll")] static extern int GetCurrentProcessorNumber();
    28.    // ****************************************************************************************** Windows 32/64
    29.  
    30.    private void Start() {
    31.        Application.runInBackground = true;
    32.  
    33.        // ************************************************************************************** Windows 32/64
    34.        // *********************************************************************** Build used Cores Binary Mask
    35.        MaxCoreToUse = System.Environment.ProcessorCount - CoreToPreserv;
    36.        Debug.Log("Cores available : " + MaxCoreToUse);
    37.        cpuMask = new int[MaxCoreToUse];
    38.        cpuID   = new int[threadsToLaunch];
    39.        for (int i = 0; i < MaxCoreToUse; i++) cpuMask[i] =  1 << (i + CoreToPreserv);
    40.        // ******************************************************************* Build array Thread => used Cores
    41.        int j = 0;
    42.        for (int i = 0; i < threadsToLaunch; i++) {
    43.            cpuID[i] = cpuMask[j];
    44.            j++;
    45.            if (j > MaxCoreToUse - 1) j = 0;
    46.        }
    47.        // ************************************************************************************** Windows 32/64
    48.  
    49.        abortTH  = new bool[threadsToLaunch];
    50.        jobEndTH = new int[threadsToLaunch];
    51.  
    52.        for (int i = 0; i < threadsToLaunch; i++) {          
    53.            ParameterizedThreadStart pts = new ParameterizedThreadStart(ThreadJobFunction);
    54.            Thread jobThread       = new System.Threading.Thread(pts);
    55.            // According with the Official documentation : https://docs.unity3d.com/Manual/BestPracticeUnderstandingPerformanceInUnity8.html
    56.            // ' The priority for the main thread and graphics thread are both ThreadPriority.Normal.
    57.            //   Any threads with higher priority preempt the main/graphics threads and cause framerate hiccups,
    58.            //   whereas threads with lower priority do not. If threads have an equivalent priority to the main thread, the CPU attempts to give equal time to the threads,
    59.            //   which generally results in framerate stuttering if multiple background threads are performing heavy operations, such as AssetBundle decompression.'
    60.            // So, better use a lowest Thread Priority
    61.            jobThread.Priority     = System.Threading.ThreadPriority.BelowNormal; // Lowest, BelowNormal, Normal, AboveNormal, Highest
    62.            jobThread.IsBackground = true;
    63.            abortTH[i]  = false; // "Disjonctor" End of Thread
    64.            jobEndTH[i] = 0;
    65.            jobThread.Start(i); // Thread Number
    66.        }
    67.        
    68.         StartCoroutine(CheckEndOfJobs()); // (Why not !!! ^^ )
    69.  
    70.    }
    71.  
    72.    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    73.    // According with :                                                                                            +
    74.    // https://forum.unity3d.com/threads/best-method-to-check-something-often-without-dragging-performance.444079/ +
    75.    // seem better than Update or FixedUpdate Loop                                                                 +
    76.    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    77.     IEnumerator CheckEndOfJobs()
    78.     {
    79.         var wait = new WaitForSeconds(chekAllJobs);
    80.         while (true)
    81.         {
    82.             yield return wait;
    83.            // ALL Threads done ? **********************************************************
    84.            countThreadEnded = 0;
    85.            for (int i = 0; i < threadsToLaunch; i++) countThreadEnded += jobEndTH[i];
    86.            if (countThreadEnded == threadsToLaunch) // YES !!!
    87.            {
    88.                Debug.Log("All thread Done...");
    89.                // USING OF THE JOBS TASK ***********************
    90.                //
    91.                // USING OF THE JOBS TASK ***********************
    92.            
    93.                // Go Again *************************************
    94.                for (int i = 0; i < threadsToLaunch; i++) {
    95.                    jobEndTH[i] = 0; // ReStart all Job/Thread
    96.                }
    97.            }
    98.            // ALL Threads done ? **********************************************************
    99.         }
    100.     }
    101.    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    102.    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    103.  
    104.  
    105. /*
    106.    private void FixedUpdate() {
    107.        // ALL Threads done ? **********************************************************
    108.        countThreadEnded = 0;
    109.        for (int i = 0; i < threadsToLaunch; i++) countThreadEnded += jobEndTH[i];
    110.        if (countThreadEnded == threadsToLaunch) // YES !!!
    111.        {
    112.            Debug.Log("All thread Done...");
    113.            // USING OF THE JOBS TASK ***********************
    114.            //
    115.            // USING OF THE JOBS TASK ***********************
    116.            
    117.            // Go Again *************************************
    118.            for (int i = 0; i < threadsToLaunch; i++) {
    119.                jobEndTH[i] = 0; // ReStart all Job/Thread
    120.            }
    121.        }
    122.        // ALL Threads done ? **********************************************************
    123.    }
    124. */
    125.    private void OnDisable() {
    126.        // Stop ALL Threads
    127.        for (int i = 0; i < threadsToLaunch; i++) {
    128.            abortTH[i] = true;
    129.        }
    130.    }
    131.  
    132.    private void ThreadJobFunction(object threadParams) {
    133.        int threadNumber = (int)threadParams;
    134.        Debug.Log("Enter Thread Number " + threadNumber );
    135.  
    136.        // ************************************************************* Windows 32/64
    137.        SetThreadAffinityMask(GetCurrentThread(), cpuID[threadNumber]); // thread=>cpu
    138.        Debug.Log("Cpu used > " + GetCurrentProcessorNumber()); // Just Checking ^^
    139.        // ************************************************************* Windows 32/64
    140.  
    141.        // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    142.        // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    143.        again:
    144.        // Waiting for the launch
    145.        while (jobEndTH[threadNumber] == 1) {
    146.            if (abortTH[threadNumber]) return; // end of Thread
    147.            Thread.Sleep(1); // Maybe another value is better?.
    148.        }
    149.        //Debug.Log("Thread Number " + threadNumber + " Started/ReStarted !!!");
    150.  
    151.        // ******************************************************************* JOB !!!
    152.        // ******************************************************************* JOB !!!
    153. //       int a = 2000000000; // Big job ^^
    154.        int a = 2; // Works very well even on VERY SMALL jobs...
    155.        while (a > 1) {
    156.            if (abortTH[threadNumber]) return; // end of Thread
    157.            a--;
    158.        }
    159.        // ******************************************************************* JOB !!!
    160.        // ******************************************************************* JOB !!!
    161.  
    162.        jobEndTH[threadNumber] = 1; // Job done
    163.        //Debug.Log("Thread Number " + threadNumber + " Completed ");
    164.  
    165.        goto again; // ^^ yes, Goto...
    166.        // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    167.        // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    168.    }
    169.  
    170. }
    171. // ************************************************************************************************************
    172. //  (c) ZJP                                                                               Parallele Thread Test
    173. // ************************************************************************************************************
    174.  
    175.  
    176.  





    TO MODO : not sure it's the good forum for this ?!!. :confused:
     
    Last edited: Apr 9, 2017
    Todd-Wasson likes this.
  2. ZJP

    ZJP

    Joined:
    Jan 22, 2010
    Posts:
    2,646
    Hi,

    Why i can add Tags for this thread?
    I got this reply : ' You may not create new tags. Please change the following tags : xx '
     
  3. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    8,317
    Not just "Desktop Only" — it sounds like "Windows Only" to me.

    Any chance your technique will work on Mac OS X (and maybe even Linux) too?
     
  4. ZJP

    ZJP

    Joined:
    Jan 22, 2010
    Posts:
    2,646
    Yes, Windows Desktop only.

    For the other OS, possible if GetCurrentThread, SetThreadAffinityMask andt GetCurrentProcessorNumber have equivalent functions. I don't know. :oops:

    .