个人ASP.NET程序性能优化心得(2):ASP.NET代码优化

个人ASP.NET程序性能优化心得系列:

个人ASP.NET程序性能优化心得(1):数据库篇

个人ASP.NET程序性能优化心得(1):数据库篇(外一篇)

个人ASP.NET程序性能优化心得(2):ASP.NET代码优化

个人ASP.NET程序性能优化心得(3):前端性能优化

------------------------------------------------------------------------------

对于影响ASP.NET程序性能的因素,重要性并非按数据库、ASP.NET代码、前端这样来排序,不同的应用环境可能重要的影响因素都不一样,除去外在硬件、网络等因素,用户在地址栏里输入地址,到整个网页完整显示在用户的浏览器中,这个时间等于所有因素之和。一般情况来讲,在正常的情况下,处理这三种因素都需要不同的角色:DBA负责数据库优化,程序员优化ASP.NET代码、前端工程师优化前端性能,如果分工没有这么明细,就会出现分工出现交叉的情况;最坏的情况是,只有一个人,这个人既是DBA,又是程序员,还要书写全部前端部分。——这可能是件坏事,你在一个抠门的老板手里,老板把你当驴子来用;也会是一件好事,Web开发本身就是一件需要掌握众多知识的工作,你会从中学习到更多的知识,这样对理解网络程序会有更全面的认识(偶就是这么过来的…………TAT)。

一、慎用服务器控件

ASP.NET中的服务器控件一部分是对html控件的扩展,包括Button、Label、Literal、TextBox、DropDownList、CheckBox、RadioButton等,ASP.NET对这些控件进行了封装,同时对控件进行了委托事件的绑定,这样使得ASP.NET程序员就可以像VB、WinFornm那样拖控件的方式来去对界面进行布局和事件的处理。这类控件生成的HTML控件基本不会生成多余的代码,如果对Web标准不是很在意的话,影响不会很大。值得一提的是Label生成的是<span>标签,CheckBox(List)、RadioButton(List)生成的标签往往是这样的:

<span class="checkBox"><input id="chkSel" type="checkbox" name="chkSel"  /><label for="chkSel">选择</label></span>

这种代码从Web标准上来看是要被前端工程师骂的,既无语义性又浪费代码。

另一类服务器控件就是微软引以为傲的大数据展示控件,包括GridView、DataList、DetailsView、ListView、Repeater等,这类控件除了Repeater控件外,其他不推荐使用,这类傻瓜控件会把程序员变得更加傻瓜,更重要的是会影响代码性能,以GridView为例,在绑定DataSource后,所有的数据都会在服务器的内存上一次加载,程序员最爱它的分页功能——瞧,点几下就可以了。但如果数据比较多,比如几百页、几千页,点一次等待的时间可以上一次厕所。Repeater控件推荐使用,因为它不会生成任何代码,仅是将数据源的内容重复显示出来。对于最最常用的分页功能建议是存储过程+Repeater,这样无论是从性能还是从HTML代码整洁度来看都是合适的。

HTML算是Web开发者最最基本的能力,如果不能手写HTML很难胜任Web开发这项工作,使用Visual Studio拖控件得来的代码是非常混乱的,建议还是打开源视图一行一行地写HTML,即使它是服务器控件。

二、ViewState的问题

ViewState本身是一个非常好的想法,微软的创新能力在业界是公认的,起因是这样的:用户向服务器提交数据,比如姓名、年龄、性别等,服务器会对这些数据的合法性进行验证,如果格式不正确或者输入为空这样服务器就会将一些错误信息返回给用户,但问题是用户刚刚提交的表单内容都已经没有了,如果表单中要填写的内容比较多,用户会发疯的。这时ASP.NET就非常好心地把这些提交的内容都还存储在这个网页里,即使数据提交未通过验证,这些数据依然保持在它本来的位置上 ,这个做法非常的讨好用户和开发者,对了,它往往和验证控件一起使用,看似非常人性化,但它却是会生成大量臃肿代码的罪魁祸首。

个人ASP.NET程序性能优化心得(2):asp.net代码优化

这种乱七八糟的代码是对已有的数据进行Base64加密后产生的结果,它会让用户的浏览器在下载你的网页时感觉慢了一些。

我的观点是,如果是互联网类产品(包含网站类)要全局把EnableViewState设为false,可以在Web.Config里设置的。解决方案是使用Javascript进行验证,现在的表单验证库使用起来是很方便的。

三、ADO.NET中的问题

 1、SqlDataReader和DataSet的选择:

SqlDataReader是一种快速向前读取数据的类,它对于从Sql Server数据源检索的数据采用只进数据流的方式,DataSet会将数据库的内容以逻辑数据库的方式存放在服务器的内存中,如果数据量比较大,对服务器的性能影响就比较大了。因此在实际项目中应该合理选择。

2、ExecuteNonQuery和ExecuteScalar的选择:

ExecuteScalar会返回执行结果后的第一行第一列数据,ExecuteNonQuery只会返回影响的行数,在一些情况下我们可能只需要知道是否已经执行成功了,也就是ExecuteNonQuery返回的行数是否大于0;有的时候我们还想知道返回结果的数据,比如在执行INSERT后我们需要使用SCOPE_IDENTITY()来得到返回的主键ID。可以根据实际需要选择使用ExecuteNonQuery和ExecuteScalar。

四、合理使用缓存

使用缓存算是属于重中之重,这个可以这么来解释:当有一百个人都需要到服务器取一个数据,那么程序每次都会到SQL Server数据库中查询取出数据,这样将对性能造成很大的影响。可以将这个数据暂时缓存到服务器上,这样第一个人查询时会将这个数据存到服务器的内存上,其余的用户在缓存时间内都会直接从服务器上取出这个数据,避免多次请求数据源,这样就会提升程序的性能。当然,如果是这个缓存数据是动态变化的话,可以适当的设置缓存时间,或者在更新数据时清除缓存,这种情况比如在文章系统中会遇到。

我这里提供了我个人使用的缓存类:

using System;
using System.Collections;
using System.Web;
using System.Web.Caching;

namespace Cute.Utility
{
    /// <summary>
    /// @ 缓存处理
    /// @ walkingp
    /// @ http://www.cnblogs.com/walkingp
    /// @ http://www.walkingp.com/
    /// @ walkingp@126.com
    /// @2011-05-01
    /// </summary>
    public class CacheHelper
    {
        private Cache webCache = HttpContext.Current.Cache;
        private int defaultCacheTime = 60;
        /// <summary>
        /// 获取某项
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public object Get(string key)
        {
            return this.webCache.Get(key);
        }
        /// <summary>
        /// 判断某项是否存在
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public bool Exists(string key)
        {
            return this.webCache.Get(key) != null;
        }
        /// <summary>
        /// 增加某项到缓存中
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public bool Add(string key, object value)
        {
            return this.Add(key, value, defaultCacheTime);
        }
        /// <summary>
        /// 增加某项到缓存中设置缓存时间为minute分钟
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="minute">要缓存的分钟数</param>
        /// <returns></returns>
        public bool Add(string key, object value, int minute)
        {
            return this.Add(key, value, TimeSpan.FromMinutes(minute));
        }
        /// <summary>
        /// 增加某项到缓存中,并设置某项的缓存时间
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="span">要缓存的时间片</param>
        /// <returns></returns>
        public bool Add(string key, object value, TimeSpan span)
        {
            this.webCache.Insert(key, value, null, DateTime.Now.Add(span), System.Web.Caching.Cache.NoSlidingExpiration);
            return true;
        }
        /// <summary>
        /// 增加某项到缓存中,并设置要过期的缓存时间
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="expireTime">缓存的过期时间</param>
        /// <returns></returns>
        public bool Add(string key, object value, DateTime expireTime)
        {
            this.webCache.Insert(key, value, null, expireTime, System.Web.Caching.Cache.NoSlidingExpiration);
            return true;
        }
        /// <summary>
        /// 把某项从缓存中移除
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public bool Remove(string key)
        {
            return this.webCache.Remove(key) != null;
        }
        /// <summary>
        /// 移除所有缓存
        /// </summary>
        public void RemoveAll()
        {
            System.Web.Caching.Cache _cache = HttpRuntime.Cache;
            IDictionaryEnumerator CacheEnum = _cache.GetEnumerator();
            ArrayList al = new ArrayList();
            while (CacheEnum.MoveNext()) {
                al.Add(CacheEnum.Key);
            }
            foreach (string key in al) {
                _cache.Remove(key);
            } 
        }
    }
}

五、其他优化

1、避免拆箱装箱:

我们知道C#中的值类型和引用类型,C#基础数据类型和Struct、Enum属于值类型,引用类型包括类、数组、接口、委托以及字符串(String),不错,字符串也属于引用类型;值类型会以栈(Stack)的形式存储在内存,引用类型会以堆(Heap)的形式存储(这个概念也就是C语言的指针)。值类型转换为引用类型称为装箱,反之称为拆箱。如下:

int i=0;
Syste.Object obj=i;//装箱
int j=(int)obj;//拆箱

装箱拆箱会影响程序性能,我们在程序中应该尽可能避免。因此程序这样写会提升那么一点点的性能:

String info="My Age is" + 24.ToString();

2、使用StringBuilder

如果字符串拼接过多,可能会影响程序性能,这样因为每次++都会创新一个新的字符串,也就是说会在内存中多开辟了一个空间给这个新的字符串。使用StringBuilder可以减少这种消耗。建议在连接字符串过多的情况下使用。

另外记得关于StringBuiler的问题曾经在博客园争吵过一次,如果有问题欢迎大家指出来。

3、避免不必要的ToLower()和ToUpper()

同上一样,都会创建新的字符串对象,如果没有必要尽量减少使用,也可以使用Compare方法来比较字符串。

4、避免不必要的异常捕获

也许你对事物的考虑过于复杂,对于异常的捕获也过于敏感,于是Try...Catch就用得多一点,那么建议还是在该用的时候才用,不该用的时候不用。

5、使用存储过程

频繁的SQL语句在Web服务器传递到数据库服务器上来回传输会影响程序执行的效率,更为有效的方式就是把语句比较复杂和需要比较频繁使用的SQL语句封装成存储过程进行调用。

总结

以上是我个人总结的ASP.NET代码优化的一点方法和心得,这个层面的优化需要更多的是对C#语言、ADO.NET机制和ASP.NET运行机制掌握得更多和更加深入。优化无止境,欢迎完善和补充。

推荐书籍

CLA Via C#》:这本书对c#进行了最深入的讲解。

ASP.NET本质论》:园子里的朋友写的一本书,上次跟dudu借看了(偶经常能看到dudu,羡慕不?),强烈推荐,谁说国内没有好的IT图书?

C#图解教程》:老外也玩标题党,差点毁了一本好书,对于感觉自己对C#似懂非懂的同学一定要去买下这本书去认真读一下,看的时候会有一种“so simple”的感觉。

-------------------------------------------------------

本文同时发表在我的个人主页上:http://www.walkingp.com/

posted @ 2011-06-10 00:47  walkingp  阅读(5113)  评论(13编辑  收藏