qileilove

blog已经转移至github,大家请访问 http://qaseven.github.io/

线程上下文切换的性能损耗测试

线程上下文切换的性能损耗到底有多少,一直没有直观的理解,今天写个程序测试一下。先看看下面的程序(点击下载):
  
  ThreadTester是所有Tester的基类。所有的Tester都干的是同样一件事情,把counter增加到100000000,每次只能加1。
1: public abstract class ThreadTester
2:     {
3:         public const long MAX_COUNTER_NUMBER = 100000000;
4:
5:         private long _counter = 0;
6:
7:         //获得计数
8:         public virtual long GetCounter()
9:         {
10:             return this._counter;
11:         }
12:
13:         //增加计数器
14:         protected virtual void IncreaseCounter()
15:         {
16:             this._counter += 1;
17:         }
18:
19:         //启动测试
20:         public abstract void Start();
21:
22:         //获得Counter从开始增加到现在的数字所耗的时间
23:         public abstract long GetElapsedMillisecondsOfIncreaseCounter();
24:
25:         //测试是否正在运行
26:         public abstract bool IsTesterRunning();
27:     }
SingleThreadTester是单线程计数。
1: class SingleThreadTester : ThreadTester
2:     {
3:         private Stopwatch _aStopWatch = new Stopwatch();
4:
5:         public override void Start()
6:         {
7:             _aStopWatch.Start();
8:
9:             Thread aThread = new Thread(() => WorkInThread());
10:             aThread.Start();
11:         }
12:
13:         public override long GetElapsedMillisecondsOfIncreaseCounter()
14:         {
15:             return this._aStopWatch.ElapsedMilliseconds;
16:         }
17:
18:         public override bool IsTesterRunning()
19:         {
20:             return _aStopWatch.IsRunning;
21:         }
22:
23:         private void WorkInThread()
24:         {
25:             while (true)
26:             {
27:                 if (this.GetCounter() > ThreadTester.MAX_COUNTER_NUMBER)
28:                 {
29:                     _aStopWatch.Stop();
30:                     break;
31:                 }
32:
33:                 this.IncreaseCounter();
34:             }
35:         }
36:     }
  TwoThreadSwitchTester是两个线程交替计数。
1: class TwoThreadSwitchTester : ThreadTester
2:     {
3:         private Stopwatch _aStopWatch = new Stopwatch();
4:         private AutoResetEvent _autoResetEvent = new AutoResetEvent(false);
5:
6:         public override void Start()
7:         {
8:             _aStopWatch.Start();
9:
10:             Thread aThread1 = new Thread(() => Work1InThread());
11:             aThread1.Start();
12:
13:             Thread aThread2 = new Thread(() => Work2InThread());
14:             aThread2.Start();
15:         }
16:
17:         public override long GetElapsedMillisecondsOfIncreaseCounter()
18:         {
19:             return this._aStopWatch.ElapsedMilliseconds;
20:         }
21:
22:         public override bool IsTesterRunning()
23:         {
24:             return _aStopWatch.IsRunning;
25:         }
26:
27:         private void Work1InThread()
28:         {
29:             while (true)
30:             {
31:                 _autoResetEvent.WaitOne();
32:
33:                 this.IncreaseCounter();
34:
35:                 if (this.GetCounter() > ThreadTester.MAX_COUNTER_NUMBER)
36:                 {
37:                     _aStopWatch.Stop();
38:                     break;
39:                 }
40:
41:                 _autoResetEvent.Set();
42:             }
43:         }
44:
45:         private void Work2InThread()
46:         {
47:             while (true)
48:             {
49:                 _autoResetEvent.Set();
50:                 _autoResetEvent.WaitOne();
51:                 this.IncreaseCounter();
52:
53:                 if (this.GetCounter() > ThreadTester.MAX_COUNTER_NUMBER)
54:                 {
55:                     _aStopWatch.Stop();
56:                     break;
57:                 }
58:             }
59:         }
60:     }
 MultiThreadTester可以指定线程数,多个线程争抢计数。
1: class MultiThreadTester : ThreadTester
2:     {
3:         private Stopwatch _aStopWatch = new Stopwatch();
4:         private readonly int _threadCount = 0;
5:         private readonly object _counterLock = new object();
6:
7:         public MultiThreadTester(int threadCount)
8:         {
9:             this._threadCount = threadCount;
10:         }
11:
12:         public override void Start()
13:         {
14:             _aStopWatch.Start();
15:
16:             for (int i = 0; i < _threadCount; i++)
17:             {
18:                 Thread aThread = new Thread(() => WorkInThread());
19:                 aThread.Start();
20:             }
21:         }
22:
23:         public override long GetElapsedMillisecondsOfIncreaseCounter()
24:         {
25:             return this._aStopWatch.ElapsedMilliseconds;
26:         }
27:
28:         public override bool IsTesterRunning()
29:         {
30:             return _aStopWatch.IsRunning;
31:         }
32:
33:         private void WorkInThread()
34:         {
35:             while (true)
36:             {
37:                 lock (_counterLock)
38:                 {
39:                     if (this.GetCounter() > ThreadTester.MAX_COUNTER_NUMBER)
40:                     {
41:                         _aStopWatch.Stop();
42:                         break;
43:                     }
44:
45:                     this.IncreaseCounter();
46:                 }
47:             }
48:         }
49:     }
  Program的Main函数中,根据用户的选择来决定执行哪个测试类。
1: class Program
2:     {
3:         static void Main(string[] args)
4:         {
5:
6:             string inputText = GetUserChoice();
7:
8:             while (!"4".Equals(inputText))
9:             {
10:                 ThreadTester tester = GreateThreadTesterByInputText(inputText);
11:                 tester.Start();
12:
13:                 while (true)
14:                 {
15:                     Console.WriteLine(GetStatusOfThreadTester(tester));
16:                     if (!tester.IsTesterRunning())
17:                     {
18:                         break;
19:                     }
20:                     Thread.Sleep(100);
21:                 }
22:
23:                 inputText = GetUserChoice();
24:             }
25:
26:             Console.Write("Click enter to exit...");
27:         }
28:
29:         private static string GetStatusOfThreadTester(ThreadTester tester)
30:         {
31:             return string.Format("[耗时{0}ms] counter = {1}, {2}",
32:                     tester.GetElapsedMillisecondsOfIncreaseCounter(), tester.GetCounter(),
33:                     tester.IsTesterRunning() ? "running" : "stopped");
34:         }
35:
36:         private static ThreadTester GreateThreadTesterByInputText(string inputText)
37:         {
38:             switch (inputText)
39:             {
40:                 case "1":
41:                     return new SingleThreadTester();
42:                 case "2":
43:                     return new TwoThreadSwitchTester();
44:                 default:
45:                     return new MultiThreadTester(100);
46:             }
47:         }
48:
49:         private static string GetUserChoice()
50:         {
51:             Console.WriteLine(@"==Please select the option in the following list:==
52: 1. SingleThreadTester
53: 2. TwoThreadSwitchTester
54: 3. MultiThreadTester
55: 4. Exit");
56:
57:             string inputText = Console.ReadLine();
58:
59:             return inputText;
60:         }
61:     }
三个测试类,运行结果如下:
Single Thread:
[耗时407ms] counter = 100000001, stopped
[耗时453ms] counter = 100000001, stopped
[耗时412ms] counter = 100000001, stopped
Two Thread Switch:
[耗时161503ms] counter = 100000001, stopped
[耗时164508ms] counter = 100000001, stopped
[耗时164201ms] counter = 100000001, stopped
Multi Threads - 100 Threads:
[耗时3659ms] counter = 100000001, stopped
[耗时3950ms] counter = 100000001, stopped
[耗时3720ms] counter = 100000001, stopped
Multi Threads - 2 Threads:
[耗时3078ms] counter = 100000001, stopped
[耗时3160ms] counter = 100000001, stopped
[耗时3106ms] counter = 100000001, stopped
  什么是线程上下文切换
  上下文切换的精确定义可以参考: http://www.linfo.org/context_switch.html。多任务系统往往需要同时执行多道作业。作业数往往大于机器的CPU数,然而一颗CPU同时只能执行一项任务,为了让用户感觉这些任务正在同时进行,操作系统的设计者巧妙地利用了时间片轮转的方式,CPU给每个任务都服务一定的时间,然后把当前任务的状态保存下来,在加载下一任务的状态后,继续服务下一任务。任务的状态保存及再加载,这段过程就叫做上下文切换。时间片轮转的方式使多个任务在同一颗CPU上执行变成了可能,但同时也带来了保存现场和加载现场的直接消耗。(Note. 更精确地说, 上下文切换会带来直接和间接两种因素影响程序性能的消耗. 直接消耗包括: CPU寄存器需要保存和加载, 系统调度器的代码需要执行, TLB实例需要重新加载, CPU 的pipeline需要刷掉; 间接消耗指的是多核的cache之间得共享数据, 间接消耗对于程序的影响要看线程工作区操作数据的大小).
  
  根据上面上下文切换的定义,我们做出下面的假设:
  之所以TwoThreadSwitchTester执行速度最慢,因为线程上下文切换的次数最多,时间主要消耗在上下文切换了,两个线程交替计数,每计数一次就要做一次线程切换。
  “Multi Threads - 100 Threads”比“Multi Threads - 2 Threads”开的线程数量要多,导致线程切换次数也比后者多,执行时间也比后者长。
  由于Windows下没有像Linux下的vmstat这样的工具,这里我们使用Process Explorer看看程序执行的时候线程上线文切换的次数。
  Single Thread:
  
  计数期间,线程总共切换了580-548=32次。(548是启动程序后,初始的数值)
  Two Thread Switch:
 
  计数期间,线程总共切换了33673295-124=33673171次。(124是启动程序后,初始的数值)
  Multi Threads - 100 Threads:
  
  计数期间,线程总共切换了846-329=517次。(329是启动程序后,初始的数值)
  Multi Threads - 2 Threads:
  
  计数期间,线程总共切换了295-201=94次。(201是启动程序后,初始的数值)
  从上面收集的数据来看,和我们的判断基本相符。
  干活的其实是CPU,而不是线程
  再想想原来学过的知识,之前一直以为线程多干活就快,简直是把学过的计算机原理都还给老师了。真正干活的不是线程,而是CPU。线程越多,干活不一定越快。
  那么高并发的情况下什么时候适合单线程,什么时候适合多线程呢?
  适合单线程的场景:单个线程的工作逻辑简单,而且速度非常快,比如从内存中读取某个值,或者从Hash表根据key获得某个value。Redis和Node.js这类程序都是单线程,适合单个线程简单快速的场景。
  适合多线程的场景:单个线程的工作逻辑复杂,等待时间较长或者需要消耗大量系统运算资源,比如需要从多个远程服务获得数据并计算,或者图像处理。
  例子程序:http://pan.baidu.com/s/1ntNUPWP

posted on 2014-06-20 11:30 顺其自然EVO 阅读(431) 评论(0)  编辑  收藏 所属分类: 测试学习专栏性能测试


只有注册用户登录后才能发表评论。


网站导航:
 
<2014年6月>
25262728293031
1234567
891011121314
15161718192021
22232425262728
293012345

导航

统计

常用链接

留言簿(55)

随笔分类

随笔档案

文章分类

文章档案

搜索

最新评论

阅读排行榜

评论排行榜