享受release版本发布的好处的同时也应该警戒release可能给你引入一些莫名其妙的大bug

  通常咱们发布项目的时候一般都会采用release版本,由于release会在jit层面对咱们的il代码进行了优化,好比在迭代和内存操做的性能提高方面,废话很少说,算法

我先用一个简单的“冒泡排序”体验下release和debug下面的性能差距。缓存

 

一:release带来的闪光点【冒泡排序】多线程

  这个是我多年前写的算法系列中的一个冒泡排序的例子,就随手翻出来展现一下,准备灌入50000条数据,这样就能够执行25亿次迭代,王健林说,不能太张dom

狂,几十亿对我来讲不算小意思,算中等意思吧。性能

 1 namespace ConsoleApplication4
 2 {
 3     class Program
 4     {
 5         static void Main(string[] args)
 6         {
 7             var rand = new Random();
 8             List<int> list = new List<int>();
 9 
10             for (int i = 0; i < 50000; i++)
11             {
12                 list.Add(rand.Next());
13             }
14 
15             var watch = Stopwatch.StartNew();
16 
17             try
18             {
19                 BubbleSort(list);
20             }
21             catch (Exception ex)
22             {
23                 Console.WriteLine(ex.Message);
24             }
25 
26             watch.Stop();
27 
28             Console.WriteLine("耗费时间:{0}", watch.Elapsed);
29         }
30 
31         //冒泡排序算法
32         static List<int> BubbleSort(List<int> list)
33         {
34             int temp;
35             //第一层循环: 代表要比较的次数,好比list.count个数,确定要比较count-1次
36             for (int i = 0; i < list.Count - 1; i++)
37             {
38                 //list.count-1:取数据最后一个数下标,
39                 //j>i: 从后往前的的下标必定大于从前日后的下标,不然就超越了。
40                 for (int j = list.Count - 1; j > i; j--)
41                 {
42                     //若是前面一个数大于后面一个数则交换
43                     if (list[j - 1] > list[j])
44                     {
45                         temp = list[j - 1];
46                         list[j - 1] = list[j];
47                         list[j] = temp;
48                     }
49                 }
50             }
51             return list;
52         }
53     }
54 }

Debug下面的执行效率:测试

 

Release下面的执行效率:优化

从上面两张图能够看到,debug和release版本之间的性能差别能达到将近4倍的差距。。。仍是至关震撼的。spa

 

二:release应该注意的bug.net

  release确实是一个很是好的东西,可是在享受好处的同时也不要忘了,任何优化都是要付出代价的,这世界不会什么好事都让你给占了,release有时候为了pwa

性能提高,会大胆的给你作一些代码优化和cpu指令的优化,好比说把你的一些变量和参数缓存在cpu的高速缓存中,否则的话,你的性能能提高这么多么~~~

绝大多数状况下都不会遇到问题,但有时你很不幸,要出就出大问题,下面我一样举一个例子给你们演示一下:

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             var isStop = false;
 6 
 7             var task = Task.Factory.StartNew(() =>
 8             {
 9                 var isSuccess = false;
10 
11                 while (!isStop)
12                 {
13                     isSuccess = !isSuccess;
14                 }
15             });
16 
17             Thread.Sleep(1000);
18             isStop = true;
19             task.Wait();
20 
21             Console.WriteLine("主线程执行结束!");
22             Console.ReadLine();
23         }
24     }

 

上面这串代码的意思很简单,我就不费劲给你们解释了,可是有意思的事情就是,这段代码在debug和release的环境下执行的结果倒是天壤之别,而咱们的常规

思想其实就是1ms以后,主线程执行console.writeline(...)对吧,而真相倒是:debug正常输出,release却长久卡顿。。。。一直wait啦。。。。这是一个大

bug啊。。。不信的话你能够看看下面的截图嘛。。。

 

debug:

 

release:

 

三:问题猜想

刚才也说过了,release版本会在jit层面对il代码进行优化,因此看应用程序的il代码是看不出什么名堂的,可是能够大概能猜到的就是,要么jit直接把代码

1 while (!isStop)
2 {
3       isSuccess = !isSuccess;
4  }

优化成了

1 while (true)
2 {
3      isSuccess = !isSuccess;
4 }

 

要么就是为了加快执行速度,mainthread和task会将isStop变量从memory中加载到各自的cpu缓存中,而主线程执行isStop=true的时候而task读的仍是cpu

缓存中的脏数据,也就是仍是按照isStop=false的状况进行执行。

 

四:三种解决方案

1:volatile 

那这个问题该怎么解决呢?你们第一个想到的就是volatile关键词,这个关键词我想你们都知道有2个意思:

<1>. 告诉编译器,jit,cpu不要对我进行任何形式的优化,谢谢。

<2>. 该变量必须从memory中读取,而不是cpu cache中。

因此能够将上面的代码优化成以下方式,问题就能够完美解决:

 1    class Program
 2     {
 3         volatile static bool isStop = false;
 4 
 5         static void Main(string[] args)
 6         {
 7             var task = Task.Factory.StartNew(() =>
 8             {
 9                 var isSuccess = false;
10 
11                 while (!isStop)
12                 {
13                     isSuccess = !isSuccess;
14                 }
15             });
16 
17             Thread.Sleep(1000);
18             isStop = true;
19             task.Wait();
20 
21             Console.WriteLine("主线程执行结束!");
22             Console.ReadLine();
23         }
24     }

 

 

2:Thread.VolatileRead

  这个方法也是.net后来新增的一个方法,它的做用就是告诉CLR,我须要从memory中进行读取,而不是cpu cache中,不信能够看下注释。

 1         //
 2         // 摘要:
 3         //     读取字段值。不管处理器的数目或处理器缓存的状态如何,该值都是由计算机的任何处理器写入的最新值。
 4         //
 5         // 参数:
 6         //   address:
 7         //     要读取的字段。
 8         //
 9         // 返回结果:
10         //     由任何处理器写入字段的最新值。
11         public static byte VolatileRead(ref byte address);

 

不过很遗憾,这吊毛没有bool类型的参数,只有int类型。。。操,,,为了测试只能将isStop改为0,1这两种int状态,哎。。。

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             int isStop = 0;
 6 
 7             var task = Task.Factory.StartNew(() =>
 8             {
 9                 var isSuccess = false;
10 
11                 while (isStop != 1)
12                 {
13                     //每次循环都要从内存中读取 ”isStop“ 的最新值
14                     Thread.VolatileRead(ref isStop);
15 
16                     isSuccess = !isSuccess;
17                 }
18             });
19 
20             Thread.Sleep(1000);
21             isStop = 1;
22             task.Wait();
23 
24             Console.WriteLine("主线程执行结束!");
25             Console.ReadLine();
26         }
27     }

 

3: Thread.MemoryBarrier

  其实这个方法在MSDN上的解释看起来让人以为莫名奇妙,根本就看不懂。

1         //
2         // 摘要:
3         //     按以下方式同步内存存取:执行当前线程的处理器在对指令从新排序时,不能采用先执行 System.Threading.Thread.MemoryBarrier
4         //     调用以后的内存存取,再执行 System.Threading.Thread.MemoryBarrier 调用以前的内存存取的方式。
5         [SecuritySafeCritical]
6         public static void MemoryBarrier();

其实这句话大概就两个意思:

 

<1>. 优化cpu指令排序。

<2>. 调用MemoryBarrier以后,在MemoryBarrier以前的变量写入都要从cache更新到memory中。

        调用MemoryBarrier以后,在MemroyBarrier以后的变量读取都要从memory中读取,而不是cpu cache中。

 

因此基于上面两条策略,咱们能够用Thread.MemoryBarrier进行改造,代码以下:

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             bool isStop = false;
 6 
 7             var task = Task.Factory.StartNew(() =>
 8             {
 9                 var isSuccess = false;
10 
11                 while (!isStop)
12                 {
13                     Thread.MemoryBarrier();
14                     isSuccess = !isSuccess;
15                 }
16             });
17 
18             Thread.Sleep(1000);
19             isStop = true;
20             task.Wait();
21 
22             Console.WriteLine("主线程执行结束!");
23             Console.ReadLine();
24         }
25     }

 

总结一下,在多线程环境下,多个线程对一个共享变量进行读写是一个很危险的操做,缘由我想你们都明白了,这个时候你就能够用到上面三种手段进行解决

啦。。。好了,本篇就说到这里,但愿对你有帮助。

相关文章
相关标签/搜索