上一篇将关于字符串以及值类型引用类型的性能增强大致说完,下面继续
关于装箱那点事
C#中的一切皆是对象,这意味着他们都继承自System.Object类,甚至int、float、bool等基本数据类型,都隐式的从System.Object中继承,每当这些值类型以处理对象的方式隐式的处理时,CLR会自动创建一个临时对象来存储或装箱内部的值,以便将其视为典型的引用类型对象,显然这将会导致堆分配,以创建包含的容器;
注意:装箱和将值类型作为引用类型的成员变量不同,装箱仅在通过转化或强制转化将值类型视为引用类型的时候发生
涉及到装箱的几种情况 这种情况就是会将整型变量i装箱到对象a中; 下面的代码使用对象表示obj以替代保存在整型中的值,并拆箱回整型,将其保存在i中,最终i的值是256 这些类型可以动态修改,下面的完全盒饭的C#代码,它覆盖了obj的类型,将其转换为float类型
注意,尝试将obj拆箱到一个不是最新赋值的类型时,将引发InvalidCastException异常; 最近的一次转换时float类型,这里将抛出InvalidCastException异常;
要知道,内存中的一些都是数据为,而我们可以想要的方式自由的去解视这些数据,毕竟int、float 等数据类型都是0,1的二进制列表的抽象,重要的时,可以通过装箱基本类型将其当成对象,转换他们的类型,随后将他们拆箱回不同的类型,但是每次这样做都会涉及到堆内存分配;
可以通过多个System.convert.To…()方法转换装箱对象的类型
此外,装箱可以是隐式的,如前面的实例,但是也可以是显式的,通过类型前置转化为System.object。而拆箱必须显式强制类型转化为特德原始类型,当值类型传递到使用System.object作为参数的方法的时候,装箱会隐式的进行; 例如将System.obj当作参数传递到System.format中,就是这样的实例,使用的时候通常传入int、float、bool等值类型,以生成字符串,装箱则在这个时候自动发生,额外的产生值得注意的堆分配,ArrayList则是另一个这样的示例,他会将传入的参数,转换成引用类型,而不管他保存的是什么类型; 只要函数使用System.object作为参数而传递了值类型,就应该意识到由于装箱而导致堆分配;
数据布局的重要性
数据在内存中的组织方式的重要性很容易被以外,但是如果处理得当,会带来相当大的性能提升,不管什么时候都需要避免缓存丢失,这意味着在大多数情况下,内存中连续存储的大量数据都应该按照顺序迭代,而不是以其他迭代方式进行迭代; 这意味着,数据布局堆GC来说也很重要,如果设法让GC跳过有问题的区域,就潜在的节省了大量迭代的时间; 本质上,我们希望将大量引用类型和值类型分开,如果值类型中内涵一个引用类型,那么GC将关注整个对象,以及它所有的成员数据、间接黑引用的对象,当发生标记-清除时,必须在移动之前验证对象的所有字段,然而如果将不同类型分离到不同数组中,那么GC可以跳过大量数据。 例如,如果有一个结构体对象数据,如下,那么GC需要迭代每个结构体的每个成员,这是相当耗时的; 然而,如果每次将所有数据块重新组织到多个数组,那么GC会忽略所有基本数据类型,只检查字符串对象,如下代码,将使GC清除的更快; 这样做的原因使减少了GC要检查的间接引用,当数据划分为多个独立数组的时候,GC会找到3个值类型数据,标记数组,接着例可继续其他工作,因为没有任何理由标记值类型数组的内容,此时依然必须迭代mtstrings中的所有字符串对象,以检查其中是否有包含间接引用,技术上而言,字符串都西昂没有包含间接引用,但GC工作的层级只知道对象是引用类型还是值类型,因为他不知道字符串和类之间的别,GC依然不需要迭代额外的300条数据myInts、myFloats、myBools;
Unity API
UnityAPI中有很多指令会导致堆内存分配,这些需要注意,这些指令本质上就是包含了返回数组数据的指令,例如:
- GetComponent()
- Mesh.vertices;
- Camera.allCameras
每次调用unity返回数组的API方法时,将导致分配该数据的全新版本,这些地方应该尽可能避免使用,或者仅调用很少的次数并缓存结果,避免比实际上所需要的更频繁的内存分配;
闭包
闭包是很有用但是很危险的工具,匿名函数和lambda表达式可以是闭包,但并不总是闭包,这取决于方法是否使用了它的作用域和参数列表之外的数据; 如下的匿名函数并不是一个闭包,因为他是自包含的;
然而,如果匿名函数拉入了它作用域之外的数据,该匿名函数会成为闭包,因为它封闭了所需数据的环境; 为了完成该事务,编译其必须要定义新的自定义类,它引用可访问的数据值l所在的环境,在运行时,它在堆上创建相应的对象,并且将它提供给匿名函数,注意着包含了值类型,该值类型最初在栈上,这可能会破坏最初在栈上分配他们的目的,因此第二个方法的每次调用都会导致堆分配以及无法避免的垃圾回收;
.NET库函数
.NET类库提供了海量通用功能,以帮助程序员解决在日常开发中可能遇到的大量问题,这些类和函数大多数为统用用例进行了优化,但是他并不是针对特定情况进行的优化,可以使用更加适合特定用力的自定义实现来替换.NET库函数的特定类; .NET类库中有两大特性通常会在使用的时候造成重大的性能问题,着往往往往是因为他们只作为对给定问题的应急解决方案,LINQ和正则表达式,应该尽可能少的使用他们,或者用更低消耗的方式去替代他们,这里不再展开讲,可以自行百度相关画题以优化他们的方式;
|