通常咱們發佈項目的時候一般都會採用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 }
總結一下,在多線程環境下,多個線程對一個共享變量進行讀寫是一個很危險的操做,緣由我想你們都明白了,這個時候你就能夠用到上面三種手段進行解決
啦。。。好了,本篇就說到這裏,但願對你有幫助。