效能優化之二:結構體型別的效能優化
阿新 • • 發佈:2020-01-27
C#裡結構體是值型別,其區域性變數的空間分配在棧上。很多同學喜歡用它,是因為它的儲存密度高、分配和回收成本非常低。
但是前幾天在查熱點的時候,卻碰到結構體的效能非常慢,甚至遠低於把同樣資料結構做成的引用型別。下文對這個問題做了些簡化,方便大家理解。
程式碼分析
優化前的原始碼示例:
//結構體宣告 public struct Point2D { public int X { get; set; } public int Y { get; set; } } var target = new Point2D() { X = 99, Y = 100 }; //熱點語句,points 是一個有幾百萬元素的連結串列: foreach(var item in point2Ds) { if (item.Equals(target)) return target; }
優化方法很簡單,就是在Point2D的結構體宣告中,加一個手寫的Equals方法:
//優化後: public struct Point2D { public int X { get; set; } public int Y { get; set; } public bool Equals(Point2D obj) { return obj.X == this.X && obj.Y == this.Y; } }
效能測試
構造一個有1千萬元素的points。
優化前的執行時間(單位:ms)
優化後的執行時間(單位:ms)
前後提升差不多50%。
原理分析
檢視IL可以發現,優化後是呼叫的Point2D.Equals方法,也就是我們寫的方法:
而優化前的IL如下,是呼叫Object.Equals方法:
那麼,這兩者有什麼區別呢?
可以檢視一下struct的Equals方法。由於struct是值型別,它從ValueType繼承來,因此Equals方法實際是執行的ValueType.Equals。
原始碼地址:https://referencesource.microsoft.com/mscorlib/system/valuetype.cs.html
public abstract class ValueType { [System.Security.SecuritySafeCritical] public override bool Equals (Object obj) { BCLDebug.Perf(false, "ValueType::Equals is not fast. "+this.GetType().FullName+" should override Equals(Object)"); if (null==obj) { return false; } RuntimeType thisType = (RuntimeType)this.GetType(); RuntimeType thatType = (RuntimeType)obj.GetType(); if (thatType!=thisType) { return false; } Object thisObj = (Object)this; Object thisResult, thatResult; // if there are no GC references in this object we can avoid reflection // and do a fast memcmp if (CanCompareBits(this)) return FastEqualsCheck(thisObj, obj); FieldInfo[] thisFields = thisType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); for (int i=0; i<thisFields.Length; i++) { thisResult = ((RtFieldInfo)thisFields[i]).UnsafeGetValue(thisObj); thatResult = ((RtFieldInfo)thisFields[i]).UnsafeGetValue(obj); if (thisResult == null) { if (thatResult != null) return false; } else if (!thisResult.Equals(thatResult)) { return false; } } return true; } }
可以發現,ValueType.Equals方法並不是直接比較的兩者引用地址是否相等,而是遞迴遍歷struct的每個欄位,判斷它們是否相等。而在遍歷struct欄位時,使用了反射取值,這是很耗效能的。
另外,由於其引數是Object型別,會把傳入的struct做一次裝箱,這也是熱點。
而我們寫的方法,是直接對比屬性,而且傳入引數是Point2D型別,也不用裝箱,可以直接使用。
總結一下,在使用結構體的時候,避免裝箱,重寫Equals方法避免原Equals的反射。
效能優化相關文章:
微服務下,介面效能優化的一些總結
&n