• dotnet程序优化心得


    近一段时间测试过几个程序,有一些心得。具体步骤如下:

    (1)写出测试程序
    (2)测试原程序,记录运行时间,作为优化的基础。要在Release下测试。
    (3)检查算法,是不是最有效的算法。尤其是现在内存便宜,看有没有能够用空间换取时间的方法
    (4)用Reflector查看类库,看你使用的方法是不是最有效率的方法
    (5)对于运行次数多,性能关键的地方,不要直接调用类库。类库是为通用目的设计的,用Reflector可以发现,针对特殊的问题,内库里面有很多冗余代码

    测试程序是很关键的,dotnet下用DateTime.Now.Ticks能够得到精确的时间,单位是10^-7s。

    我爱用下面这种结构的测试代码:

     1    static void Main(string[] args)
     2    {
     3long _start,_end,_loops; //DateTime.Now.Ticks是long int
     4        _loops=100000;    //重复次数
     5        _start=DateTime.Now.Ticks;
     6
     7        for (int i=0;i<_loops;i++)
     8        {
     9            原方法;
    10        }

    11        _end=DateTime.Now.Ticks;
    12
    13        Console.WriteLine("{0}次调用原方法共花费时间{1}ms",_loops,(_end-_start)/10000);
    14
    15        _start=DateTime.Now.Ticks;
    16
    17        for (int i=0;i<_loops;i++)
    18        {
    19            优化后方法;
    20        }

    21        _end=DateTime.Now.Ticks;
    22
    23        Console.WriteLine("{0}次调用优化后方法共花费时间{1}ms",_loops,(_end-_start)/10000);
    24    }

    一定要在一次运行中同时测试原方法和优化后方法,这样得到的测试结果才有比较意义。(如果你的方法非常耗用CPU,用带超线程的计算机,或者把测试进程设置成实时进程,这样得到的结果更精确。)

    windows下时间精度应该是0.001-0.01ms,运行时间在0.01ms以下的结果不可靠。.net下时间精度应该更低一点。我怀疑只有0.1ms,但没有认真测试过。

    不想让别的进程干扰,就把测试进程设置成实时进程。半年前测试一个程序,性能只到了40-60,查看进程发现cpu时间主要是GUI占用了,测试进程只占用了20%。动动鼠标,程序性能会上串下跳的。把进程优先级调到最高,测试进程cpu占用稳定在93-97%。设置为实时进程,cpu占用稳定在96-97%,这样测试的结果才有效。

    下面以实际例子具体解释相关技巧。

    下面以实际例子具体解释相关技巧。

     (1)缘起

    bfax@smth.org发了一个字符串转换程序,引起了热烈讨论。原程序如下:


    测试性能,结果为300万字/秒。性能提高了10倍。

     1
     2Function B2G(prestr As StringAs String
     3    Dim i, j As Integer
     4    Const GB_Lib = ""    //几千个字符吧,因为字符串长度限制,原程序是由GB_lib1,GB_lib2GB_lib4四个字符串构成的,为了简化问题,只用一个字符串代替。
     5    Const BIG5_Lib = ""    //与GB_Lib中简体字一一对应的繁体字
     6    
     7    For i = 1 To prestr.Length
     8        j= Instr(1, BIG5_Lib1, GetChar(prestr, i)) 
     9        If j<>0 Then prestr=prestr.Replace(GetChar(BIG5_Lib1,j),GetChar(GB_Lib1,j))
    10        j= Instr(1, BIG5_Lib2, GetChar(prestr, i)) 
    11        If j<>0 Then prestr=prestr.Replace(GetChar(BIG5_Lib2,j),GetChar(GB_Lib2,j))
    12        j= Instr(1, BIG5_Lib3, GetChar(prestr, i)) 
    13        If j<>0 Then prestr=prestr.Replace(GetChar(BIG5_Lib3,j),GetChar(GB_Lib3,j))
    14        j= Instr(1, BIG5_Lib4, GetChar(prestr, i)) 
    15        If j<>0 Then prestr=prestr.Replace(GetChar(BIG5_Lib4,j),GetChar(GB_Lib4,j))
    16    Next
    17    Return prestr
    18End Function



    (2) 分析问题

    写测试程序测试在我的1.5M迅驰本本测试,替换效率为30万字/s,该程序采用Replace,这样对每一个字符都要扫描GB_Lib字符串中的几千个字符,性能自然上不去。需要寻找更好的数据结构和算法,降低每一个字符串的操作时间。

    .net类库里有一个很好的东西可以拿来直接用:Hashtable。也就是说,把每一个简体字作为key,每一个繁体字作为value。这样处理每个字符的时候只需要看它在不在Hashtable的key里面,在的话就找出对应的value替换,否则就不做任何操作。这样做的代价是Hashtable初始化的耗时,不过初始化顶多也就一次嘛。程序如下:

     1public class ConvertDemo
     2{
     3    private static Hashtable _libTable;
     4
     5    static ConvertDemo()
     6    {
     7        InitHashTable();
     8    }

     9
    10    static string GB_lib="";
    11
    12    static string BIG5_lib="";
    13
    14    private static void InitHashTable()
    15    {
    16        _libTable = new Hashtable();
    17        PushIntoHashtable(_libTable,GB_lib,BIG5_lib);
    18    }

    19
    20    private static void PushIntoHashtable(Hashtable t, string g , string b)
    21    {
    22        for (int i=0;i<g.Length;i++)
    23        {
    24            t.Add(g[i],b[i]);
    25        }

    26    }

    27
    28    private static char ConvertChar(char input)
    29    {
    30        if (_libTable.ContainsKey(input)) return (char)_libTable[input];
    31        else return input;
    32    }

    33
    34    public static string ConvertText(string inputString)
    35    {
    36        StringBuilder sb = new StringBuilder(inputString);
    37        for (int i=0;i<inputString.Length;i++)
    38        {
    39            sb[i] = ConvertChar(inputString[i]);
    40        }

    41        return sb.ToString();
    42    }

    43}

    (3)用relector看Hashtable源代码,消除无用操作,继续优化

    还能不能继续优化呢?ConvertChar (char input)执行次数最多,是对性能最有影响的方法。用reflector反编译Hashtable的get_Item(object key)方法:

     1public virtual object get_Item(object key)
     2{
     3      uint num1;
     4      uint num2;
     5      Hashtable.bucket bucket1;
     6      if (key == null)
     7      {
     8            throw new ArgumentNullException("key", Environment.GetResourceString("ArgumentNull_Key"));
     9      }

    10      Hashtable.bucket[] bucketArray1 = this.buckets;
    11      uint num3 = this.InitHash(key, bucketArray1.Length, out num1, out num2);
    12      int num4 = 0;
    13      int num5 = (int) (num1 % bucketArray1.Length);
    14      do
    15      {
    16            bucket1 = bucketArray1[num5];
    17            if (bucket1.key == null)
    18            {
    19                  return null;
    20            }

    21            if (((bucket1.hash_coll & 0x7fffffff== num3) && this.KeyEquals(key, bucket1.key))
    22            {
    23                  return bucket1.val;
    24            }

    25            num5 = (int) ((num5 + num2) % ((ulong) bucketArray1.Length));
    26      }

    27      while ((bucket1.hash_coll < 0&& (++num4 < bucketArray1.Length));
    28      return null;
    29}


    我的天天天天天天天天天天天天天........好长呀,先不管这个。哦,方法并不抛出异常,如果key不存在就直接返回null。这样的话,采用ContainsKey(...)判断key是否存在就是多次一举了。

    把ConvertChar (char input)改为:

    1private static char ConvertChar(char input)
    2{
    3object temp = _libTable[input];
    4return temp == null?input:(char)temp;
    5}


    这样大概能节省一半的操作。

    测试结果验证了我这一想法。性能一下提高了40%,达到了500万字/s

    注:上面程序有小bug,后来发现的。

    4)继续优化――用空间换取时间

    现在对每一个字符,都要用get_Item(object key)方法过一遍,可这个乖乖方法那么长,肯定太耗时间了,能不能用更简单的手段呢?改Hashtable?哇,那代码,叫一个看不懂。仔细琢磨琢磨,有了,直接用数组!每一个汉字对应一个Int16,以该值为索引,数组中在那个位置的值为值,这样最快速了。就是占点空间,内存便宜嘛。反正这东西只初始化一次,占不了多少k。新程序如下:

     1public class QuickChineseConvert
     2{
     3    static char[] _lib;
     4    static int _size;
     5
     6    static QuickChineseConvert()
     7    {
     8        _size=UInt16.MaxValue;
     9        Init();
    10    }

    11    static string GB_lib="……";
    12    static string BIG5_lib="……";
    13
    14    private static void PushIntoArray(char[] c , string g, string b)
    15    {
    16        for (int i=0;i<g.Length;i++)
    17        {
    18            c[Convert.ToUInt16(g[i])]=b[i];
    19        }

    20    }

    21
    22    private static void Init()
    23    {
    24        _lib = new char[_size];
    25        PushIntoArray(_lib,GB_lib,BIG5_lib);
    26    }

    27
    28    public static char ToBIG5(char inputChar)
    29    {
    30        char temp = _lib[Convert.ToUInt16(inputChar)];
    31        return temp==0?inputChar:(char)temp;
    32    }

    33
    34    public static string ToBIG5(string inputString)
    35    {
    36        StringBuilder sb = new StringBuilder();
    37        for (int i=0;i<inputString.Length;i++)
    38        {
    39            sb.Append(ToBIG5(inputString[i]));
    40        }

    41        return sb.ToString();
    42    }

    43}


    这样的话,对于每一个字符,只进行几个简单的操作了:

    Convert.ToUInt16(inputChar)
    从数组中取值_lib[Convert.ToUInt16(inputChar)]
    然后就是return temp==0?inputChar:(char)temp;

    对比Hashtable臭长臭长的get_Item(object key),这下简单多了!当然,要付出一定代价,代价就是要弄一个大小为UInt16.MaxValue的数组,不大嘛。

    测试性能,很鼓舞人心:1857万字/s


    (5)Go on...........

    还能继续优化吗?数组不是还要检查边界条件吗?用指针?书上说能提高性能17%。17%少了点,不过也试一试。测试结果表明,性能没怎么提高。现在性能瓶颈在传值,return那一块,用ref,out什么的试一试?
    做了以下类似的改动,这个性能又提了到差不多10倍。

    1                public static void ToBIG5(ref char inputChar)
    2                {
    3                        char temp = LibGB2BIG5[Convert.ToUInt16(inputChar)];
    4                        if (temp!=0) inputChar=temp;
    5                }
    1                public static void ToBIG5(ref char[] inputString)
    2                {
    3                        for (int i=0;i<inputString.Length;i++)
    4                        {
    5                                inputString[i]=ToBIG5(inputString[i]);
    6                        }

    7                }


    测试结果:1.444亿字/s。10个指令周期一个字符!!应该不能再优化了吧!到此为止吧,这性能应该够用了.

    不过这个结果有点伪,因为必须传入字符或字符数组。而String是只读的,使用过程必须复制一遍,用String做参数性能只到的了8000万字/s左右

    (6)优化路径小结

                简单的方法,Replace,30万字/s 
    ------->采用更好的数据结构和算法(Hashtable),300万字/s
    ------->用reflector查看Hashtable代码,做进一步的优化,500万字/s
    ------->分析问题所在,直接采用数组,用空间换取时间,1850万字/s
    ------->采用ref,out等技巧做进一步优化,1.44亿字/s(传数组)8000万字/s(传string)

    一、使用存储过程

    1. 性能方面:存储过程提供了许多标准sql语言中所没有的高级特性。其传递参数和执行逻辑表达式的功能,有助于应用程序设计者处理复杂任务。另外,存储过程存储在本地服务器上,减少了执行该过程所需的网络传输宽带和执行时间。(存储过程已经对sql语句进行了预编译,所以其执行速度比在程序里执行sql语句快很多)
    2. 程序结构方面:从程序的可扩展性看,使用存储过程会对程序以后的修改带来方便。比如数据库的结构改变了,只需修改相对应的存储结构,和程序中的调用部分即可。

    这部分不属于本文探讨范围,属于程序结构设计方面。所以不在此展开。

       3.  程序安全性:使用存储过程可避免SQL Injection攻击。

    二、查询语句的优化(针对sql server2000

    很多人只为目的写出sql语句,而不考虑sql语句的执行效率。在这我只提供一优化表顺序的方法,(sql语句的优化和原则将会在我的sql server2000学习笔记中专题讨论)

    对sql语句执行效率可用sql server2000的查询分析器来查看语句的执行过程。

    优化表顺序:一般情况下,sqlserver 会对表的连接作出自动优化。例如:

    select name,no from A

    join B on A. id=B.id

    join C on C.id=A.id

    where name=’wang’

    尽管A表在From中先列出,然后才是B,最后才是C。但sql server可能会首先使用c表。它的选择原则是相对于该查询限制为单行或少数几行,就可以减少在其他表中查找的总数据量。绝大多数情况下,sql server 会作出最优的选择,但如果你发觉某个复杂的联结查询速度比预计的要慢,就可以使用SET FORCEPLAN语句强制sql server按照表出现顺序使用表。如上例加上:SET FORCEPLAN ON…….SET FORCEPLAN OFF  表的执行顺序将会按照你所写的顺序执行。在查询分析器中查看2种执行效率,从而选择表的连接顺序。

    *使用SET FORCEPLAN选择表联结顺序

    三、页面的优化(.aspx

    主要针对几个页面属性

    1. 1.       EnableViewState(页面的视图状态)。如果无特殊要求设置为false。

    使用ViewState ,每个对象都必须先序列化到 ViewState 中,然后再通过回传进行反序列化,因此使用 ViewState是没有代价的。尽量减少使用对象,如果可能,尽量减少放入 ViewState 中的对象的数目。下面情况基本上可以禁用viewstate:

    (1)页面控件 (.ascx)

    (2)页面不回传给自身。

    (3)无需对控件的事件处理。

    (4)控件没有动态的或数据绑定的属性值(或对于每个postpack都在代码中处理)

    单个页面或每个页面都禁用 ViewState,如下所示:

    单个页面:<%@ Page EnableViewState="False" %>

    每个页面:在 web.config 中 <Pages EnableViewState="false" />

    EnableSessionState保持默认值即可(如果页面用到sessionstate它才会占用资源)。

    EnableViewStateMac如果无安全上的特殊要求,保持默认值。

    1. 2.       Pagelayout.页面布局模型。建议使用Flowlayout(元素不带绝对定位属性添加).Gridlayout(绝对定位属性)由于采用绝对定位,将会比Flowlayout生产更多的代码,主要是控件的定位信息。

    3.项目发布的时候切记解除页面的Debug状态。

    4.Html语言的优化。我的建议是熟练掌握Html/JavaScript,少用vs.net2003自动生产的代码,它会自动生成一些无用的html代码。

    5. smart navigation设置为true能让用户明显的感觉性能提高。启用此属性后对客户端和服务端影响不大.它能智能涮新需要涮新需涮新的部分.

    四、控件的选择

    Html控件和服务器控件的选择。服务器控件带来的方便和功能上的实现是html控件所不能比拟的。但是是以牺牲服务器端的资源来取得的。我个人建议:如果html控件达不到所要实现的功能,而且和一些脚本语言(如javascrpt/vbscript)结合也不能实现的话。才会选择服务器控件。选择服务器控件后,也尽量对其控件优化,如取消一些页面状态等(具体看控件的优化)

    服务器控件的选择:主要针对几个常用数据控件说明一下:

          DataGrid:自带最强大的数据显示控件,内置了对数据的修改、删除、添加、分页等很多实用功能。如果你只需对数据显示的话,尽量不要选择DataGrid(它把数据都存储在viewstate中).也不要使用自带的分页功能,microsoft在自动分页的底层做了很多工作,虽然使用方便了,但性能开销大了。(推荐一分页控件:http://webdiyer.europe.webmatrixhosting.net/default.aspx

              DataList:比DataGrid功能少了很多。但自定义性强了很多。特有的多行数据显示,给我们带来了很多方便。DataGrid能实现的功能,它基本能实现。所以建议使用它。

              Repeater:功能最少,但自定义性非常强。如果只需对数据显示,建议使用。由于减少了很多功能,对服务器的性能带来消耗最小。因此,如果是对数据显示的话,我基本上都是选择Repeater然后DataList最后DataGrid

    *尽量选择html控件。能在客户端实现的功能就在客户端实现(熟练掌握javascript),减少服务器的压力。数据控件选择顺序:Repeater、DataList、DataGrid

    五、服务器控件的优化

    1.  Viewstate

    控件的viewstate与页面的viewstate基本是一致的。用来保存控件的一些状态。

    处理原则和处理页面的viewstate一样。有兴趣的可以用Datagrid绑定数据测试下

    viewstate保存的数据量有多大,它所保存的数据基本和Datagrid显示的数据量大小

    是等同的。

    2.  Ispostpack

     默认false.需要产生事件的时候才需设置为true.

    控件的优化,主要看你对此控件的熟悉情况。对控件内部运作的原理越了解,就会对其作出合适的优化。

     

    性能优化是三两句话说不清的,我所写出的仅仅是冰山一角,性能的优化是靠平时经验的积累和对程序的运作原理的不断认知。

    如有错误,请提出.由于有些不可抗拒因素.此文,先写到此.有时间再继续补充.

  • 相关阅读:
    Java异常简介
    mysql索引
    关于this问题
    Centos 7 安装jdk1.7
    Java 将html导出word格式
    tomcat生成ssl证书
    数据库连接池的作用
    jquery的异步获取返回值为中文时乱码解决方法
    JQuery中ajax跨域问题
    JAVA 非对称加密算法RSA
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/1805073.html
Copyright © 2020-2023  润新知