今天分享一下平常工做中遇到的性能問題和解決方案,比較零碎,後續會持續更新(運行環境爲.net core 3.1)html
本次分享的案例都是由實際生產而來,通過簡化後做爲舉例sql
關於class和struct的區別,根據經驗,在實際開發的絕大多數場景,都會使用class做爲數據類型,可是若是是做爲簡單數據的超大集合的類型,而且不涉及到拷貝、傳參等其餘操做的時候,能夠考慮使用struct,由於相對於引用類型的class分配在堆上,做爲值類型的struct是分配在棧上的,這樣就擁有了更快的建立速度和節約了指針的空間,列舉了3000萬個元素的集合分別以class和struct做爲類型,作以下測試(測試工具爲vs自帶的 Diagnostic Tools):數組
class Program { static void Main (string[] args) { var structs = new List<StructTest> (); var stopwatch1 = new Stopwatch (); stopwatch1.Start (); for (int i = 0; i < 30000000; i++) { structs.Add (new StructTest { Id = i, Value = i }); } stopwatch1.Stop (); var structsTotalMemory = GC.GetTotalMemory (true); Console.WriteLine ($"使用結構體時消耗內存:{structsTotalMemory}字節,耗時:{stopwatch1.ElapsedMilliseconds}毫秒"); Console.ReadLine (); } public struct StructTest { public int Id { get; set; } public int Value { get; set; } } }
class Program { static void Main (string[] args) { var classes = new List<ClassTest> (); var stopwatch2 = new Stopwatch (); stopwatch2.Start (); for (int i = 0; i < 30000000; i++) { classes.Add (new ClassTest { Id = i, Value = i }); } stopwatch2.Stop (); var classesTotalMemory = GC.GetTotalMemory (true); Console.WriteLine ($"使用類時消耗內存:{classesTotalMemory}字節,耗時:{ stopwatch2.ElapsedMilliseconds}毫秒"); Console.ReadLine (); } public struct StructTest { public int Id { get; set; } public int Value { get; set; } } }
經過計算,struct的空間消耗包含了:每一個結構體包含兩個存放在棧上的整型,每一個整型佔4個字節,每一個結構體佔8字節,乘以3000萬個元素共計佔用240,000,000字節, 跟實際測量值大致吻合;dom
而class的空間消耗較爲複雜,包含了:每一個類包含兩個存在堆上的整型,每一個整型佔4字節,兩個存在棧上的指針,由於是64位計算機因此每一個指針佔8字節,再加上類自身的指針8字節,每一個類佔24字節(4+4+8+8+8),乘以3000萬個元素共計佔用960,000,000字節,跟實際測量值大致吻合。時間消耗方面class由於存在內存分配,耗時5秒左右,遠大於struct的1.5秒。ide
基於這次測試,工具
更多關於class和struct的關係和區別請移步微軟官方文檔 https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/choosing-between-class-and-structpost
關於嵌套集合遍歷,咱們以兩層集合嵌套遍歷,每一個集合存放10000個亂序的整型,而後統計同時存在兩個集合的元素個數,從上到下分別以常規嵌套循環,使用HashSet類型,參考PostgreSQL的MergeJoin思路舉例:性能
class Program { static void Main (string[] args) { var l1s = new List<int> (); var l2s = new List<int> (); var rd = new Random (); for (int i = 0; i < 10000; i++) { l1s.Add (rd.Next (1, 10000)); l2s.Add (rd.Next (1, 10000)); } var sw = new Stopwatch (); sw.Start (); var r = new HashSet<int> (); foreach (var l1 in l1s) { foreach (var l2 in l2s) { if (l1 == l2) { r.Add (l1); } } } sw.Stop (); Console.WriteLine ($"共找到{r.Count}個元素同時存在於l1s和l2s,共計耗時{sw.ElapsedMilliseconds}毫秒"); Console.ReadLine (); }
class Program { static void Main (string[] args) { var l1s = new HashSet<int> (); var l2s = new HashSet<int> (); var rd = new Random (); while (l1s.Count < 10000) l1s.Add (rd.Next (1, 100000)); while (l2s.Count < 10000) l2s.Add (rd.Next (1, 100000)); var sw = new Stopwatch (); sw.Start (); var r = new List<int> (); foreach (var l1 in l1s) { if (l2s.Contains (l1)) { r.Add (l1); } } sw.Stop (); Console.WriteLine ($"共找到{r.Count}個元素同時存在於l1s和l2s,共計耗時{sw.ElapsedMilliseconds}毫秒"); Console.ReadLine (); }
class Program { static void Main (string[] args) { var l1s = new List<int> (); var l2s = new List<int> (); var rd = new Random (); for (int i = 0; i < 10000; i++) { l1s.Add (rd.Next (1, 10000)); l2s.Add (rd.Next (1, 10000)); } var sw = new Stopwatch (); sw.Start (); var r = new List<int> (); l1s = l1s.OrderBy (x => x).ToList (); l2s = l2s.OrderBy (x => x).ToList (); var l1index = 0; var l2index = 0; for (int i = 0; i < 10000; i++) { var l1v = l1s[l1index]; var l2v = l2s[l2index]; if (l1v == l2v) { r.Add (l1v); l1index++; l2index++; } if (l1v > l2v && l2index < 10000) l2index++; if (l1v < l2v && l1index < 10000) l1index++; if (l1index == 9999 && l2index == 9999) break; } sw.Stop (); Console.WriteLine ($"共找到{r.Count}個元素同時存在於l1和l2s,共計耗時{sw.ElapsedMilliseconds}毫秒"); Console.ReadLine (); }
由結果可見,常規嵌套遍歷耗時1秒,時間複雜度爲O(n2);使用HashSet耗時3毫秒,HashSet底層使用了哈希表,經過循環外層集合,對內層集合直接進行hash查找,時間複雜度爲O(n); 參考PostgreSQL的MergeJoin思路實現耗時19毫秒,方法爲先對集合進行排序,再標記當前位移,利用數組能夠下標直接取值的特性取值後對比,時間複雜度爲O(n)。因而可知,對於數據量較大的集合,嵌套循環要尤其重視起來。學習
更多關於merge join的設計思路請移步PostgreSQL的官方文檔 https://www.postgresql.org/docs/12/planner-optimizer.html測試
要注意的是,不管是使用哈希表仍是排序,都會引入額外的損耗,畢竟在計算機的世界裏,要麼以時間換空間,要麼以空間換時間,若是想同時優化時間或空間能夠辦到嗎?在某些場景上也是有可能的,能夠參考我以前的博文,經過內存映射文件結合今天講的內容,結合具體業務場景嘗試一下。
若有任何問題,歡迎你們隨時指正,分享和試錯也是個學習的過程,謝謝你們~