赏梅斋

关注微软技术

博客园 首页 新随笔 联系 订阅 管理

   随着硬件设备性能的飞快提高及软件开发工具的智能化、简易化,使得开发数据库应用程序时,一些设计人员与开发人员不再像过去那样重视应用程序的性能问题了,甚至完全不考虑。开发工具的智能化与简易化并不是没有代价的,很多开发工具把内存管理完全封装了起来,使得用户不用考虑内存是怎么分配的,然而这样也造成了很多开发人员并不了解其内在的内存管理机制,使得开发时浪费了非常多的性能而不自知。重要的是这种情况是在开发人员完全不知情情况下暗地里发生的,使得一些开发人员日久成习,完全忽略了性能方面的考虑,养成了非性能式的开发方式。

 

这种开发方式确实可以让这些开发人员把大部分的精力放在业务逻辑上,使得在应用程序的业务层可以在较短的时间里完成更加复杂的业务模型,然而随着业务组件的复杂度的不断增加,这种方式的隐患也会渐渐显露出来。具有这种隐患的应用程序好比随意快速地搭建出的积木大楼,没有较好地调整其角度使得积木块之间非常松散,随着积木大楼的高度不断增加,你会发现大楼倾斜不稳的程度也越来越大,直到最后倒塌。

 

可以说,评价一个应用程序的质量,其性能是非常重要的评价因素。我曾经参与过一个OA项目的开发,其业务模型非常复杂,功能非常的庞大,而且用户界面也非常地优秀,无论是其所使用的技术还是所实现的理念都是最先进的,看上去它分明就是一个完美的火箭项目,然而最终它却实施失败了。可能引起项目实施失败的原因会有很多种,然而我知道引起它实施失败的最大原因就是性能问题。其实开发它的人员并不是一点性能问题都没有考虑,而是考虑的不够,至使系统在中期规模时的测试显示出非常骄人的面貌,然而随着系统的不断扩大,业务功能的更加复杂,最后它却成了飞不起来的火箭了。可以想像,再复杂的火箭如果飞不起来又有什么用?也许经历过项目实施失败也是一种宝贵的经验吧,从那之后,它让我明白了在软件开发中也是存在“蝴蝶效应”的!特别是在性能问题上,有时在开发中你的一点点疏漏所造成的影响会随着项目的不断庞大而被无限地扩大!以至于最后无法补救。像就一只蝴蝶在南半球用力扇动了一下翅膀,却引起了北半球的一场风暴。

 

也许有人自认为精深应用程序的优化,于是肆无忌惮地运用非性能式的开发方式去开发一个大型项目,企图在积木搭建完成后用手把其抚直、抚正。其态度好像是在对人说,没关系!来吧,让风暴来得更猛烈些吧!似乎他真的能够阻挡这场风暴一样,然而我敢肯定,他的下场绝对不会比真的面对风暴更好。要知道,一个过于复杂的系统中,在几十万行的代码中或是在无数个逻辑模块中找寻一个性能问题无意于大海捞针。

 

而且,当一个性能问题一旦暴露出来成为影响整个积木大楼的安危时,它决对不会是一个或几个简单的代码疏忽,往往它将是一系列不同问题的集合够成的,而且深入积木大楼的内部。那时你将面对的情况是牵一发而动全身,局部的拆东墙补西墙无意于杯水车薪,如果你企图抚直积木大楼的手轻轻一抖,积木大楼就有立即倒塌的危险。

 

       我们知道,一件事情的成败往往取决于一个很小的不被人知的细节。一个项目的成败也是这样。一个项目最细节的地方就是编码,编码的不同是由编码方式决定的,而编码方式又往往是一个程序员的编程习惯所造成的。那么程序员的编码习惯就是这里决定成败的细节。

前面提到的非性能式的编程方式如果成为一个程序员的编码习惯,而他又不自知的话,那么,其中的隐患是可以想像的。下面我就举例子来说一下在性能方面常被一些程序员所忽略的细节。

 

       DotNet(C#)开发环境中,使用最多的对象莫过于String类。一些程序员经常会定义非常多的String类型的对象,然后对它们进行处理。比如string1 + string2 + ……。其实,从性能方面的考虑,他这样的做法是非常浪费性能的,同样的字符处理功能用StringBuilder类来实现就会非常多地节省性能。我这样说,可能会有人不以为然,会说:我也知道StringBuilder类,但是这么细节的地方能节省多少性能啊!好,下面我会用一段测试代码告诉你一个让你吃惊的结果。值得说明的是,这段测试代码并不是我想出来的,而是上海TechEd2004上,微软的一位开发Framework的高级工程师卢斌举的例子。

代码如下:

using System;

using System.Text;

 

class Test

{

  public static void Main(string[] argvs)

  {

    string s = “”;

    int n = 0;

 

    if(argvs.Length < 2)

    {

      PrintHelp();

      return;

    }

   

    try

    {

      s = argvs[0];

      n = int.Parse(argvs[1]);

    }

    catch

    {

      PrintHelp();

    }

 

    if(argvs.Length > 2 && argvs[2] == "/o")

      StringBuilderTest(s, n);

    else

      StringTest(s,n);

  }

 

  static void PrintHelp()

  {

    Console.WriteLine("usage:  Test <sString> <nTimes> [/o]");

  }

 

  static void StringTest(string s, int n)

  {

    DateTime beginTime = DateTime.Now;

   

    string temp = "";

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

      temp += s;

 

    DateTime endTime = DateTime.Now;

    Console.WriteLine(temp);

 

    TimeSpan ts = endTime - beginTime;

   

    Console.WriteLine(" " + ts.TotalMilliseconds + " ms");

  }

 

  static void StringBuilderTest(string s, int n)

  {

    DateTime beginTime = DateTime.Now;

   

    string temp = "";

    StringBuilder sb = new StringBuilder(temp, n*s.Length);

 

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

      sb.Append(s);

 

    temp = sb.ToString();

 

    DateTime endTime = DateTime.Now;

    Console.WriteLine(temp);

 

    TimeSpan ts = endTime - beginTime;

   

    Console.WriteLine(" " + ts.TotalMilliseconds + " ms");

  }

}

编译完成后,在命令行下分别通过输入:

 

Test Hello,China 10000

 

Test Hello,China 10000 /o

 

通过上面的代码来测试StringStringBuilder的效率,你会吃惊地发现两者的效率相差竟然是几千倍!,随着n的增加,这个差异还会增加。测试中StringTest类会占用近10M的内存并触发18次垃圾回收。而StringBuilderTest类只占用几十K内存,没有垃圾回收。也许你不知道触发18次垃圾回收意味着什么,相信你一定可以明白10M几十K的差距!

开发过程中,像StringStringBuilder这样并不太为人注意的例子只是细节之一,还有很多这样需要程序员深入了解的细节。

 

再比如使用最普通的SQL语句,也有常常不被人注意的细节。

如下:

Select * from UserTest where condition1=’conditions’ and condition2=’conditions’ and condition3=’conditions’ ……

像这样一条SQL语句,当where后的条件非常多时,很多程序员都会不假思索地快速顺序写出来,一般很少有人会有精力想到应当把经常查询的条件放在前面。然而,这是非常重要的,一个百万级的数据库其数据的读取速度也许就浪费在你的一条非常普通的查询语句里了。

 

从积木大楼的例子可以明白,企图当一个项目在开发完之后再进行优化很可能就是把自己置身于风暴中,所以我们应该把性能问题考虑在项目的设计之前。

如果说,编码造成的性能浪费好比在搭积木时没有把每一个积木块放好,那么设计时所造成的性能问题就好比在搭积木时搭建的方法不正确了。

 

 

 

posted on 2004-09-29 15:53  赏梅斋  阅读(4083)  评论(23编辑  收藏  举报