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. Dismiss Notice

Threading

Discussion in 'Scripting' started by CoopOwnz, Oct 25, 2016.

  1. CoopOwnz

    CoopOwnz

    Joined:
    Oct 6, 2016
    Posts:
    74
    The code below is a simple demonstration of what I eventually want to do with threading. I have a long calculation that I want to do in the sample class I created below but for now I just made a simple loop for demonstration. I essentially want to create 4 instances of the class and run the function in them simultaneously to improve speed. I understand that the threads won't always be simultaneous depending on the system and other factors but the threading example below should be faster then simply calling the function. I tried to use stopwatch to gauge how quickly the function is executing but I'm confused at the results. It seems faster to not create the threads but of course the program hangs without them. If you run the code I commented out that just calls the function you will see the stopwatch time is not accurate per execution of the function (or at least I don't understand it). The code is just attached to an empty game object. Can anyone help explain this?

    Code (CSharp):
    1. using UnityEngine;
    2. using System;
    3. using System.Collections;
    4. using System.Threading;
    5. using System.Diagnostics;
    6.  
    7. public class SimControl : MonoBehaviour {
    8.  
    9.     PlayShoe shoe1;
    10.     PlayShoe shoe2;
    11.     PlayShoe shoe3;
    12.     PlayShoe shoe4;
    13.     Stopwatch watch1;
    14.     Stopwatch watch2;
    15.     Stopwatch watch3;
    16.     Stopwatch watch4;
    17.  
    18.     // Use this for initialization
    19.     void Start () {
    20.         shoe1 = new PlayShoe(1);
    21.         shoe2 = new PlayShoe(2);
    22.         shoe3 = new PlayShoe(3);
    23.         shoe4 = new PlayShoe(4);
    24.  
    25.         watch1 = new Stopwatch();
    26.         watch2 = new Stopwatch();
    27.         watch3 = new Stopwatch();
    28.         watch4 = new Stopwatch();
    29.  
    30.  
    31.         Thread newthread = new Thread(new ThreadStart(shoe1.Run));
    32.         Thread newthread2 = new Thread(new ThreadStart(shoe2.Run));
    33.         Thread newthread3 = new Thread(new ThreadStart(shoe3.Run));
    34.         Thread newthread4 = new Thread(new ThreadStart(shoe4.Run));
    35.         newthread.Start();
    36.         watch1.Start();
    37.         newthread2.Start();
    38.         watch2.Start();
    39.         newthread3.Start();
    40.         watch3.Start();
    41.         newthread4.Start();
    42.         watch4.Start();
    43.  
    44.         /*
    45.         watch1.Start();
    46.         shoe1.Run();
    47.  
    48.         watch2.Start();
    49.         shoe2.Run();
    50.  
    51.         watch3.Start();
    52.         shoe3.Run();
    53.  
    54.         watch4.Start();
    55.         shoe4.Run();
    56. */
    57.     }
    58.  
    59.     // Update is called once per frame
    60.     void Update () {
    61.         if (shoe1.done)
    62.         {
    63.             print(shoe1.getTotal()+ " " +watch1.Elapsed);
    64.             shoe1.done = false;
    65.         }
    66.         if (shoe2.done)
    67.         {
    68.             print(shoe2.getTotal()+ " " +watch2.Elapsed);
    69.             shoe2.done = false;
    70.         }
    71.         if (shoe3.done)
    72.         {
    73.             print(shoe3.getTotal()+ " " +watch3.Elapsed);
    74.             shoe3.done = false;
    75.         }
    76.         if (shoe4.done)
    77.         {
    78.             print(shoe4.getTotal()+ " " +watch4.Elapsed);
    79.             shoe4.done = false;
    80.         }
    81.     }
    82. }
    83.  
    84.  
    85. public class PlayShoe {
    86.  
    87.     int currShoe;
    88.     private int total = 0;
    89.     public bool done = false;
    90.  
    91.     public PlayShoe(int theShoe)
    92.     {
    93.         currShoe = theShoe;
    94.     }
    95.  
    96.     public void Run()
    97.     {
    98.         for (int i = 0; i < 500000000; i++)
    99.         {
    100.             total = i;
    101.         }
    102.         done = true;
    103.     }
    104.  
    105.     public int getTotal()
    106.     {
    107.         return total;
    108.     }
    109.  
    110.  
    111. }
    Edit: fixed the code to call stopwatch before calling function on the commented out section not using threading.
    Edit2: I understand that the section that doesn't use threading is not actually updating until all of the function calls are finished which is why the stopwatch times seem strange. But either way why would running on separate threads be slower then just calling the functions?
    Edit3: I run a similar test without using a separate class and the threading is way faster then without threading. Why is the class slowing down the threads so much?
     
    Last edited: Oct 25, 2016
  2. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,376
    Why would a function be any faster if it was ran in another thread?

    Multi-threading is NOT inherently faster, and actually introduces certain overheads that can make it technically slower in some ways.

    Where multi-threading can gain perceived speeds is it allows concurrent work.

    So, in Unity everything runs in a single thread. When 'Update' on a given script is called, the entire game stops and waits for that 'Update' call to finish. If you do 5 seconds worth of work in that single call... the game is going to freeze for 5 whole seconds.

    By separating it into another thread, you can continue on updating the game, and when your concurrent work is finished you actually do something in game.

    An example of this can be AI calculations which can get very expensive. But they don't have to be done in a single frame... you can start calculating AI on a separate thread... like pathfinding... and once the path is calculated, you then start moving the player on screen.

    Soooo.... where does "speed" come in here?

    Lets say you have 4 AI enemies that need to pathfind. Calculating 4 paths on a thread will still be a single thread for AI calculations... and you'll end up with calculating each path one after the other. A perceived 4 times the amount of time. BUT, if you give a single thread to each AI, then they can all calculate concurrently as well, resulting in a calculation that is roughly the same amount of time of calculating just 1 AI path (of course, this isn't exact in real world... this hinges on the number of CPU cores, as well as how many cores are actually utilized... your OS usually balances threads).

    ...

    tldr;

    Multi-threading does not speed things up, it allows work to be performed concurrently
     
    Ryiah likes this.
  3. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,376
    How did you implement it in this case?

    I notice you have a 'total' and a 'done' variable. Having them in the separate class means each instance has its own identity.

    If you don't have unique 'total' and 'done' variables for each thread when you don't have a separate class, this could cause the multiple calls to 'Run' to be updating the same fields. So it could finish faster. You basically have 4 different workers working on the same problem at the same time... you know, one of those sorts of jobs multi-threading is really meant for.

    It's kind of like this... you know how like a Nvidia GPU is advertised as having like 732 CUDA cores, or 968 CUDA cores, or whatevers.

    What the hell does that even mean?

    Well in a GPU it has hundreds of little floating point processors. It's a several hundred core processor designed to do floating point arithmetic. Because the lower left corner of the screen really doesn't have anything to do with the upper right corner of the screen... the screen can be broken into small little sectors and each core can take care of its own little region (this is a gross over simplification)... several workers can all work together on small sections of a job to get it done faster. But this only works for jobs that can be divided up easily.

    That's what multi-threading is all about.
     
  4. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    This.

    In 99.99% of cases multi threading in Unity is not about getting more performance. Its all about not blocking the main thread. Regardless of what happens we want the screen to keep refreshing and to keep responding to player input.

    So don't focus on the absolute times to do a job. Focus on eliminating spikes in frame time. The profiler is a good tool to do this.
     
    CoopOwnz likes this.
  5. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    To add to what was said above, starting a new thread has some extra overhead; for simplicity let's say it adds 1 second to start the thread. In the threaded section, you're starting all the stopwatches at basically the same time, then starting all four threads, and then stopping each stopwatch when its thread stops. So if your method takes, say, five seconds, then it will take six seconds to do it threaded, but all of the threads can run at the same time, so you'd see about six seconds total on all the stopwatches.

    In your non-threaded section though, you're not starting all the stopwatches at the same time. You're starting a single stopwatch, running the method (five seconds) then stopping it. Then starting another stopwatch, running the method (five seconds) then stopping it, etc. So it's going to print out "five seconds" even though in reality 20 seconds will have passed in total before it's done. If you want to actually see the real difference in time, you need to start all the stopwatches (or just use one stopwatch) at the beginning of the non-threaded section and then stop it only after all four calls are complete.
     
    CoopOwnz and Kiwasi like this.
  6. CoopOwnz

    CoopOwnz

    Joined:
    Oct 6, 2016
    Posts:
    74
    Thanks for your responses. I have a better grasp on multithreading now. Basically what I was trying to say is I was trying to cut down the time it took to complete a complicated calculation. If you are interested, here is a new take on my initial code that demonstrates what I was trying to accomplish.

    Code (CSharp):
    1. using UnityEngine;
    2. using System;
    3. using System.Collections;
    4. using System.Threading;
    5. using System.Diagnostics;
    6.  
    7. public class SimControl : MonoBehaviour {
    8.  
    9.     Thread main2;
    10.  
    11.     // Use this for initialization
    12.     void Start ()
    13.     {
    14.        
    15.     }
    16.  
    17.     public void StartButton()
    18.     {
    19.         main2 = new Thread(RunSimulation);
    20.         main2.Start();
    21.     }
    22.  
    23.     void RunSimulation()
    24.     {
    25.        
    26.         Stopwatch timer = new Stopwatch();
    27.         PlayShoe shoe1 = new PlayShoe();
    28.         PlayShoe shoe2 = new PlayShoe();
    29.         PlayShoe shoe3 = new PlayShoe();
    30.         PlayShoe shoe4 = new PlayShoe();
    31.  
    32.         Thread t = new Thread(shoe1.RunLoop);
    33.         Thread t2 = new Thread(shoe2.RunLoop);
    34.         Thread t3 = new Thread(shoe2.RunLoop);
    35.         Thread t4 = new Thread(shoe2.RunLoop);
    36.         timer.Start();
    37.         t.Start();
    38.         t2.Start();
    39.         t3.Start();
    40.         t4.Start();
    41.  
    42.         t.Join();
    43.         t2.Join();
    44.         t3.Join();
    45.         t4.Join();
    46.         /*
    47.         timer.Start();
    48.         shoe1.RunLoop();
    49.         shoe2.RunLoop();
    50.         shoe3.RunLoop();
    51.         shoe4.RunLoop();
    52.         */
    53.         timer.Stop();
    54.        
    55.         print(timer.Elapsed);
    56.  
    57.  
    58.     }
    59. }
    60.  
    61.  
    62. public class PlayShoe {
    63.  
    64.     public void RunLoop()
    65.     {
    66.         int x = 0;
    67.         for (int i = 0; i < 900000000; i++)
    68.         {
    69.             if (i % 2 == 0)
    70.                 x++;
    71.             else
    72.                 x--;
    73.         }
    74.     }
    75.  
    76. }
    Since my PC has 4 cores that can be utilized, the code with threading reduces the time by roughly 1/4.
     
    makeshiftwings likes this.
  7. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,376
    Yes, because you're doing 4 jobs in a row in one, and doing 4 jobs concurrently in the other.

    That would result in a perceived speed increase.
     
  8. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    Well it's an actual speed increase, not just a perceived one; that's the point of threading. But I know what you mean.
     
    CoopOwnz likes this.