性能优化之二:结构体类型的性能优化

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

性能优化相关文章:

微服务下,接口性能优化的一些总结

原文地址:https://www.cnblogs.com/cc299/p/12234816.html

时间: 2024-11-13 09:00:46

性能优化之二:结构体类型的性能优化的相关文章

自定义数据类型 C++ 结构体类型 共同体类型 枚举类型 类类型{}

一.结构体类型 结构体类型,共用体类型,枚举类型,类类型等统称为自定义类型(user-defined-type,UDT). 结构体相当于其他高级语言中的记录(record);例如: struct Student{ int num; char name[20]; char sex; int agel float score; char addr[30]; }; 结构体声明的一般形式: struct 结构体类型名 {成员列表}: 结构体类型名用作结构体类型的标志,上面的Student就是结构体类型名

语言中结构体变量和结构体类型的定义

1.结构体类型定义 定义方式1: Typedef struct  LNode {    int  data;  // 数据域    struct LNode   *next;  // 指针域 } *LinkList; 定义方式2: struct  LNode {    int  data;  // 数据域    struct LNode   *next;  // 指针域 }: Typedef struct  LNode  *LinkList; 以上两个定义方式是等价的,是将*LinkList定义

关于结构体类型的学习

1. 定义结构体类型时,最后的分号一定不能忘: struct st { int num; int s; //分号不能少 }; 2. 关于结构体成员的获取: int main(void) { struct st a[3]; int i=0; scanf("%d",a); //输入a[0].num的值 scanf("%d",&a->s); //a.s的值 scanf("%d",(a+1)); //a[1].num scanf("

指向结构体类型的指针强制转换为指向另外一种结构体类型。会不会出现问题?

结构体和int等类型一样,都是数据类型.其他类型怎么转换,结构体就怎么转换,没有什么特殊的地方. 楼主可能想知道的不是结构体怎样强制转换这个问题吧,猜测,楼主想知道如下几个问题: 如果将一个结构体强制类型转换为另一个结构体(或者类型),那这个结构体的成员会怎样了? 如果将一个结构体强制类型转换为另一个结构体(或者类型),那么这个结构体成员的数值又会是什么了? 解答: 1.结构体的本质是:我们和C语言约定了一段内存空间的长短,及其内容的安排.假设下面两个结构体: struct A1 { int a

C语言中的系统时间结构体类型

在C语言涉及中经常需要定时触发事件,涉及到获取系统时间,其结构体类型有多种.Unix/Linux系统下有以下几种时间结构: 1.time_t 类型:长整型,一般用来表示从1970-01-01 00:00:00时以来的秒数,精确度:秒:由函数time()获取: 该类型定义在头文件 /usr/include/sys/time.h 中: #define _TIME_T       typedef   long   time_t;             #endif 函数定义:time_t   tim

结构体类型重声明导致的bug一个

bug前提条件 当模块比较多,头文件较多,某个结构体类型会在当前模块中重新声明进而引用其成员,而不直接包含其他模块的头文件.这样的好处是不引入不需要的类型声明到此模块,头文件包含的交叉:坏处是,增加了bug的几率,耦合太大!比如下面一种情况发生而导致bug: 已知两个模块A和B,同一个结构类型struct node在两个模块中分别声明,其中B模块无意或者有意调整了结构类型中的某些域.那么这个时候,若B模块中引用A模块中此类型实例然后访问成员变量,就会引发bug!如下重现示例 bug重新示例代码

Swift2.0(6)结构体类型&枚举类型

结构体类型 基础数据类型都是结构体,如Int  Float Bool等,是Swift自带的并且作为开发基础供开发者使用 在Swift中,结构体(Struct)和类类型(Class)非常相似,结构体是值类型,类是引用类型. 定义格式: struct 名称 : 协议... { 属性和方法 } 如: struct Sword { var length:Int = 11 var name:String = "hello world" func description() { print(&qu

结构体类型快速驱魔运算及运算符的重载

下面得到这段代码可以用在很多地方:只需要自己修改下接Ok. 1 struct Matrix 2 { 3 long long mat[N][N]; 4 Matrix operator*(const Matrix m)const//定义矩阵乘法的运算符* 5 { 6 Matrix tmp; 7 for(int i = 0;i < n;i++) 8 { 9 for(int j = 0;j < n;j++) 10 { 11 tmp.mat[i][j] = 0; 12 for(int k = 0;k &

声明结构体类型

偶然发现代码里面有个未定一个结构体类型,形式如下: struct vas; 而在当前文件中又实现了其定义. 一开始不理解其意义,看完代码发现了其作用: 因为另一个包含了它的结构体的定义代码放在了struct vas定义的前面,前面的struct vas是为了告诉编译器,这个struct vas是定义了的,后面是其真正定义. #include <stdio.h> struct vas; struct devs{ struct vas vaa; char* name; int v; }; stru