赏梅斋

关注微软技术

博客园 首页 新随笔 联系 订阅 管理
  72 Posts :: 2 Stories :: 151 Comments :: 26 Trackbacks

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

 

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

 

可以说,评价一个应用程序的质量,其性能是非常重要的评价因素。我曾经参与过一个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 赏梅斋 阅读(3471) 评论(21)  编辑 收藏 网摘 所属分类: 软件开发

Feedback

#1楼  2004-09-29 16:06 灵感之源      
或许第一点还有部分人有所认识和注意,第二条估计很少很少。
  回复  引用  查看    

#2楼  2004-09-29 16:19 rIPPER [未注册用户]
我对数据库底层不是很熟悉,常见的数据库系统对这个查询不能自动优化吗?
  回复  引用    

#3楼  2004-09-29 16:39 runmin      
第一个例子好像被举过了

if(argvs.Length > 2 && argvs[2] == "/o")
StringBuilderTest(s, n);
else
StringTest(s,n);

看到了这么一句,好像

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

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

这两种写法上的效率也是不一样的,不知道记错了没有。

  回复  引用  查看    

#4楼  2004-09-29 16:44 runmin      
呵呵,看来我这个写错了,如果不大于2根本不可能有2,但是,我的意思我想能明白吧?
  回复  引用  查看    

#5楼  2004-09-29 17:11 红移      
非常同意搂主的观点。
平台和工具开发商努力地把程序员从繁重的“体力活”中解脱出来,这是没错,但是我们不能应为这样就片面地认为从此可以不管性能和损耗了。
“让代码的每一个符号里都闪耀着智慧的火花,每一行都体现对卓越的追求”,我想这应该是优秀程序员所追求的目标吧。
  回复  引用  查看    

#6楼  2004-09-29 22:04 kwklover      
性能问题确实是需要关注的
但是我细微之处显示出来的,就拿楼主的例子来说
string和stringbuilder的性能差异是明显的,特别是构造复杂和很多字符连接的情况下,而且很多朋友都知道这么回事
但关键在于,我们大多数时候,处理字符都是几个字符变量,那么在这种情况,使用string和stringbuilder在性能有多大差异,比如
string s1 = "name=" ;
stinrg s2 = "kwklover" ;
和使用
StringBuilder sb = new StringBuilder("name=") ;
sb.Append("kwklover") ;
那么在这种情况下,性能蜀优蜀劣呢?

所以很多情况下,程序的性能不是一两句的事情,一个项目的性能和项目的开发团队的每一个成员的经验和知识丰度有很大关系

楼主的“应当把经常查询的条件放在前面”就很有实用了,不知道那位有经验的朋友能把自己的心得和小细节可以提高性能的写成文章?

楼主的愿望是好的,把事情放大来举例的方式也让人能更深刻体会区别
但并不希望大凡将性能优化必那string和stringbuilder作举例,能不能把实例将的宽一点,也许这样更有说服力

一点感想,话语有不妥之处,请楼主海量

  回复  引用  查看    

#7楼  2004-09-30 06:07 FantasySoft      
To kwklover:我来说一个性能优化的例子吧,就是创建ArrayList实例的时候,应该尽量使用ArrayList(Int32)的构造函数。Int32定义的是ArrayList的初始化容量(capacity)。如果你根据实际的情况大概估算出ArrayList需要的容量,那么就会减少因为容量不够而重新分配内存的次数了。
  回复  引用  查看    

#8楼  2004-09-30 08:55 赏梅斋 [未注册用户]
1 灵感之源 : 感谢灵感之源的关注!
2 rIPPER : 我做过测试确会有影响,特别是在SQL语句非常多的应用系统中.当然可能不同的数据库系统影响程度会有所区别.
3 kwklover :
我觉得你这句话说的很对"一个项目的性能和项目的开发团队的每一个成员的经验和知识丰度有很大关系",我写这篇文章的目的也是想说程序员的功力往往体现在对性能细节的高度关注之中.即兴写的,可能举例子的地方描述不太精确,还请见谅.
4 FantasySoft : 强烈支持FantasySoft!
相信很多功力深厚的程序员都有非常多的宝贵经验,希望能够让大家都能来分享一下!
我相信,细节决定成败,而很多细节都是苦练内功所得,如果可以集百家内功心法于一处,决对是旷世秘籍!
  回复  引用    

#9楼  2004-09-30 08:59 赏梅斋 [未注册用户]
5 runmin : 我明白你的意思,感谢你的提醒.

6 红移 :“让代码的每一个符号里都闪耀着智慧的火花,每一行都体现对卓越的追求”,强烈支持!


  回复  引用    

#10楼  2004-09-30 09:03 赏梅斋 [未注册用户]
在framework中,大于80K的对象会被认为大对象,而直接被放入一个特殊区域"大对象堆",这样的对象会直至GC的第二代时才会被回收.将会是完全回收.
  回复  引用    

#11楼  2004-09-30 09:33 age0 [未注册用户]
第一个问题基本上不用考虑,当然前提是你要使用分布式设计,只要把计算分布到客户机上,这些性能损耗完全可以忽略不计。

不过用web方式来开发的话是肯定没办法进行分布式设计的,还是用smart client吧。
  回复  引用    

#12楼  2004-09-30 11:41 赏梅斋 [未注册用户]
age0 : 我们追求的是尽量关注性能,尽量在编码或设计的细节上形成好的习惯,并不是想办法用什么手段去抵消性能上的浪费.
  回复  引用    

#13楼  2004-09-30 17:52 age0 [未注册用户]
我只是想提醒你们,不要对PC机性能飞跃式的发展视而不见,web系统集中式的处理完全忽略了客户机的性能,所有的计算都集中在web服务器上所以迫使你不得不去考虑与性能相关的细枝末节。
  回复  引用    

#14楼  2004-10-01 23:08 赏梅斋 [未注册用户]
age0 : 你说的有道理,其实我也觉得PC机的性能越来越高,Web系统集中式的处理确实没有很好地利用客户机的性能,所以现在也有很多人在利用客户端的脚本来完成一些原来是在服务器端进行的工作。不过我想Web系统之所以一直大行其道,还是有它不可替代的优势的,比如部署简单,可维护性强,从简单到复杂都可以快速地进行搭建,所以不可能一下子就被“智能客户端”等重量级的系统所代替。正像你说的,我在这篇文章里写的确是“与性能相关的细枝末节”,还如你所说,“所有的计算都在Web服务器上迫使我们不得不去考虑与性能相关的细枝末节”,而且,更重要的是,即使利用客户端PC的高性能,我们仍然不能不去考虑这些细枝末节,因为软件系统的复杂度与规模同样如硬件一样在飞跃 式地提高,我们不能指望所有的客户都能够买得起价钱那么高的硬件设备。同时,一个项目的软件开发虽然没有必要一定把软件的性能达到某某个极至,但就程序员来说,软件开发本身对性能的考虑也应是关键性的。
  回复  引用    

#15楼  2004-10-24 14:44 SUN      
呵呵,有同感,我也举个例子:

很多程序员在动态绑定treeview的时候,采用的一层一层的建造树结点,也就是:取出第一级节点,然后再迭代第一级节点集,访问数据库,在节点比较小的情况下,和数据库交互还比较小,但是如果在系统使用的过程,总是会有用户添加节点,可能会添加n层n个节点,哈哈,
如果有用户打开treeview的页面,整个系统就这样个over了
  回复  引用  查看    

#16楼  2004-10-25 11:15 赏梅斋 [未注册用户]
To SUN : 非常好的例子,我们公司最近的项目中也碰到了同样的问题!
  回复  引用    

#17楼  2005-03-24 16:16 some [未注册用户]
像这样一条SQL语句,当where后的条件非常多时,很多程序员都会不假思索地快速顺序写出来,一般很少有人会有精力想到应当把经常查询的条件放在前面。

---------------------------
为什么是把经常查询的条件放在前面?
经常查询是什么概念?

我SQL不熟悉,但是按照我对C的理解,应该是把最小概率匹配的条件放在前面。

谁能解释一下?


  回复  引用    

#18楼 [楼主] 2005-03-25 08:57 赏梅斋      
一般数据库会从左到右地去查看所有的条件的,所以应该把最常用的查询条件放在最左侧,这样会使查询效果变快,当然,如果数据量不大是看不出来的。

  回复  引用  查看    

#19楼  2005-11-03 10:24 work [未注册用户]
呵呵,这么多人说,我也来说两句,性能问题的确非常重要,楼主说的也很好,不过我有点想法:
1,性能的决定,第一是在于系统分析师的构架的方案选择,其次才是程序员的代码功力.不同的软件设计结构和构架,性能级别上就已经划分了.比如在设计上是不是用分布,是不是用数据库连接池
2,不要多次连接数据库,每次连接的IO消耗是很惊人的,即使是个常连接也一样
3,"select * form Tablea "这样的语句尽量不要用* 而是用列举的字段值
4,如果不是数据库的一些限制,大数据库返回的时候,都要使用在数据库那分页.
5,使用做连接的时候一定要注意,联立表的时候,哪个做驱动表,都需要考虑一下再确定,不要动不动就轻易的把n个表联立查询
6,开发初期就要用模拟出来比以后正常运行级别稍高的数据量,不要开发的时候,只是用少量数据样本,这也是开发的时候程序员不考虑性能的原因,因为那个时候他怎么写,都看不出来性能的变化,往往不能发现问题在初期
总之,我的感觉是,性能问题,的确是个很重要的问题,因为我就被原有一个大系统的性能优化所累,做了很多工作来改善性能,而在改善中,感觉,数据库的选择上面也很重要,要不然有一些数据库在你想分页的时候,很难实现.总之,高手是应该治理在初期的,等到病入膏肓,系统上了1年多出现性能问题,优化就显得不那么能放开手脚了.
  回复  引用    

#20楼 [楼主] 2006-01-05 11:40 赏梅斋      
To:work
  对于性能问题其实是在系统设计时应该解决的问题,现在硬件设备越来越好,很多开发人员不再重视性能问题,一味的考虑功能,往往性能出现问题的系统大都是由于系统设计时对算法的合理性考虑不够.
  回复  引用  查看    

#21楼  2006-04-12 02:38 释然 [未注册用户]
一般的 DBMS都有自己的查询优化,即使你把主键或者聚焦索引的查询放在最后面,但是在执行的时候查询优化器会帮你执行,这个担心有点多余了
  回复  引用    





标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2005-10-18 10:39 编辑过
Google站内搜索

China-pub 计算机图书网上专卖店!6.5万品种 2-8折!
近千种 9-95 新二手计算图书火热销售中!
开发者征途系统新作:《设计模式——基于C#的工程化实现及扩展》

相关文章:

相关链接: