性能優化之二:結構體類型的性能優化


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的反射。

 

性能優化相關文章:

微服務下,接口性能優化的一些總結

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM