2008年4月21日星期一

Lenovo/IBM ThinkPad Seveice Centre应该算是最垃圾的计算机服务中心吧?

别以为冠名有IBM就是有着先进服务意识的团队了,对于它们的小黑,不提钱,硬件上我还是很支持的,毕竟贵了那么多,要做的太差也早就退出历史舞台了,但是软件呢?我也是依照品牌机的使用惯例尽量保留原厂“效果”的,IBM/Lenovo推荐的那些服务我都也开着,不要说系统占用是否大,我都不考虑这些因素了,但是它们是否真的有用,我就很是怀疑了。算是我的无知,对这一点我也不提。

但是能把支持中心的网站做成那样,真让人心寒。先是整合有点问题,一会跳到联想,一会儿跳到IBM,连界面风格都不太一致,关键是两边的账户还不能共享。我已经注册了IBM产品了,在我填写各种信息的时候还是要求我一次又一次地输入,真是心寒,输入也就算了。机器背后的服务编号和机器型号都是有带“-”符号的,但是最后要求的主机编号(这两个编号的组合)却不需要这些连字符,我算是试验成功了,但是要是换成别人,不知道运气有没有这么好。这也是简单的问题了,我就开始想办法“联系Lenovo”了。我先是看到一个智能什么的,我辛辛苦苦回答完问题,发现要在右侧输入编号,输入就输入吧,结果告诉我只支持联想产品线,对不起哦,我买的ThinkPad的IBM都被他们改成Lenovo了,现在跟我说不是联想系列吗?真是好处都让他一个人占尽了。好吧,我忍,我换“网上报修”,跟我说“正在建设中……”,建设中你就不要弄出来误导消费者了嘛。搞个Flash在那里那么漂亮,东西都是虚的……邮件支持的Link也是没办法重定向的。只能跑到“联系Lenovo”的链接里面去找“在线保修”,不知道和那个“网上报修”是什么关系,总之这个是可以的。又要填写主机序列号,惨了我了……

好吧,进去洋洋洒洒写下了一篇长篇问题描述,估计联想自己的报修记录都没我描述地详细吧,结果点击提交,跟我说“保存失败”,但是没有任何提示啊,我怎么知道什么问题啊,我按习惯觉得应该是文字太长了,可是我的问题就是如此长啊,只好截断一半,然后补充一句另外提供联系方式联系吧。我真的没辙了。这下文字短了,该可以了吧?点击提交,天哪,进度条是好像在提交数据了,但是结果是我的网页连个“Succeed”都没告诉我,反正进度条是走了一遍,除了没有之前的保存失败之外,就跟没有变化一样。至今还纳闷是不是申报成功了……

联想啊,你就这样走向全世界吗?真让我伤心……不知道整这么个慢悠悠的IBM是干嘛使的,浪费心情,还不如DELL,又便宜售后又还不错……关键是人家不会把网站做成那个水平啊……

2008年4月20日星期日

如何设计高性能的ASP.NET应用程序

一、问题
    1、资源,增加内存、CPU、服务器来解决性能问题。
    2、额外的配置,这样的额外配置通常是临时的,例如在长循环中对string做+=操作时候分配更多的内存等。   
    3、错误地分配稀缺资源,如数据库connection资源未调用Close()方法释放资源导致connection耗尽。
    4、阻塞操作,比如下载的时候单一线程被占用而无法做其他事,导致了长时间线程占用,或执行一个耗时长的存储过程的时候远程对象将被阻塞。
    5、滥用线程,错误地使用单线程模型STA COM对象将导致多个请求被放入一个队列中顺序执行从而导致性能问题。
    6、延迟绑定(late-bound)调用,(在运行时载入指定的代码进行运行)当目标代码为托管/非托管的时候最好避免这样的额外操作。
    7、滥用COM互操作,虽然COM互操作很有用但是它仍然有很多影响性能的因素。包括当你跨越托管/非托管边界传递它的参数,参数的尺寸和类型都是瓶颈,由它们所导致的线程切换的代价是巨大的。
    8、大页面,页面的尺寸通常由控件的数量和类型来决定,同时也可能由于数据和图片的数量决定。大量的数据穿越网络将带来巨大的花销,高额的带宽占用也可能是一个性能瓶颈。
    9、错误地使用数据缓存,缓存静态数据或者很少使用的数据都是没意义的,缓存近乎所有的数据将导致缓存频繁更新,这方面的开销且不说,甚至命中率都会严重下降。缓存应该用来缓存高频请求的数据。
    10、错误地使用输出缓存,错误地或者不使用输出缓存都将导致Web服务器的性能瓶颈。
    11、低效率的渲染(rendering),散布不必要的postback数据或者延迟绑定数据将导致页面的真实性能下降。

二、考虑以下的设计原则:
    1、考虑安全与性能之间的权衡,比如a.若使用SQLServer的话则可以利用其连接池的可扩展性为应用程序带来丰富的可扩展性;b.若只是不想让Hacker通过Network篡改数据,可以用Hash算法计算一个表示当前数据的唯一Hash值,在信息达到后进行核对,而不需要将所有的数据进行加密,加密要能损耗是应该引起注意的。
    2、逻辑上分割Application,对应用程序合理地分层也可以带来更好的扩展性,同时也可能提高性能。层与层之间的距离也是值得关注的问题,比如最快的调用是进程内调用,接下来是同主机跨进程调用,接下来是远程网络调用。尽量将层与层之间的距离缩至最短。微软的建议是将数据库数据访问逻辑都放在WebServer的bin目录下。
    3、评估Affinity(关联度?),包括线程内会话状态和指定计算机加密锁。
    4、减少循环,
        a.HttpResponse.IsClientConnected,获取一个值,通过该值指示客户端是否仍连接在服务器上。(比如用户点击一个操作可能要消耗服务端3秒的时间,但是用户等不了这么久,就停止了网页或者直接跳到别的网页去了,这时这个属性的值将是false但是若不进行任何操作的话,长达3秒的操作仍将继续。)但是调用这个属性可能会消耗更多的资源,你必须自己权衡是否使用它。
        b.缓存,如果你的应用程序近乎静态或者你的数据也都是静态的,则使用缓存来避免频繁向数据库读取重复的数据,以提高程序性能。
        c.输出缓冲,可以利用类似HttpResponse.Flush()的方法来将缓存强制输出给用户,这样可以避免用户等到全部处理完才看到内容。注意,如果关闭缓冲,并且客户端连接网络缓慢时,会影响服务器的响应时间。(因为你的服务器必须等待客户端确认,在客户端接到服务器端发送的所有内容后会发出确认)。
        d.Server.Transfer,如果可能的话使用Server.Transfer取代Response.Redirect。Response.Redirect发送一个response头给客户端,让客户端发送一个新的请求给服务端进行重定向。Server.Transfer直接在服务端转向。因为Server.Transfer在请求处理阶段时用了新的Handler。如果你必须在重定向时验证和授权检查,那么你就应该用Response.Redirect。Server.Transfer要先把当前页面POST过来的数据保存住,再去执行新的页面,有时候速度会很慢。另外Server.Transfer可以用于控制在同一个应用程序中的页面,若是跨应用程序重定向,就应该用Response.Redirect。
    5、避免一个需要较长时间运行的任务阻塞,如果执行长时间操作或者阻塞操作的时候,可以考虑以下几种异步方式让Web Server处理其他请求。
        a.异步调用WebServices或者远程对象,当调用的时候可以并行地处理一些其他的事务。要避免同步调用WebService,处理请求的线程来自ASP.NET线程池,如果线程都去处理WebService的调用,将势必减少用来处理新请求的线程数量。
        b.如果你不需要WebService或者远程对象回复,最好将它们的调用接口设置为OneWay(就是有去无回的那种调用,发出命令,却不关心命令的执行结果)。
        c.排队工作,然后由客户端投票决定完成。允许WebServer调用代码,然后让WebClient投票让服务器确认工作完成情况。
    6、使用缓存,缓存策略可能是提高性能的至关重要的一个设计点。ASP.NET缓存机制包括输出缓存,部分页缓存,以及缓存API,利用这些特征来设计你的应用程序。缓存主要是用来减少数据访问和渲染的次数,因为很多这样的操作是完全重复的。
        a.标识数据和输出的创建和找回是代价高昂的。
        b.评估数据的缓存价值。缓存是有限而宝贵的,应该用来缓存不易改变且又经常访问的数据,对那些经常变化的数据,就没有缓存的必要了。
        c.评估数据的使用频度。对于那些经常使用而不经常更新的数据有缓存的价值。
        d.将稳定的数据和不稳定的数据区别开来。设计用户控件来封装静态内容例如导航栏或者帮助系统,并将它们与易变数据分开。
        e.选择正确的缓存机制。用户信息通常放在Session对象中,静态页或者一些类型的动态页比如那些供很多人查阅的公共资料可以用输出缓存和响应缓存。一个页面内的静态内容也可以用用户控件缓存。ASP.NET缓存特征提供一种内建的机制来更新缓存。应用程序状态,会话状态和其他缓存都没有提供内建的更新缓存机制。
    7、避免不必要的异常,如果可以的话不要使用异常来控制判断(比如取代if判断)。设计异常的时候考虑以下一些建议。
        a.设计避免异常的代码。
        b.避免在逻辑判断流中使用异常,如用它来取代if判断等。
        c.避免依靠全局的处理器(handler)处理所有的异常。处理异常是一种高昂的花销,它会导致运行时操作和进入堆栈。然后在堆中找到异常处理者。
        d.在最接近异常发生的地方缓存和处理异常。它避免了更多的堆栈查找和操作。
        e.不要捕获你无法处理的异常。如果你无法即时处理你的异常,可以使用try/finally块来关闭那些资源,比如数据库连接什么的。让异常传播到那些能够处理它的地方去。
        f.尽早发现错误,避免避免在长时间操作后才报错。
        g.将异常记入日志文件中。有利于管理者或者开发者能够亡羊补牢一下爱避免向用户展示过多的异常信息。首先是用户可能不喜欢看到这些错误的信息,避免用户的信任度下降。其次也是最重要的是避免那些钻空子的人利用这些不为人知的异常信息找到系统漏洞进行恶意破坏。

三、考虑以下的实现原则
    ASP.NET应用程序的性能考察点通常是响应时间、吞吐量以及资源管理。
    1、通过减少页面尺寸、减少服务器控件的使用、利用缓冲来减少与客户端的纠缠不清的交互可以避免被这些不可见的资源浪费你的工作。
    2、通过有效地使用线程可以增加吞吐量。用线程池来减少连接,避免因为线程池阻塞而导致的可用工作线程减少,而带来吞吐能力的下降。
    3、落后的资源管理常在服务器的CPU和内存中发生额外的载入。通过改进资源的利用有效地将资源集合起来,显式地关闭或释放打开的资源,并有效地管理字符串。

建议一:如果下面的几条成立的话,则按接下来的表的设置(可以将其设置到machine.config中):
    a.有一个可用的CPU,通常指一个多核的CPU或者有多颗CPU的Server;
    b.正在读取I/O操作,如调用Web方法或访问磁盘文件。
    c.队列中ASP.NET应用程序/请求性能计数器指示你已经有队列的请求了。
表:
Best Practice For Setting
    a.maxIoThreads和maxWorkerThreads都的实际数量都会是推荐值×CPU的个数。
    b.minFreeThreads:当当前请求的线程数小于这个值的时候,它将有效地限制请求的数量并且只能有maxWorkerThreads*number of CPUs-minFreeThreads个请求同时进行。它限制了同时请求的数量为(maxWorkerThreads*number of CPUs-minFreeThreads),在推荐表中,这个值则为12。也就是说,请求数量大于12的时候将会有请求被拒绝(RejectRequestNow),而请求小于12,并且队列中没有请求的时候将正常处理。
    c.minLocalRequestFreeThreads:这个选项与minFreeThreads具备类似的特性,它处理来自本地的队列的请求。值得强调的是它只处理本地的(localhost)请求。

相关参考:
1、《Contention, poor performance, and deadlocks when you make Web service requests from ASP.NET applications》( http://support.microsoft.com/default.aspx?scid=kb;en-us;821268 )
2、《Information about the new GetMinThreads method and the new SetMinThreads method of the ThreadPool class in the .NET Framework 1.1》( http://support.microsoft.com/default.aspx?scid=kb;en-us;827419 )
3、《FIX: SetMinThreads and GetMinThreads API Added to Common Language Runtime ThreadPool Class》( http://support.microsoft.com/default.aspx?scid=kb;en-us;810259 )

建议二:不要为每一次请求创建线程
    创建线程是一种非常奢侈的操作,它要求要初始化托管和非托管的资源。应该坚决避免手动创建线程。
    可以考虑为那些可以并行运行的调用执行异步操作。比如那些I/O操作或者那些Web服务中的远程方法等。你可以使用.NET Framework提供的基于Begin+同步方法名与End+同步方法名这样的配对方法的异步调用模型来实施你的异步方案。如果这种模式没法满足您的需求,还可以使用CLR线程池来调用。下面的代码是CLR线程池调用:
    WaitCallback methodTarget = new WaitCallback(myClass.UpdateCache);
    bool isQueued = ThreadPool.QueueUserWorkItem(methodTarget);
建议三:避免线程阻塞
    很明显这点意味着用于响应的工作线程越来越少了。
建议四:如果你有并行工作,最好使用异步调用(Avoid Asynchronous Calls Unless You Have Additional Parallel Work)
    这一点要考虑到异步编程模型,看上去你好像利用了一个工作线程在进行异步操作,实际上是另外一个线程在替你完成这件事,而工作线程在你去完成并行操作的同时将空闲下来去处理其他的请求或者其他的异步调用。当异步操作返回结果之后才会返回给工作线程进行结果的返回操作。通过获得平均时间内更多的响应工作线程数量,从而提高了应用程序的吞吐量。
相关参考:
1、《Use Threads and Build Asynchronous Handlers in Your Server-Side Web Code》( http://msdn2.microsoft.com/zh-cn/magazine/cc164128(en-us).aspx )

建议五:合理管理资源
    资源的匮乏将是影响性能的一大因素,匮乏资源的程序必将性能低下。考虑以下的几点,可以利于管理资源。
    a.资源池:ADO.NET提供了内建的数据库连接池,你的任何一次调用都将自动地连接到池里采用已经初始化过的连接资源,当你使用完后它又会回到池中待命。因此你不应该长时间地霸占它也不应该将它缓存起来,这样的结果只会导致别的请求可能因为池中没有连接对象而无法访问数据库,而事实上这些资源都是被你无端地浪费了。
    b.明确地调用关闭方法释放你有限的资源:这样的资源通常包括向数据库连接对象或者文件操作这样的共享稀缺资源的操作,明确地将其关闭将减轻内存的压力,也使资源更早地等待重用。通常可以使用try/catch/finally块将关闭的操作放在finally中,这样将保证不管是否发生异常都能关闭该稀缺对象。在C#中还可将IDisposiable接口的对象用在using(××××)块中,它将在using块执行结束后调用××××的Dispose方法。
    c.不要缓存池资源,这一点在a中已经有所提及。它们通常包括:数据库连接,网络连接,企业服务池对象等。
    d.了解我们的编程方式将有助于理解资源管理:.NET的程序通常通过GC(垃圾回收)来释放资源,GC并不是及时地释放资源,而是有一定的时间拖延的,而且就其机制本身而言也比手动释放要来得慢。通常这不会带来性能问题,但起码这是一个可以改善的点。比如用string的+=方法来将很多的字符串累加,这将无形中创建了很多的临时对象,释放这些临时对象就将增加GC的压力从而导致性能问题,.NET推荐的做法是在执行大量的字符串叠加的时候使用stringBuilder的方式进行,这样不仅仅是在字符串叠加的操作上效率更高,而且更为重要的是减轻了GC的压力。于此类似的还有String.Split方法。当出现此类问题的时候我们可以通过CLR Profiler和系统监视器来查看具体的性能问题。
    e.尽量缩短占用资源的时间:比较推荐尽量迟的地获得资源的使用,而不要在程序刚开始就获得它,而到真正使用它的这段时间内导致了总的可用资源的数量的减少。尽早地将其释放也是一种良好的习惯。没用就将其释放,霸占着它只会带来不良的后果。比如数据库连接、网络连接以及事务模块等。
    f.避免每次请求都使用最小的安全性以及最大的资源共享:应该验证一下用户的身份,不能总是让它们使用Web应用程序进程或者使用默认的系统帐户来进行操作那些像事件日志或者数据库等资源。

建议六、页面成为性能的重点
    ASP.NET页面与*.ASPX.CS文件逻辑成为设计高性能网站的一个重中之重。下面一些建议值得遵循。
    a.精简页面尺寸,使用include的方式包含脚本,这将有利于客户端缓存它们。去除页面中的空格和Tabs等没必要的空格,这将减少页面的尺寸,这在多行表格的时候尤为有效,因为表格的标签很多,因此标签间的空隙也就比较多了。手动去除空格是不太明智的,虽然少去了CPU判断空格的性能损耗,但是却给编码工作带来了极大的不便利,而因此所获得的性能提升就显得没有什么份量了。可以使用ISAPI来过滤这些不必要的字符或者使用HttpModule来进行处理。相比之下ISAPI的性能更优,但是却对编写ISAPI的程序员提出了更高的要求。可以考虑使用IIS压缩。启用IIS压缩的方式在后面这篇微软的官方帮助中有详细的步骤,并简要阐释了部分基本原理(《HOW TO: Enable ASPX Compression in IIS》http://support.microsoft.com/default.aspx?scid=kb;en-us;322603 )。另外还有几点值得注意的是减少ViewState的使用,这一点也已经被大多数的程序员所接受了。在没有用到ViewState的情况下最好将它们关闭。减少图形的使用或者用压缩图形,这一点也很容易理解。用css文件而不是将样式代码散布在网页中,这一点和脚本一样,可以起到客户端缓存的作用。但有时候很多地方类似淘宝这样的站点限制用户使用这种include的方式是出于安全等方面的考虑,但同时牺牲的就是一点的性能了。另外在模板控件的子项如Repeater中的控件要特别注意控件名的长度,它们都将用于动态生成UniqueID和ClientID,比如每个名字有10个字符,每行3个控件的话,那么100行记录,但若每个名字只有2个字符,则这些名字所带来的数据量在(10-2)×3×100=2400个字符,而这些字符对页面性能的提高就是显而易见的了。
    b.启用缓冲。缓冲所带来的问题就是对于一个较大的页面,可能用户需要等待才能看到数据。当运行ASP.NET应用程序的时候,ASP.NET的工作线程首先会用响应缓冲的形式发送响应给IIS。在ISAPI过滤器运行之后,IIS接收到缓冲响应。这些缓冲响应时31KB大小,在此之后IIS才发送实际的响应给客户端。如果缓冲不可用的话,31KB缓冲将被代替,取而代之的则是应用程序只能发送少量的字符给缓冲,这将导致在ASP.NET和IIS两方面都增加了CPU的消耗,更戏剧性地带来内存的大量消耗。

2008年4月19日星期六

看看属性被变异(编译)后的样子……

准备工作

其实没有什么准备工作,不过可以看看《关于打开ILDASM的方法(2种)》,或许对您有用。

 

前言

本来没有这个前言的,刚才自己看了标题,感觉有属性大全的味道,顺便改了标题,很可惜这里不讲那些你需要基础知识,那些知识您可以从MSDN获取。本文究竟讲些什么呢?本文其实没讲什么,就想看看属性被“编译”(变异)后的样子……

 

关于属性

get、set访问器

在使用了get、set访问器后,同类中不能够定义名称为get_[PropertyName]()与set_[PropertyName](TypeOfProperty value)这样的方法,否则将遭遇编译错误,如下图所示:

image

通过ILDASM工具查看后方可知道,get和set访问器会被编译器编译成依照上述规则而转换的公有方法供外部调用,这时我们若定义了相同签名的方法体将导致编译器的编译结果违背最基本的定义规则。

image 

自动实现的属性

众所周知在.NET 3.0的新版本中增加了自动实现的属性(automatically implemented property)语言特性。这将带来形如下面这样的属性声明方式:

        public int DotNet3Field1
{
get;
set;
}


那么它将如何编译呢?同样用ILDASM查看:



image



在图中我们可以很容易看到它通过了'<PropertyName>k__BackingField'的方式进行了声明。关于自动实现的属性的限制,请参看《自动实现的属性》而这样的方式避免了之前编译错误的尴尬,因为由编译器生成的字段名不符合我们的语言规范。



索引器



为了说明问题以下仅依照索引器的定义规范实现索引器,但不保证索引器能够正常工作:



        public int this[int index]
{
get {
return 0;
}
set {
}
}


image



仔细对比就可以看见多了



.custom instance void [mscorlib]System.Reflection.DefaultMemberAttribute::.ctor(string) = (01 00 04 49 74 65 6D 00 00)



get_Item : int32(int32)



set_Item : void(int32,int32)



Item : instance int32(int32)



以下分别是get_Item和set_Item的IL代码



.method public hidebysig specialname instance int32 
get_Item(int32 index) cil managed
{
// 代码大小 7 (0x7)
.maxstack 1
.locals init ([0] int32 CS$1$0000)
IL_0000: nop
IL_0001: ldc.i4.0
IL_0002: stloc.0
IL_0003: br.s IL_0005
IL_0005: ldloc.0
IL_0006: ret
} // end of method BaseClass::get_Item

.method public hidebysig specialname instance void
set_Item(int32 index,
int32 'value') cil managed
{
// 代码大小 2 (0x2)
.maxstack 8
IL_0000: nop
IL_0001: ret
} // end of method BaseClass::set_Item


因此其实索引器与属性之间的唯一差别仅在于参数的个数上,当然外部表现还有那个中括号。