由一个性能问题引出的.net概念

概念|问题|性能

由一个性能问题引出的.net概念
关键字:.net 性能 GC 值类型 引用类型 堆 堆栈 string

1 引子
我们先来看一下两组代码,每组中的哪一段代码效率更高呢?

第一组:

代码1:

for(int i = 0; i < 10000; i++)

{

AddressData ds = new AddresssData();

ds = addressS.GetAddress();

}

代码2:

for(int i = 0; i < 10000; i++)

{

AddressData ds;

ds = addressS.GetAddress();

}

第二组:

代码一:

string strNames = "@"+Guid.NewGuid().ToString().Replace("-","")+ ",@"+Guid.NewGuid().ToString().Replace("-","")+ ",@"+Guid.NewGuid().ToString().Replace("-","");

for(int i = 0; i < 10000; i++)

{

……

}

代码2:

for(int i = 0; i < 10000; i++)

{

string strNames = "@"+Guid.NewGuid().ToString().Replace("-","")+ ",@"+Guid.NewGuid().ToString().Replace("-","")+ ",@"+Guid.NewGuid().ToString().Replace("-","");

……

}

每一组代码中,两段代码实现的功能是一样的,它们之间的区别也很小,但是其效率会相差得惊人,其中一种会很频繁的GC,为什么呢?

在回答这个问题的时候我们先了解几个.net概念。

2 什么是GC
GC的全称是garbage collection,中文名称垃圾回收,是.net中对内存管理的一种功能。垃圾回收器跟踪并回收托管内存中分配的对象,定期执行垃圾回收以回收分配给没有有效引用的对象的内存。当使用可用内存不能满足内存请求时,GC会自动进行。

在进行垃圾回收时,垃圾回收器回首先搜索内存中的托管对象,然后从托管代码中搜索被引用的对象并标记为有效,接着释放没有被标记为有效的对象并收回内存,最后整理内存将有效对象挪动到一起。这就是GC的四个步骤。

由上可见,GC是很影响性能的,所以一般说来这种事情况还是尽量少发生为好。

为了减少一些性能影响,.net的GC支持对象老化,或者说分代的概念,代是对象在内存中相对存现时期的度量单位,对象的代数或存现时期说明对象所属的代。目前.net的垃圾回收器支持三代。每进行一次GC,没有被回收的对象就自动提升一代。较近创建的对象属于较新的代,比在应用程序生命周期中较早创建的对象的代数低。最近代中的对象位于零代中。每一次GC的时候,都首先回收零代中的对象,只有在较低代数的对象回收完成后仍不能满足需求的情况下才回收较高代数的对象。

3 堆栈和堆
内存有堆栈和堆的概念。堆栈遵循后进先出的原则,后被推入堆栈的对象必定实现本拉出堆栈,这样保证了这部分内存的紧凑,也基本上不需要考虑内存地址的问题。而堆则没有这个原则,任何一个对象都有可能在任何时候进入堆中,也可能在任何时候被移出堆。这样很明显,我们就要考虑每一个对象保存在哪里,所以就需要在堆栈中保存每一个对象保存在堆中的地址。同时在经过一段时间之后我们就会发现堆中产生了许多空隙,也就是碎片,为了提高系统性能,我们这个时候经常需要整理堆,以清除碎片。关于堆栈和堆,如下图所示:

4 GC和堆栈、堆
由前述堆栈和堆的概念可以看出,堆栈不存在垃圾收集的问题,只需要直接压栈即可,而堆,则面临着很复杂的垃圾回收的问题。GC完全是对堆进行操作的,而对堆中对象是否有效的判断则是通过遍历堆栈来实现的。这里涉及到一个引用计数的概念,引用计数是对堆中对象被引用次数的统计,当一个对象的引用计数为零了,那么这个对象就可以被回收了。在进行GC的时候,垃圾回收器遍历堆栈,当发现一个堆地址的时候,它就将堆中该地址上的对象的引用计数加1,然后销毁堆中所有引用计数为零的对象,回收内存并整理堆中的碎片。

5 值类型和引用类型
我们都知道,计算机中的数据类型分为值类型和引用类型两种。那么到底什么是值类型什么是引用类型呢?

大多数编程语言提供内置的数据类型(比如整数和浮点数),这些数据类型会在作为参数传递时被复制(即,它们通过值来传递)。在 .NET Framework 中,这些称为值类型。运行库支持两种值类型:内置值类型和用户定义的值类型。

引用类型则存储对值的内存地址的引用。引用类型可以是自描述类型、指针类型或接口类型。引用类型的类型可以由自描述类型的值来确定。自描述类型进一步细分成数组和类类型。类类型是用户定义的类、装箱的值类型和委托。

作为值类型的变量,每个都有自己的数据副本,因此对一个变量的操作不会影响其他变量。作为引用类型的变量可以引用同一对象;因此对一个变量的操作会影响另一个变量所引用的同一对象。

6 值类型、引用类型和堆栈、堆
了解了值类型和引用类型,那么,这两种类型在内存中又是怎样表现的呢?

值类型存储在堆栈中,而引用类型则存储在堆中,然后在堆栈中存储对堆中对象的引用(又叫指针),如下图所示:

因为这样的一种存储方式,于是就造成了对变量操作影响的不同,例如通过引用指针b对数据所作的更改也会表现在通过引用指针c的得到的数据中。而对值类型进行操作却不会有这样的情况。

这两种不同也会表现在我们的方式上,例如:

我们假设ModifyClass()方法是对ClassA中的字段Value1加2;

ClassA ca = new ClassA();

ca.Value1 = 2;

ModifyClass(ca);

int getValue = ca.Value1;

……

这时你可以看到getValue的值是4,而ModifyClass()方法并没有返回任何数据。

而对于值类型,我们就会发现这样做是不可以的,你必须要让方法有返回数据,如:

int ca = 2

int getValue = ModifyValue(ca);

值类型和引用类型的区别还在于声明新变量的时候,如ClassA ca = null是合法的,而int ca = null则是非法的。

7 类实例化的步骤
类是最常见也是我们用的最多的一种引用类型,我们知道实例化一个类使用的是一个我们司空见惯的语句:

ClassA ca = new ClassA();

那么这短短的一句话中,计算机又做了些什么事情呢?

实际上,计算机在这个过程中大致做了这么几件事:

首先,在ClassA ca的时候,生成一个空的引用指针,并将它推入堆栈中:

然后,在new ClassA()的时候,生成ClassA的新的实例,并放入堆中:

在赋值号=这一步,将ca的引用指针指向刚刚生成的新实例:

这个时候,才算完成了整条语句的操作。

好了,了解了以上这些概念之后,我们可以来回答本文开始的问题了:

8 回答本文开始的问题
关于第一组代码,我们首先要了解

AddressData ds = new AddresssData();

ds = addressS.GetAddress();

在内存中的情况,我们已经知道,在上述代码的第一句完成之后,会是这样的一个样子:

而在第二句代码完成之后,就会变成这样一个样子:

其中ClassA的实例1是第代码一句产生的,实例2是addressS.GetAddress()方法产生的,实例2才是有效的对象,而实例1则不幸落了个一产生就只能等待着被垃圾回收器回收掉的命运。

一般而言,这种做法不会带来太大的性能问题,但是在某些情况下呢?例如本文一开始演示的这样一个循环?

这个时候就会在堆中产生非常多的垃圾,占用了大量的内存,于是不得不不断的GC,从而严重影响性能。

那么对于第二组代码又会怎样呢?

看起来,第二组代码和第一组代码很不一样,一个是类这种典型的引用类型,而一种是字符串这种通常看起来像是值类型的东西。

实际上,字符串是一种很特殊的东东,它兼有值类型和引用类型的特征,例如我们必须用这样的方式对他进行处理:

string ds = “This is a Test”;

ds = ModifyString(ds);

其中需要注意的是第二句,这是典型的对值类型进行操作的方式,ModifyString()方法必须要返回数据;但另一方面,我们初始化一个新的字符串变量的时候却又可以这样写:string ds = null。很奇怪对吧,我感觉,之所以会出现这种情况的原因可能是设计者希望它能尽量的和其他的值类型如int,float等数据类型的使用方式一致,毕竟它本身的特征和给我们的感觉是如此的相似,但它却又是无法固定长度的,int和float等等我们都明确的规定了它是多少位的,string却不行。

由于这个原因,造成了第二组两段代码之间的性能差异。

9 一些题外话:一定要用Class吗?
我们知道Class是引用类型的,struct则是一种值类型,Class是面向对象编程时代所特有的,而我们称作结构的struct则只是面向对象编程萌芽阶段出现的一个实现对象化的一个变通做法,以至于java就抛弃了struct的概念,那么.net为什么没有放弃java放弃了的东西呢?

struct不是垃圾。首先,struct是一种值类型,这样使得它可以存放在堆栈中,而不是堆中,也就是说,它不会带来GC的性能影响;其次,例如一个对象中有100个元素,如果把这个对象分别定义成类和结构各会占用多少内存空间呢?答案是,定义为类会占用101块内存空间,分别是100个元素和引用指针的;而定义成结构只占用100块而已。也许你会说,才多占用这么一块没什么关系,不过增加了1%而已,那么,如果这个对象只有三个元素呢?

所以我们可以很明白的得出结论,Class不是唯一。

时间: 2024-12-23 04:39:02

由一个性能问题引出的.net概念的相关文章

一个性能较好的JVM参数配置

一个性能较好的web服务器jvm参数配置: -server//服务器模式 -Xmx2g //JVM最大允许分配的堆内存,按需分配 -Xms2g //JVM初始分配的堆内存,一般和Xmx配置成一样以避免每次gc后JVM重新分配内存. -Xmn256m //年轻代内存大小,整个JVM内存=年轻代 + 年老代 + 持久代 -XX:PermSize=128m //持久代内存大小 -Xss256k //设置每个线程的堆栈大小 -XX:+DisableExplicitGC //忽略手动调用GC, Syste

做一个合格的Team Leader -- 基本概念

1.领导和管理 人们乐于被领导:他们不喜欢被管理,不喜欢像牛一样被驱赶或指挥. 管理者强迫人们服从他们的命令,而领导者则会带领他们一起工作. 管理是客观的,没有个人感情因素,它假定被管理者没有思想和感受,必须被告知要做什么和该如何做.管理适合处理无生命的对象或者例行公事. 领导是引领.引导,它激励人们达成目标.领导力是带有强烈个人感情色彩的,它不是你能命令的,也不是你能测量评估和测试的. 2.变革型领导(内在激励)和交易型领导(外在激励)的区别. 3.Team Leader的任务 从一个更概括性

Oracle分区方法详解

下面我分别对这四种分区方法的概念,他们的使用场景,以及各种分区方法做一个性能比较. 一.概念 1.Range Partitioning 这是最常用的一种分区方法,基于COLUMN的值范围做分区,最常见的是基于时间字段的数据的范围的分区,比如:对于SALE表,可以对销售时间按照月份做一个Range Partitioning.这种分区在数据仓库里用的比较多,以下是 CREATE STATMENT CREATE TABLE sales_range (salesman_id NUMBER(5), sal

概述开发流程中的性能生命周期背后的基本概念

性能是 IT 团队常常寻求咨询建议的一个领域,无论是寻求一般指导,还是解决威胁到应用程序或网站稳定性的特定关键性能问题.尽管二者都是讨论网站性能的明确.有效的原因,而不太明显的原因很可能正是 "性能" 的含义.影响它的因素.谁需要参与改进它,而且可能最重要的是,为什么它是开发流程中不可或缺的一部分. 本文概述了性能生命周期背后的重要理念,从项目管理角度和执行角度帮您准备好应对在 Web 项目开发中通常最具挑战性的.至关重要且被忽视了的元素之一.要解决的问题包括: 为什么业务支持者和 I

品牌资产从品牌管理角度提出的一个概念

品牌资产(Brandequity)是上世纪80年代西方管理学界从品牌管理角度提出的一个概念.关于品牌资产概念比较有影响的观点主要有: 美国市场营销科学研究院(MSI,1990)将品牌资产定义为"品牌的顾客.渠道成员.母公司等对于品牌的联想和行为,这些联想和行为使得产品可以获得比在没有品牌名称的条件下更多的销售额或利润,可以赋予品牌超过竞争者的强大.持久和差别化的竞争优势" 美国卡内基梅隆大学教授Peter. farquhar(1989)认为,品牌资产是指"对企业.经销商或消费

通向架构师的道路(第三天)之apache性能调优

一.总结前一天的学习 在前两天的学习中我们知道.了解并掌握了Web Server结合App Server实现单向Https的这样的一个架构.这个架构是一个非常基础的J2ee工程上线布署时的一种架构.在前两天的教程中,还讲述了Http服务器.App Server的最基本安全配置(包括单向https的实现), 它只是避免了用户可以通过浏览器侵入我们的Web访问器或者能够通过Web浏览器来查询我们的Web目录结构及其目录内的文件与相关内容,这种入侵我们把它称为: Directory traversal

小议学习java的浮躁心态-引出篇

"切忌浮躁"--我想这是在任何学习方法中,都会特意提出来的. 在国内的几个论坛也混了几年,甚至眼看着曾经轰轰烈烈的javaunion从成长到消失.在javaunion ,chinajavaworld,dev2dev,javaresearch,cn-java等论坛上,也相遇或结识了很多java爱好者,有经验丰富者,也有初出茅庐者. 其实,国内技术论坛,是个很奇怪很有意思的地方.从这里成长了一批批的开发者,但也从这里消失了一批批的开发者.犹记得javaunion论坛曾经的一篇名为"

如何优化Windows服务器性能

优化Windows服务器磁盘性能 在一台服务器的使用上,磁盘往往占据着很重要的位置.服务器磁盘的配置和维护对整个服务器性能方面产生很大影响.实际上,优化服务器磁盘的方法只需要花费一点点时间.在这篇文章中,我将会分享一些提高你的服务器磁盘子系统效率的方法. 注意:几乎所有的服务器都被做了不相同的配置.也就是说,最适合您的组织的磁盘配置和优化技巧可能对另外一个组织的服务器是有害的.唯一的完全优化一台服务器磁盘的方法是去分析,在这台服务器上都运行了哪些应用,并且这些应用在服务器磁盘子系统中占用了多少工

可预见的Oracle应用程序的性能调优

这篇技巧性文章是由"国际Oracle用户组"(IOUG)提供的,它是一个由用户组成的组织,这个组织通过提供高质量的信息.培训.网络和支持,来提高Oracle数据库专家和数据库开发者的水平.这篇文章摘自由David Welch所写的论文<可预见的Oracle应用程序性能调优>.点击这里成为"国际Oracle用户组"的一员,从而获得成千上万的由Oracle用户写的技巧性文章和科技文献. 引言 我们见到过很多带有巨大性能问题的Oracle应用程序和电子商务套件