文章详情

  • 游戏榜单
  • 软件榜单
关闭导航
热搜榜
热门下载
热门标签
php爱好者> php文档>[学习笔记]提高.net 效率的20个建议

[学习笔记]提高.net 效率的20个建议

时间:2010-11-03  来源:天涯走狗

Item1:推荐以Dispose模式来代替Finalize方式。

在本章中关于非托管资源的清理,主要有终止化操作和 Dispose模式两种,其中 Finalize方式存在执行时间不确定,运行顺序不确定,同时对垃圾回收的性能有极大的损伤。因此强烈建议以Dispose模式来代替Finalize方式,在带来性能提升的同时,实现了更加灵活的控制权。

对于二者的详细比较,请参见 5.3节“垃圾回收”的讨论。

Item2:选择合适的垃圾收集器:工作站 GC和服务期GC。

.NET CLR实现了两种垃圾收集器,不同的垃圾收集器应用不同的算法,分别为不同的处理机而设计:工作站 GC主要应用于单处理器系统,而服务器收集器专为多处理器的服务器系统设计,默认情况为工作站收集器。因此,在多处理器系统中如果使用工作站收集器,将大大降低系统的性能,无法适应高吞吐量的并行操作模式,为不同主机选择合适的垃圾收集器是有效提高性能的关键之一。

Item3:在适当的情况下对对象实现弱引用。

为对象实现弱引用,是有效提高性能的手段之一。弱引用是对象引用的一种“中间态”,实现了对象既可以通过 GC回收其内存,又可被应用程序访问的机制。这种看似矛盾的解释,的确对胖对象的内存性能带来提升,因为胖对象需要大量的内存来创建,弱引用机制保证了胖对象在内存不足时 GC可以回收,而不影响内存使用,在没有被 GC回收前又可以再次引用该对象,从而达到空间与时间的双重节约。

在.NET中,WeakReference类用于表示弱引用,通过其 Target属性来表示要追踪的对象,通过其值赋给变量来创建目标对象的强引用,例如:

public void WeakRef() 
{ 
        MyClass mc = new MyClass(); 
        //创建弱引用
        WeakReference wr = new WeakReference(mc); 
        //移除强引用
        mc = null; 
        if (wr.IsAlive) 
        { 
                //弱引用转换为强引用,对象可以再次使用
                mc = wr.Target as MyClass; 
        } 
        else 
        { 
                //对象已经被回收,重新创建
                mc = new MyClass(); 
        } 
} 

关于弱引用的相关讨论,参见 5.3节“垃圾回收”。

Item4:尽可能以using来执行资源清理。

以using语句来执行实现了 Dispose模式的对象,是较好的资源清理选择,简洁优雅的代码实现,同时能够保证自动执行 Dispose 方法来销毁非托管资源,在本章已做详细讨论,

因此值得推荐。

Item5:推荐使用泛型集合来代替非泛型集合。

泛型实现了一种类型安全的算法重用,其最直接的应用正是在集合类中的性能与安全的良好体现,因此我们建议以泛型集合来代替非泛型集合,以 List<T>和ArrayList为例来做以说明:

public static void Main() 
{ 
        //List<T>性能测试
        List<Int32> list = new List<Int32>(); 
        for (Int32 i = 0; i < 10000; i++) 
        //未发生装箱
        list.Add(i); 
        
        //ArrayList性能测试
        ArrayList al = new ArrayList(); 
        for (Int32 j = 0; j < 10000; j++) 
        //发生装箱
        al.Add(j); 
} 

上述示例,仅仅给出了泛型集合和非泛型集合在装箱操作上引起的差别,同样的拆箱操作也伴随了这两种不同集合的取值操作。同时,大量的装箱操作会带来频繁的垃圾回收,类型转换时的安全检查,都不同程度的影响着性能,而这些弊端在泛型集合中荡然无存。 必须明确的是,泛型集合并不能完全代替非泛型集合的应用,.NET框架类库中有大量的集合类用以完成不同的集合操作,例如 ArrayList中包含的很多静态方法是 List<T>所没有的,而这些方法又能为集合操作带来许多便利。因此,恰当地做出选择是非常重要的。

注意,这种性能差别对值类型的影响较大,而引用类型不存在装箱与拆箱问题,因此性能影响不是很明显。关于集合和泛型的讨论,详见 7.9节“集合通论”和第 10章“接触泛型”中的讨论。

Item6:初始化时最好为集合对象指定大小。

长度动态增加的集合类,例如 ArrayList、Queue的等。可以无需指定其容量,集合本身能够根据需求自动增加集合大小,为程序设计带来方便。然而,过分依赖这种特性并非好的选择,因为集合动态增加的过程是一个内存重新分配和集合元素复制的过程,对性能造成一定的影响,所以有必要在集合初始化时指定一个适当的容量。例如:

public static void Main() 
{ 
        ArrayList al = new ArrayList(2); 
        
        al.Add("One"); 
        al.Add("Two"); 

        //容量动态增加一倍
        al.Add("Three"); 
        Console.WriteLine(al.Capacity); 
} 

Item7:特定类型的 Array性能优于ArrayList。

ArrayList只接受Object类型的元素,向ArrayList添加其他值类型元素会发生装箱与拆箱操作,因此在性能上使用 Array更具优势,当然 object类型的数组除外。不过,ArrayList更容易操作和使用,所以这种选择同样存在权衡与比较。

Item8:字符串驻留机制,是 CLR为String类型实现的特殊设计。

String类型无疑是程序设计中使用最频繁、应用最广泛的基元类型,因此 CLR在设计上为了提升String类型性能考虑,实现了一种称为“字符串驻留”的机制,从而实现了相同字符串可能共享内存空间。同时,字符串驻留是进程级的,垃圾回收不能释放 CLR内部哈希表维护的字符串对象,只有进程结束时才释放。这些机制均为 String类型的性能提升和内存优化提供了良好的基础。

关于String类型及其字符串驻留机制的理解,详见 8.3“如此特殊:大话 string”。

Item9:合理使用System.String和System.Text.StringBuilder。

在简单的字符串操作中使用 String,在复杂的字符串操作中使用StringBuilder。简单地说,StringBuilder对象的创建代价较大,在字符串连接目标较少的情况下,应优先使用String类型;而在有大量字符串连接操作的情况下,应优先考虑 StringBuilder。 同时,StringBuilder在使用上,最好指定合适的容量值,否则由于默认容量的不足而频繁进行内存分配的操作会影响系统性能。

关于String和StringBuilder的性能比较,详见 8.3“如此特殊:大话string”的讨论。

Item10:尽量在子类中重写 ToString方法。

ToString方法是System.Object提供的一个公有的虚方法,.NET中任何类型都可继承 System.Object类型提供的实现方法,默认为返回类型全路径名称。在自定义类或结构中重写 ToString 方法,除了可以有效控制输出结果,还能在一定程度上减少装箱操作的发生。

public struct User 
{ 
        public string Name; 
        public Int32 Age; 
        
        //避免方法调用时的装箱
        public override string ToString()        
        { 
                return "Name: " + Name + ", Age:" + Age.ToString(); 
        } 
} 

关于ToString方法的讨论,可以参考 8.1节“万物归宗:System.Object”。

Item11:其他推荐的字符串操作。

字符串比较,常常习惯的做法是:

public bool StringCompare(string str1, string str2) { 
        return str1 == str2; 
} 

而较好的实现应该是:

public int StringCompare(string str1, string str2) 
{ 
        return String.Compare(str1, str2); 
}

二者的差别是:前者调用 String.Equals方法操作,而后者调用 String. Compare方法来实现。String.Equals方法实质是在内部调用一个 EqualsHelper辅助方法来实施比较,内部处理相对复杂。因此,建议使用 String.Compare方式进行比较,尤其是非大小写敏感字符串的比较,在性能上更加有效。

类似的操作包含字符串判空的操作,推荐的用法以 Length属性来判断,例如:

public bool IsEmpty(string str) 
{ 
        return str.Length == 0; 
} 

Item12:for和 foreach的选择。

推荐选择foreach来处理可枚举集合的循环结构,原因如下:

l .NET 2.0以后编译器对foreach进行了很大程度的改善,在性能上foreach和for实际差别不

大。

l foreach语句能够迭代多维数组,能够自动检测数组的上下限。

l foreach语句能够自动适应不同的类型转换。

l foreach语句代码更简洁、优雅,可读性更强。

Item13:以多线程处理应对系统设计。

毫无疑问,多线程技术是轻松应对多任务处理的最强大技术,一方面能够适应用户的响应,一方面能在后台完成相应的数据处理,这是典型的多线程应用。在.NET中,基于托管环境的多个线程可以在一个或多个应用程序域中运行,而应用多个线程来处理不同的任务也造成一定的线程同步问题,同时过多的线程有时因为占用大量的处理器时间而影响性能。

推荐在多线程编程中使用线程池,.NET提供了 System.Threading.ThreadPool类来提供对线程池的封装,一个进程对应一个 ThreadPool,可以被多个 AppDomain共享,能够完成异步 I/O操作、发送工作项、处理计时器等操作,.NET内部很多异步方法都使用 ThreadPool来完成。在此做以简单的演示:

class ThreadHandle 
{ 
        public static void Main() 
        { 
                ThreadHandle th = new ThreadHandle(); 
                
                //将方法排入线程池队列执行
                ThreadPool.QueueUserWorkItem(new WaitCallback(th.MyProcOne), "线程 1"); 
                
                Thread.Sleep(1000); 

                ThreadPool.QueueUserWorkItem(new WaitCallback(th.MyProcTwo), "线程 2"); 

                //实现阻塞主线程
                Console.Read(); 
        } 

        //在不同的线程执行不同的回调操作
        public void MyProcOne(object stateInfo) 
        { 
                Console.WriteLine(stateInfo.ToString()); 
                Console.WriteLine("起床了。"); 
        } 

        public void MyProcTwo(object stateInfo) 
        { 
                Console.WriteLine(stateInfo.ToString()); 
                Console.WriteLine("刷牙了。"); 
        } 
} 

然而,多线程编程将使代码控制相对复杂化,不当的线程同步可能造成对共享资源的访问冲突等待,在实际的应用中应该引起足够的重视。

Item14:尽可能少地抛出异常,禁止将异常处理放在循环内。

异常的发生必然造成系统流程的中断,同时过多的异常处理也会对性能造成影响,应该尽量用逻辑流程控制来代替异常处理。对于例行发生的事件,可以通过编程检查方式来判断其情况,而不是一并交给异常处理,例如:

Console.WriteLine(obj == null ? String.Empty : obj.ToString()); 

不仅简洁,而且性能表现更好,优于以异常方式的处理:

try 
{ 
        Console.WriteLine(obj.ToString()); 
} 
catch (NullReferenceException ex) 
{ 
        Console.WriteLine(ex.Message); 
} 

当然,大部分情况下以异常机制来解决异常信息是值得肯定的,能够保证系统安全稳定的面对不可意料的错误问题。例如不可预计的溢出操作、索引越界、访问已关闭资源等操作,则应以异常机制来处理。

关于异常机制及其性能的讨论话题,详见 8.6节“直面异常”的分析。

Item15:捕获异常时,catch块中尽量指定具体的异常筛选器,多个 catch块应该保证异常由特殊到一般的排列顺序。

指定具体的异常,可以节约 CLR搜索异常的时间;而 CLR是按照自上而下的顺序搜索异常,因此将特定程度较高的排在前面,而将特定程度较低的排在后面,否则将导致编译错误。

Item16:struct和class的性能比较。

基于性能的考虑,在特殊情况下,以 struct来实现对轻量数据的封装是较好的选择。这是因为,struct是值类型,数据分配于线程的堆栈上,因此具有较好的性能表现。在本章中,已经对值类型对象和引用类型对象的分配进行了详细讨论,由此可以看出在线程栈上进行内存分配具有较高的执行效率。 当然,绝大部分情况下,class都具有不可代替的地位,在面向对象程序世界里更是如此。关于strcut和class的比较,详见 7.2节“后来居上:class和struct”。

Item17:以is/as模式进行类型兼容性检查。

以is和as操作符可以用于判断对象类型的兼容性,以 is来实现类型判断,以 as实现安全的类型转换,是值得推荐的方法。这样能够避免不必要的异常抛出,从而实现一种安全、灵活的转换控制。

详细的论述,请参见 7.5“恩怨情仇:is和as”。

Item18:const和static readonly的权衡。

const是编译时常量,readonly是运行时常量,所以 const高效,readonly灵活。在实际的应用中,推荐以static readonly来代替 const,以解决 const可能引起的程序集引用不一致问题,还有带来的较多灵活性控制。

关于const和readonly的讨论,详细参见7.1节“什么才是不变:const和 readonly”。

Item19:尽量避免不当的装箱和拆箱,选择合适的代替方案。

通过本节多个条款的性能讨论,我们不难发现很多情况下影响性能的正是装箱和拆箱,例如非泛型集合操作,类型转换等,因此选择合适的替代方案是很有必要的。可以使用泛型集合来代替非泛型集合,可以实现多个重载方法以接受不同类型的参数来减少装箱,可以在子类中重写ToString方法来避免装箱等等。

关于装箱和拆箱的详细讨论,参见 4.4节“皆有可能——装箱与拆箱”的深入分析。

Item20:尽量使用一维零基数组。

CLR对一维零基数组使用了特殊的 IL操作指令 newarr,在访问数组时不需要通过索引减去偏移量来完成,而且 JIT也只需执行一次范围检查,可以大大提升访问性能。在各种数组中其性能最好、访问效率最高,因此值得推荐。

相关阅读 更多 +
排行榜 更多 +
西安交大通

西安交大通

生活实用 下载
长江云通

长江云通

生活实用 下载
translatez

translatez

生活实用 下载