
.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%).
First, we write a base class for single-threaded query execution.
Run the program with several parameters for the delay in query execution: 2, 5, 10
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.
We rewrite the program for working with multithreading, optimize it and compare the results:
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.
Select all the compared values in the table.
Here is such a "student laboratory work" came out in the study of such a question. Please do not throw stones :)
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.
- using System;
- using System.Diagnostics;
- using System.Threading;
-
- namespace TestConsoleApplication
- {
-
- class mockClass
- {
- private readonly Int32 incriment_speed;
- private Int32 inc;
-
- public mockClass(int incriment_speed)
- {
- this.incriment_speed = incriment_speed;
- inc = 0;
- }
-
- public Int32 incriment()
- {
- Thread.Sleep(incriment_speed);
- return inc++;
- }
-
- public Int32 getIncriment()
- {
- return inc;
- }
-
- }
-
- class TestConsoleApplication
- {
-
- static void Main(string[] args)
- {
- if (args.Length<1) return;
-
- Int32 mockSpeed = 0;
- if (!Int32.TryParse(args[0], out mockSpeed)) return;
- var mock = new mockClass(mockSpeed);
-
- int beginTick = Environment.TickCount;
- for (int j = 0; j < 1200; j++)
- {
- mock.incriment();
- }
- int endTick = Environment.TickCount;
-
- var performance = new PerformanceCounter("Process", "Private Bytes", Process.GetCurrentProcess().ProcessName);
- Console.WriteLine(mock.getIncriment());
- Console.WriteLine("tick: {0}", endTick - beginTick);
- Console.WriteLine("memory: {0:N0}K", (performance.RawValue/1024));
- Console.ReadLine();
- }
- }
- }
* This source code was highlighted with Source Code Highlighter.
Run the program with several parameters for the delay in query execution: 2, 5, 10
2 | 5 | 10 | |||
tick | memory | tick | memory | tick | memory |
3688 | 10 792K | 7281 | 10 780K | 13125 | 10 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:
- using System;
- using System.Diagnostics;
- using System.Threading;
-
- namespace TestConsoleApplication
- {
-
- class mockClass
- {
- private readonly Int32 incriment_speed;
- private Int32 inc;
-
- public mockClass(int incriment_speed)
- {
- this.incriment_speed = incriment_speed;
- inc = 0;
- }
-
- public Int32 incriment()
- {
- Thread.Sleep(incriment_speed);
- return inc++;
- }
-
- public Int32 getIncriment()
- {
- return inc;
- }
-
- }
-
- class TestConsoleApplication
- {
- private static mockClass mock = null;
-
- static void threadmethod()
- {
- lock (mock)
- {
- mock.incriment();
- }
- }
-
- static void Main(string[] args)
- {
- if (args.Length<1) return;
-
- Int32 mockSpeed = 0;
- if (!Int32.TryParse(args[0], out mockSpeed)) return;
- mock = new mockClass(mockSpeed);
-
- var performance = new PerformanceCounter("Process", "Private Bytes", Process.GetCurrentProcess().ProcessName);
- long performance_RawValue = 0;
- int beginTick = Environment.TickCount;
- lock (mock)
- {
- for (int j = 0; j < 1200; j++)
- {
- var trd = new Thread(threadmethod, 65536); //выделяем 1 страницу под стек
- trd.Start();
- }
- performance_RawValue = performance.RawValue;
- }
- int end1Tick = Environment.TickCount;
- while(mock.getIncriment()<1200)
- {
- Thread.Sleep(2);
- }
- int end2Tick = Environment.TickCount;
-
- Console.WriteLine("starttick: {0}", end1Tick - beginTick);
- Console.WriteLine("alltick: {0}", end2Tick - beginTick);
- Console.WriteLine("memory: {0:N0}K", (performance_RawValue / 1024));
- Console.ReadLine();
- }
- }
- }
* This source code was highlighted with Source Code Highlighter.
- | 2 | 5 | 10 | ||||||
- | start tick | all tick | memory | start tick | all tick | memory | start tick | all tick | memory |
Single threaded | - | 3688 | 10 792K | - | 7281 | 10 780K | - | 13125 | 10 792K |
Multithreaded | 656 | 4234 | 323 508K | 625 | 7719 | 323 508K | 750 | 13735 | 323 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 threaded | Multithreaded |
Total time | The total time of the main thread depends on the execution time of all requests | The runtime of the main thread depends only on the number of requests |
Total processor time | Low parasitic loads | Spurious loads 2 times higher |
Memory | Low memory requests, independent of the number of requests | At 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 :)