.Net: Costs for multithreading

    I recently got a simple task: write a windows service to handle user requests. The question of what these requests are and by what protocol the service works is beyond the scope of this article. Another factor that seemed more interesting to me was whether to do multi-threaded query processing. On the one hand, sequential execution slows down the process of processing information. On the other hand, the costs of creating and starting a thread may not be justified.
    So, the initial data: 20 simple queries per second (1200 requests per minute) at peak time. Test "server": Celeron, 3GHz, 1GB (free 70%).

    Single threaded system


    First, we write a base class for single-threaded query execution.
    1. using System;
    2. using System.Diagnostics;
    3. using System.Threading;
    4.  
    5. namespace TestConsoleApplication
    6. {
    7.  
    8.   class mockClass
    9.   {
    10.     private readonly Int32 incriment_speed;
    11.     private Int32 inc;
    12.  
    13.     public mockClass(int incriment_speed)
    14.     {
    15.       this.incriment_speed = incriment_speed;
    16.       inc = 0;      
    17.     }
    18.  
    19.     public Int32 incriment()
    20.     {
    21.       Thread.Sleep(incriment_speed);
    22.       return inc++;
    23.     }
    24.  
    25.     public Int32 getIncriment()
    26.     {
    27.       return inc;
    28.     }
    29.  
    30.   }
    31.  
    32.   class TestConsoleApplication
    33.   {    
    34.  
    35.     static void Main(string[] args)
    36.     {
    37.       if (args.Length<1) return;
    38.  
    39.       Int32 mockSpeed = 0;
    40.       if (!Int32.TryParse(args[0], out mockSpeed)) return;
    41.       var mock = new mockClass(mockSpeed);
    42.  
    43.       int beginTick = Environment.TickCount;
    44.       for (int j = 0; j < 1200; j++)
    45.       {
    46.         mock.incriment();
    47.       }
    48.       int endTick = Environment.TickCount;
    49.  
    50.       var performance = new PerformanceCounter("Process", "Private Bytes", Process.GetCurrentProcess().ProcessName);
    51.       Console.WriteLine(mock.getIncriment());
    52.       Console.WriteLine("tick: {0}", endTick - beginTick);
    53.       Console.WriteLine("memory: {0:N0}K", (performance.RawValue/1024));
    54.       Console.ReadLine();
    55.     }
    56.   }
    57. }
    * This source code was highlighted with Source Code Highlighter.

    Run the program with several parameters for the delay in query execution: 2, 5, 10
    2510
    tickmemorytickmemorytickmemory
    368810 792K728110 780K1312510 792K

    As you can see, the memory practically does not suffer, and the time is approximately equal to (mockSpeed ​​+ 1) * 1200. We write off the extra millisecond for overhead.

    Multithreaded system


    We rewrite the program for working with multithreading, optimize it and compare the results:
    1. using System;
    2. using System.Diagnostics;
    3. using System.Threading;
    4.  
    5. namespace TestConsoleApplication
    6. {
    7.  
    8.   class mockClass
    9.   {
    10.     private readonly Int32 incriment_speed;
    11.     private Int32 inc;
    12.  
    13.     public mockClass(int incriment_speed)
    14.     {
    15.       this.incriment_speed = incriment_speed;
    16.       inc = 0;      
    17.     }
    18.  
    19.     public Int32 incriment()
    20.     {
    21.       Thread.Sleep(incriment_speed);
    22.       return inc++;
    23.     }
    24.  
    25.     public Int32 getIncriment()
    26.     {
    27.       return inc;
    28.     }
    29.  
    30.   }
    31.  
    32.   class TestConsoleApplication
    33.   {    
    34.     private static mockClass mock = null;
    35.  
    36.     static void threadmethod()
    37.     {
    38.       lock (mock)
    39.       {
    40.         mock.incriment(); 
    41.       }      
    42.     }
    43.  
    44.     static void Main(string[] args)
    45.     {
    46.       if (args.Length<1) return;
    47.  
    48.       Int32 mockSpeed = 0;
    49.       if (!Int32.TryParse(args[0], out mockSpeed)) return;
    50.       mock = new mockClass(mockSpeed);
    51.  
    52.       var performance = new PerformanceCounter("Process", "Private Bytes", Process.GetCurrentProcess().ProcessName);
    53.       long performance_RawValue = 0;
    54.       int beginTick = Environment.TickCount;
    55.       lock (mock)
    56.       {
    57.         for (int j = 0; j < 1200; j++)
    58.         {
    59.           var trd = new Thread(threadmethod, 65536); //выделяем 1 страницу под стек
    60.           trd.Start();
    61.         }
    62.         performance_RawValue = performance.RawValue;
    63.       }
    64.       int end1Tick = Environment.TickCount;
    65.       while(mock.getIncriment()<1200)
    66.       {
    67.         Thread.Sleep(2);
    68.       }
    69.       int end2Tick = Environment.TickCount;
    70.       
    71.       Console.WriteLine("starttick: {0}", end1Tick - beginTick);
    72.       Console.WriteLine("alltick: {0}", end2Tick - beginTick);
    73.       Console.WriteLine("memory: {0:N0}K", (performance_RawValue / 1024));
    74.       Console.ReadLine();
    75.     }
    76.   }
    77. }
    * This source code was highlighted with Source Code Highlighter.


    -2510
    -start tickall tickmemorystart tickall tickmemorystart tickall tickmemory
    Single threaded-368810 792K-728110 780K-1312510 792K
    Multithreaded6564234323 508K6257719323 508K75013735323 508K

    When testing the performance of multithreading, a new value for the process startup time appeared. It is by this value that the overall duration of the program increases. The approximate start of the process is 0.5 milliseconds. We also see a significantly increased amount of used memory, which is spent on the stack of launched threads.

    Summary


    Select all the compared values ​​in the table.
    -Single threadedMultithreaded
    Total timeThe total time of the main thread depends on the execution time of all requestsThe runtime of the main thread depends only on the number of requests
    Total processor timeLow parasitic loadsSpurious loads 2 times higher
    MemoryLow memory requests, independent of the number of requestsAt least 256KB of memory per thread stack is consumed per request


    Here is such a "student laboratory work" came out in the study of such a question. Please do not throw stones :)

    Also popular now: