简介 技巧1:在Web服务器上缓存常用数据 技巧2:在Application或Session对象中缓存常用数据 技巧3:在Web服务器磁盘上缓存数据和HTML 技巧4:避免在Application或Session对象中缓存非灵活组件 技巧5:不要在Application或Session对象中缓存数据库连接 技巧6:妙用Session对象 技巧7:在COM对象中封装代码 技巧8:晚点获取资源,早点释放资源 技巧9:进程外的执行将牺牲可靠性 技巧10:显式使用选项 技巧11:在子例程和函数中使用局部变量 技巧12:将常用数据复制到脚本变量 技巧13:避免重新定义数组 技巧14:使用响应缓冲 技巧15:批处理内嵌脚本和Response.Write语句 技巧16:在开始长时间的任务之前先使用Response.IsClientConnected 技巧17:使用<OBJECT>标记实例化对象 技巧18:使用ADO对象和其他组件的TypeLib绑定 技巧19:利用浏览器的验证能力 技巧20:在循环中避免字符串串联 技巧21:启用浏览器和代理缓存 技巧22:尽可能使用Server.Transfer替代Response.Redirect 技巧23:在目录URL尾部加斜线 技巧24:避免使用服务器变量 -------------------------------------------------------------------------------- 简介 性能是一个特性。您需要预先设计性能,或是在日后重新编写应用程序。换句话说,什么是最大限度优化ActiveServerPages(ASP)应用程序性能的好策略? 本文为优化ASP应用程序和"VisualBasic(R)脚本编辑器(VBScript)"提供了许多技巧。对许多陷阱和缺陷进行了讨论。本文所列的建议均在http://www.microsoft.com及其他站点上进行了测试,而且工作正常。本文假定您对ASP开发有基本的理解,包括对VBScript和/或JScript、ASPApplication、ASPSession和其他ASP内部对象(请求、响应和服务器)。 ASP的性能,通常不止取决于ASP代码本身。我们并不想在一篇文章中囊括所有的至理名言,只在最后列出与性能相关的资源。这些链接包括ASP和非ASP主题,包括"ActiveX(R)数据对象(ADO)"、"部件对象模型(COM)"、数据库和"Internet信息服务器(IIS)"配置。这些是我们喜欢的链接-务请关注它们。 技巧1:在Web服务器上缓存常用数据 典型的ASP页从后端数据库检索数据,然后将结果转换为超文本标记语言(HTML)。无论数据库的速度如何,从内存检索数据要比从后端数据库检索数据快得多。从本地硬盘读取数据通常也要比从数据库检索数据快得多。因此,通常可以通过在Web服务器(在内存或磁盘)上缓存数据来改善性能。 缓存是典型的空间与时间的折衷。如果恰当地缓存数据,您将看到性能会有惊人的提高。为使缓存发挥效力,它必须保持经常重用的数据,而且重新计算这些数据的代价是昂贵的或比较昂贵的。如果缓存充满了垃圾数据,则是对存储器的浪费。 不经常变化的数据也是缓存的候选数据,因为您无须担心数据与数据库的同步问题。组合框、引用表、DHTML碎片、可扩展标记语言(XML)字符串、菜单项和站点配置变量(包括数据源名称(DSN)、Internet协议(IP)地址和Web路径)都是缓存的候选数据。注意,您可以缓存数据的表示而不是数据本身。如果ASP页不经常更改,而且缓存的成本也非常高(例如,整个产品目录),请考虑预先生成HTML,而不是在每次请求时重新绘制。 数据应缓存在何处,有哪些缓存策略?数据经常缓存在Web服务器内存或Web服务器磁盘上。下面两个技巧讨论这些选项。 技巧2:在Application或Session对象中缓存常用数据 ASPApplication和Session对象为在内存中缓存数据提供了方便的容器。既可以将数据赋予Application对象,也可将数据赋予Session对象,这些数据在HTTP调用中将保留在内存中。Session数据按用户存储,而Application数据在所有用户间共享。 何时将数据载入Application或Session?通常,在Application或Session启动时加载数据。要在Application或Session启动时加载数据,请在下面两函数中添加相应的代码: Application_OnStart() 或 Session_OnStart() 。这两个函数应该位于Global.asa;如果没有,可以添加这些函数。也可以在第一次需要数据时加载数据。要进行上述操作,请在ASP页中添加一些代码(或编写可重用的脚本函数),这些代码检查数据是否存在,并在数据不存在时加载数据。这是称为迟缓计算的经典性能技术的例子-在您的确需要它之前,不进行计算。请看例子: <% FunctionGetEmploymentStatusList Dimd d=Application("EmploymentStatusList") Ifd=""Then 'FetchEmploymentStatusList函数(不显示) '从DB中取出数据,返回数组 d=FetchEmploymentStatusList() Application("EmploymentStatusList")=d EndIf GetEmploymentStatusList=d EndFunction %> 可以为每一块所需的数据编写类似的函数。 数据应该以什么格式存储?任何变量类型均可存储,因为所有脚本变量是各不相同的。例如,可以存储字符串、整型或数组。通常,您将以这些变量类型之一存储ADO记录集的内容。若要获取ADO记录集衍生的数据,可以手工将数据复制到VBScript变量中,每次一个字段。使用一个ADO记录集保留函数GetRows()、GetString()或Save()(ADO2.5),会更快更简便。完整而详细的内容已超出了本文的范围。下面的演示函数使用了 GetRows() 来返回记录集数据的数组: '取记录集,以数组返回 FunctionFetchEmploymentStatusList Dimrs Setrs=createObject("ADODB.Recordset") rs.Open"selectStatusName,StatusIDfromEmployeeStatus",_ "dsn=employees;uid=sa;pwd=;" FetchEmploymentStatusList=rs.GetRows()'以数组返回数据 rs.Close Setrs=Nothing EndFunction 对上面示例的进一步改进应当是缓存该列表的HTML,而不是缓存数组。下面是一个简单的范例: '取记录集,以"HTML选项"列表返回 FunctionFetchEmploymentStatusList Dimrs,fldName,s Setrs=createObject("ADODB.Recordset") rs.Open"selectStatusName,StatusIDfromEmployeeStatus",_ "dsn=employees;uid=sa;pwd=;" s="<selectname=""EmploymentStatus">"&vbCrLf SetfldName=rs.Fields("StatusName")'ADO字段绑定 DoUntilrs.EOF '下面一行违背了不要进行字符串连接, '但这是可以的,因为我们正在建立高速缓存 s=s&"<option>"&fldName&"</option>"&vbCrLf rs.MoveNext Loop s=s&"</select>"&vbCrLf rs.Close Setrs=Nothing'参见尽早释放 FetchEmploymentStatusList=s'以字符串返回数据 EndFunction 在正常的情况下,可以在Application或Session作用域中缓存ADO记录集本身。有两个警告: ADO必须为标记的自由线程 必须使用断开连接的记录集。 如果不能保证满足这两个要求,请不要缓存ADO记录集。在下面的非灵活组件和不要缓存连接技巧中,我们将讨论在Application或Session作用域中存储COM对象的危险。 如果在Application或Session作用域中存储数据,这些数据将一直保留在那儿,直到在程序中改变它、Session过期或Web应用程序重新启动时为止。数据需要更新如何处理?若要用手工强制更新应用程序数据,可以调用只允许管理员访问的数据更新ASP页。另外,还可以通过函数,周期地自动刷新数据。下面的示例存储带缓存数据的时间戳,在指定时间间隔后刷新数据。 <% '未显示错误处理... Constupdate_INTERVAL=300'刷新时间间隔,以秒计 '函数返回雇佣状态列表 FunctionGetEmploymentStatusList updateEmploymentStatus GetEmploymentStatusList=Application("EmploymentStatusList") EndFunction '定期更新缓存的数据 SubupdateEmploymentStatusList Dimd,strLastupdate strLastupdate=Application("Lastupdate") If(strLastupdate="")Or_ (update_INTERVALDateDiff("s",strLastupdate,Now))Then '注意:此处可能有两个或多个调用。这是可以的,只不过 '产生几个不必要的取指令罢了(就此有一个工作区) 'FetchEmploymentStatusList函数(不显示) '从DB中取数据,返回一个数组 d=FetchEmploymentStatusList() '更新Application对象。用Application.Lock() '来确保一致的数据 Application.Lock Application("EmploymentStatusList")=d Application("Lastupdate")=CStr(Now) Application.Unlock EndIf EndSub 其他示例,请参阅具有Application数据的最快列表框(英文)。 请注意,在Session或Application对象中缓存大型数组并非上策。在访问数组元素之前,脚本语言的语法要求建立整个数组的临时副本。例如,如果在Application对象中缓存了将美国邮政编码映射到本地气象站的字符串数组,该字符串数组有100,000个元素,ASP在找出一个字符串之前,必须将所有100,000个气象站复制到临时数组中。在这种情况下,建立带自定义方法的自定义组件,来存储气象站-或使用一个字典组件,也许更好。 请不要在倒洗澡水时把孩子一同倒掉,对这种观点的一个新的注解是:数组提供了对内存中相邻关键-数据对的快速查找和存储。索引字典比索引数组要慢。您应该根据具体情况选择能够提供最佳性能的数据结构。 技巧3:在Web服务器磁盘上缓存数据和HTML 有时,数据过多不能在内存中进行缓存。"过多"是一种定性的判断;它取决于打算消耗的内存量,还有缓存项的数量和这些项的检索频率。总之,如果有过多的数据要在内存中缓存,请考虑以文本或XML文件的形式,在Web服务器的硬盘上缓存数据。可以将在磁盘上缓存数据和在内存中缓存数据组合起来,为站点建立最优的缓存策略。 注意,在度量单个ASP页的性能时,在磁盘上检索数据不一定比从数据库中检索数据快。但是,缓存减轻了数据库和网络的负荷。在高负荷情况下,这将明显提高总体通信量。在查询成本很高时缓存查询的结果,缓存便非常有效,例如多表联合或复杂的存储过程,或缓存大型的结果集。按照惯例,测试竞争方案。 ASP和COM提供了几种构建磁盘缓存方案的工具。ADO记录集的Save()和Open()函数,保存和加载磁盘上的记录集。您可以使用这些方法重写上面Application数据缓存技巧中的范例代码,用Save()文件替换向Application对象写入数据的代码。 还有其他一些处理文件的组件: Scripting.FileSystemObject使您能够创建、读取和写入文件。 MSXML是随InternetExplorer提供的Microsoft(R)XML解析器,它支持保存和加载XML文档。 LookupTable对象(在MSN上使用的范例)是从磁盘加载简单列表的良好选择。 最后,请考虑在磁盘上缓存数据的表示,而不是数据本身。预制的HTML可以作为.htm或.asp文件存储在磁盘上;超级链接可以直接指向这些文件。可以使用商业工具,如XBuilder或Microsoft(R)SQLServer的Internet发行功能来自动化HTML生成过程。另外,可以将HTML片段#include到.asp文件。还可以使用FileSystemObject从磁盘读取HTML文件或使用XML进行早期调整(英文)。 技巧4:避免在Application或Session对象中缓存非灵活组件 虽然在Application或Session对象中缓存数据是个好主意,但是缓存COM对象可能有严重缺陷。将常用COM对象嵌入Application或Session对象通常具有吸引力。遗憾的是,很多COM对象,包括用VisualBasic6.0或更早版本编写的COM对象,在Application或Session对象中存储时将导致严重的瓶颈。 特别是任何非灵活组件,在Session或Application对象中缓存时将导致性能瓶颈。灵活组件是标记为 ThreadingModel=Both 的组件(它聚集了自由线程汇集器(FTM))或标记为 ThreadingModel=Neutral 的组件(Windows(R)2000和COM+中新增的"中性"模型。)下列组件是非灵活的: 自由线程组件(除非它们聚集了FTM)。 单元线程组件。 单线程组件。 已配置组件(MicrosoftTransactionServer(MTS)/COM+库和服务器包/应用程序)为非灵活组件,除非它们是"中性"线程的。单元线程组件和其他非灵活组件最适于在页作用域工作(也就是说,它们在单个ASP页上创建和销毁)。 在IIS4.0中,标记为 ThreadingModel=Both 的组件被视为灵活的。在IIS5.0中,这已经不够了。组件不仅必须标记为Both,而且还必须聚集FTM。灵活性文章说明了如何使得用"活动模板库"编写的C++组件聚集FTM。请注意,如果组件缓存接口指针,这些指针本身必须为灵活的、或者必须存储在"COM全局接口表(GIT)"中。如果不能重新编译Both线程组件,使它聚集FTM,则可以将该组件标记为 ThreadingModel=Neutral 。另外,如果不希望IIS进行灵活性检查(这样,希望非灵活组件能够存储在Application或Session作用域中),可以在metabase中设置 AspTrackThreadingModel 为 True 。不主张更改 AspTrackThreadingModel 。 如果试图在Application对象中存储用 Server.createObject 创建的非灵活组件,IIS5.0将产生错误。可以通过在Global.asa中使用 <objectrunat=serverscope=application...> 解决该问题,但是不主张这样做,因为这将导致汇集和串行化,说明如下。 如果缓存非灵活组件,会发生什么错误呢?缓存在Session对象中的非灵活组件,将把会话"锁定"到某个ASP工作器线程。ASP维护着一个工作器线程池,它向请求提供服务。通常,新的请求由第一个可用的工作器线程来处理。如果Session被锁定到某个线程,则该请求将不得不等待它所关联的线程变为可用。打个比方:您进入一个超市,挑选了一些食品,然后在第3号收款台交款。从这以后,每当您在这个超市购买食品,都不得不始终在第3号收款台交款,即使是在其他收款台人少或没人时。 将非灵活组件存储在Applicaton作用域甚至会对性能产生更严重的影响。ASP将不得不创建专用的线程来运行非灵活的、Applicaton作用域内的组件。这将导致两种后果:所有调用不得不被汇集到该线程,而且所有调用被串行化。汇集意味着:参数不得不存储在内存的共享区;对该专用线程执行昂贵的上下文切换;组件的方法被执行;结果汇集到共享区域;以及经过另一个昂贵的上下文切换,使控制权返回原来的线程。串行化意味着所有方法必须一个挨一个地运行(同一时刻只能运行一个方法)。两个不同的ASP工作器线程不可能同时执行共享组件上的方法。这将扼杀并行机制,尤其是在多处理器计算机上。更坏的是,所有非灵活的、Application作用域内的组件都将共享一个线程("HostSTA"),所以串行化的影响更加严重。 是否感到困惑?下面我们提出几个通用规则。如果您正在用VisualBasic(6.0)或更早版本编写对象,请不要将它们缓存在Application或Session对象中。如果您不知道对象的线程模型,就不要缓存它。不要缓存非灵活对象,而应当在每页上创建并释放它们。对象将直接运行在ASP工作器线程上,这样,将不会发生汇集或串行化。如果COM对象正运行在IIS框中,而且如果它们没有花很长时间来初始化和取消,性能将是足够的。注意,不要用该方法使用单线程对象。小心:VB可以创建单线程的对象!如果您必须以该方式使用单线程的对象(如MicrosoftExcel电子表格),则不要期望有很高的吞吐量。 当ADO被标记为自由线程时,则缓存ADO记录集是安全的。要将ADO标记为自由线程,请使用Makfre15.bat文件,该文件通常位于如下目录中://ProgramFiles/Common/System/ADO。 警告:如果您正在用MicrosoftAccess作为数据库,则不应当将ADO标记为自由线程。通常,ADO记录集还必须是断开连接的,如果您不能控制站点的ADO配置(例如,您是独立的软件厂商[ISV],将Web应用程序卖给客户,然后由他们来管理他们自己的配置),那么不缓存记录集可能会更好。 词典组件也是灵活对象。LookupTable从数据文件加载它的数据,并且它对组合框数据和配置信息是有用的。来自DuwamishBooks的PageCache对象提供了目录语义,和CaprockDictionary的表现一样。这些对象或它们的派生对象可以构成有效缓存策略的基础。注意,Scripting.Dictionary对象不是灵活的,所以不应当存储在Application或Session作用域。 技巧5:不要在Application或Session对象中缓存数据库连接 缓存ADO连接通常是不好的策略。如果一个Connection对象存储在Application中,并在所有页上使用,那么所有页将竞争使用该连接。如果Connection对象存储在ASPSession对象中,那么将为每个用户创建数据库连接。这将连接池的好处毁于一旦,并对Web服务器和数据库产生不必要的压力。 取代缓存数据库连接的方法是,在每个使用ADO的ASP页上创建并取消ADO对象。这是个有效的方法,因为IIS具有内置的数据库连接池。更准确的说,IIS自动启用OLEDB和ODBC连接池。这确保了创建并取消每个页上的连接将是有效的。 由于被连接的记录集中存储有对数据库连接的引用,所以,不应当在Application或Session对象中缓存被连接的记录集。但是,可以安全地缓存断开连接的记录集,因为它不包含对其数据连接的引用。要断开记录集的连接,请执行如下两个步骤: Setrs=Server.createObject("ADODB.RecordSet") rs.CursorLocation=adUseClient'第1步 '植入带数据的记录集 rs.OpenstrQuery,strProv '现在断开记录集同数据提供者和数据源的连接 rs.ActiveConnection=Nothing'第2步 有关连接池的详细信息,请参阅ADO和SQLServer(英文)引用。 技巧6:妙用Session对象 在肯定了在Applications和Sessions中缓存的优点之后,我们建议您避免使用Session对象。下面将会谈到,当用于忙碌站点时,Sessions有几个缺点。所谓忙碌,通常是指站点每秒请求数百页或同时有数千个用户。该技巧对于必须进行水平扩展的站点,即那些利用多个服务器来适应负载或执行容错功能的站点来说,更加重要。对于较小的站点,如intranet站点,Sessions的便利,与开销相比也是值得的。 为了翻新,ASP自动为每个访问Web服务器的用户创建一个Session。每个Session有大约10KB内存开销(在存储在Session中的任何数据中是最高的),并使所有的请求都慢了一点。Session一直保持活动状态,直到达到可配置的超时(通常20分钟)为止。 Session最大的问题不是性能而是可伸缩性。Session不能跨越Web服务器;一旦在一个服务器上创建了Session,它的数据就保持在那里。这意味着,如果您在Web领域中使用Sessions,您将不得不为每个用户的请求设计一种策略,以便始终将这些请求引向用户的Session所在的服务器。这被称为将用户"粘"到Web服务器上。术语"粘性会话"即来源于此。由于Session没有保持到磁盘上,所以,当Web服务器崩溃时,被"粘住"的用户将丢失他们的Sessions状态。 用于实施粘性会话的策略包括硬件和软件解决方案。如Windows2000AdvancedServer中的网络负载平衡解决方案和Cisco公司的"本地指向器"解决方案可以实施粘性会话,但以牺牲一些可伸缩性为代价。这些解决方案并不完美。我们不主张您现在全盘推翻您的软件解决方案(我们过去常用ISAPI筛选器和URL矫直对方案进行检查)。 Application对象也不能跨越服务器;如果您需要在Web领域内共享并更新Application数据,则需要使用后端数据库。但只读的Application数据在Web领域中仍然有用。 如果只是为了增加正常运行时间(用于处理故障转移和服务器维护),大多数执行重要任务的站点将需要部署至少两台Web服务器。所以,在设计执行重要任务的应用程序时,您将需要实施"粘性会话",或者简单地避开Sessions以及其他任何在单个Web服务器上存储用户状态的状态管理技术。 如果当前没有使用Sessions,请确保将它们关闭。可以通过"Internet服务管理器"(请参阅ISM文档)来为应用程序执行该操作。如果决定使用Sessions,可以采取几个方法来将对性能的影响降低到最小。 可以将不需要Sessions的内容(如"帮助"屏幕、访问者区域等)移动到关闭了Sessions的、单独的ASP应用程序中。可以逐页提示ASP:在给定的页中您不需要Session对象;使用位于ASP页顶端的如下指令: <%@EnableSessionState=False%> 使用该指令的一个很好的原因是,Session给框架集带来了有趣的问题。ASP保证任何时候只执行一个来自Session的请求。这样可以确保如果浏览器为一个用户请求了多个页时,在每一时刻只有一个ASP请求将进入Session;这就避免了在访问Session对象时出现多线程问题。遗憾的是,结果,框架集中的所有页均被以串行化方式绘制,一个接一个地,而不是同时地。这样,用户可能不得不等待很长时间才能得到所有框架内容。这意味着:如果某些框架页不信任Session,一定要使用 @EnableSessionState=False 指令告诉ASP。 作为使用Session对象的替代方式,有很多方法可以用来管理Session状态。对于状态数量较小的情况(不到4KB),通常建议使用Cookies、QueryString变量和隐藏形式的变量。对于较大数量的数据,如购物推车,则使用后端数据库是最合适的选择。关于在Web服务器领域中的状态管理技术已经有很多资料。详细信息,请参阅会话状态(英文)。 技巧7:在COM对象中封装代码 如果您有很多VBScript或JScript,那么您可以通过把代码移动到已编译的COM对象来经常改进它们的性能。已编译的代码通常比被解释代码运行得更快。已编译的COM对象可以通过"早期绑定"访问其他COM对象,这种调用COM对象方法的手段,比脚本所使用的"后期绑定"更有效。 将代码封装在COM对象种有如下好处(超越性能): COM对象是将表达逻辑与业务逻辑分隔开来的好办法。 COM对象启用了代码重用。 很多开发商发现,用VB、C++或VisualJ++书写的代码,比ASP更容易调试。 COM对象有一些缺点,包括初始开发时间以及需要不同的编程技巧。需要警告您的是,封装"少"量的ASP可能会导致性能降低,而不是提高。通常,在少量ASP代码封装到COM对象时出现这样的情况。这时候,创建和调用COM对象的开销,超过了已编译代码的好处。至于ASP脚本和COM对象代码怎样合并才能产生最佳性能还有待测试。注意,与WindowsNT(R)4.0/IIS4.0相比,Microsoft已经在Windows2000/IIS5.0中极大地提高了脚本和ADO性能。这样,已编译代码对ASP代码的性能优势已经随着IIS5.0的引入而降低。 有关在ASP中使用COM对象的优缺点的更多讨论,请参阅ASP组件准则和用COM和MicrosoftVisualBasic6.0对分布式应用程序进行编程(英文)。如果您的确部署了COM组件,要对它们进行强度测试是非常重要的。实际上,所有ASP应用程序都应当作为正式过程进行强度测试。 技巧8:晚点获取资源,早点释放资源 这是个小技巧。通常,最好晚点获取资源而要早点释放资源。这些资源包括COM对象、文件句柄和其他资源。 ADO连接和记录集是这种优化的首要目标。当您使用完记录集,就是说用它的数据打印完一个表格后,请立即将它释放,而不是等到页的末尾。将您的VBScript变量设置为 Nothing 是最好的做法。不要让记录集简单地脱离作用域。同时,应当释放任何有关的Command或Connection对象。(不要忘了对记录集或"连接"调用 Close() ,在将它们设置为 =Nothing 之前。)这将缩短数据库必须为您调整资源的时间跨度,并将数据库连接尽可能快地释放给连接池。 技巧9:进程外的执行将牺牲可靠性 ASP和MTS/COM+都有允许您以可靠性换取性能的配置选项。当建立和部署应用程序时,应当理解这种交换。 ASP选项 ASP应用程序可以配置为以三种方式之一运行。在IIS5.0中引入了术语"隔离级"来描述这些选项。三个隔离级值分别是低、中和高: 低级隔离。该隔离级在所有版本的IIS中受到支持,并且是最快的。它在主IIS进程Inetinfo.exe中执行ASP。如果ASP应用程序崩溃,则IIS也将崩溃。(要在IIS4.0下重新启动IIS,Web站点管理员需要使用工具,如InetMon,来监视站点,如果服务器失败,将运行批处理文件来重新启动服务器。而IIS5.0则引入了可靠的重新启动,它将自动重新启动失败的服务器。) 中级隔离。IIS5.0引入了这个新隔离级,它称为进程外的,这是因为ASP运行在IIS进程之外。在中级隔离中,所有被配置按"中级"运行的ASP应用程序,将共享单个进程空间。这将减少在一个服务器上运行多个进程外的ASP应用程序所需的进程数。中级是IIS5.0中默认的隔离级。 高级隔离。在IIS4.0和IIS5.0中受到支持,高级隔离也是进程外的。如果ASP崩溃,则Web服务器并不崩溃。ASP应用程序将在下一个ASP请求时自动重新启动。使用高级隔离,每个被配置为按高级运行的ASP应用程序,将在其自己的进程空间中运行。这样可以保护ASP应用程序彼此不受干扰。它的缺点是它需要为每个ASP应用程序建立独立的进程。当需要在一个服务器上主持十多个应用程序时,会增加很多开销。 那么,哪个选项是最好的呢?在IIS4.0中,运行进程外的应用程序会极大地影响性能。在IIS5.0中,做了许多工作,使得进程外运行ASP应用程序对性能产生的影响降到了最低。实际上,在大多数测试中,在IIS5.0中的ASP进程外应用程序,要比IIS4.0中的进程内应用程序运行得更快。无论如何,进程内(低隔离级)在两种平台上仍然产生了最好的性能。但是,如果您的命中率相对较低或最大吞吐量较低,选择低隔离级不会有太大的好处。所以,除非您需要每个Web服务器每秒处理数百或数千个页面,否则没有必要选择低隔离级。同样,应当测试多种配置并判断哪种情形最适合您。 注意:当您进程外运行ASP应用程序(中级或高级隔离)时,则在NT4上它们将运行在MTS中,而在Windows2000上它们将运行在COM+中。即,在NT4上它们运行在Mtx.exe中,而在Windows2000上它们运行在DllHost.exe中。在"任务管理器"中,您可以看见这些正在运行的进程。还可以看见IIS如何为进程外的ASP应用程序配置MTS程序包或COM+应用程序。 COM选项 COM组件也有三个配置选项,虽然与ASP选项不完全相似。COM组件可以被:"不配置"、配置为"库应用程序"或配置为"服务器应用程序"。"不配置"是指不向COM+注册组件。组件将运行在调用者的进程空间,就是说,它们是"进程中"的。"库应用程序"也是进程中的,但受惠于COM+的服务,包括安全性、事务和环境支持。"服务器应用程序"被配置为在其自己的进程空间中运行。 您可能看到,不配置的组件比库应用程序优点稍微多些。您还可能看到"库应用程序"比"服务器应用程序"有很大的性能优点。这是因为"库应用程序"与ASP运行在同一个进程中,而"服务器应用程序"则运行在自己的进程中。内部进程调用的开销要比进程内调用的开销大得多。而且,当在进程之间传递数据(如记录集)时,必须在两个进程之间复制所有的数据。 缺点!当使用"COM服务器应用程序"时,如果要在ASP和COM之间传递对象,请确保对象实现"按值汇集",即MBV。实现MBV的对象将其自身从一个进程复制到另一个进程。这比另一种方式好,在另一种方式中,对象留在创建它的进程中,而其他进程则重复调用创建使用该对象的进程。被断开连接的ADO记录集将是按值汇集的,已连接的记录集则不是。Scripting.Dictionary并不实现MBV,不会在进程之间传递。最后,要另外告诉VB程序员的是:MBV不是通过传递参数 ByVal 获得的。MBV是由原始组件创作者实现的。 怎么办? 如果您想要以性能与可靠性的合理交换来完成您的配置,我们的推荐如下: 在IIS4.0上,使用ASP的低隔离级别,并使用"MTS服务器包"。 在IIS5.0上,使用ASP的中隔离级别,并使用"COM+库应用程序"。 这些是很一般的准则;通常让公司以中或高隔离级别运行ASP,而单一目的的Web服务器可运行于低隔离级别。请权衡折中并自行决定满足需求的配置。 技巧10:显式使用选项 在.asp文件中显式使用 选项Explicit 。置于.asp文件开头的这一指令,强制开发人员声明所有要使用的变量。许多开发人员认为这有助于调试应用程序,因为它避免了错误键入变量名称而不经意地新建变量(例如, MyXLMString=... 而非 MyXMLString=) 。 也许更重要的是,声明的变量比未声明的变量快。实际上,脚本运行时,在每次使用未声明变量时按照名称引用。而声明的变量,在编译或运行时分配了序号。这样,声明的变量按照该序号引用。由于 选项Explicit 强制变量声明,因此保证声明了所有变量而实现快速访问。 技巧11:在子例程和函数中使用局部变量 局部变量是在子例程和函数中声明的变量。在子例程和函数中,局部变量访问要快于全局变量访问。使用局部变量还可以使代码更加清晰,因此尽可能使用局部变量。 改进性能和样式的24个ASP技巧 www.hackbase.com阅读:时间:2005-4-78:13:54来源:www.hackbase.com 技巧12:将常用数据复制到脚本变量 在ASP中访问COM时,应该将常用的对象数据复制到脚本变量中。这将削减COM方法的调用,COM方法的调用与访问脚本变量相比,要相对昂贵些。在访问Collection和Dictionary对象时,这一技术也可以削减了昂贵的查找。 通常,如果打算多次访问对象数据,请将数据放入脚本变量。该优化的主要目标是Request变量(Form和QueryString变量)。例如,您的站点可能传递一个名为UserID的QueryString。假定该UserID变量要在特定页中引用12次。请不要调用 Request("UserID") 12次,而在ASP页的开头将UserID赋予某个变量。然后就在页中使用该变量。这将节省11次COM方法调用。 在实际中,访问COM属性或方法暗藏着繁复的过程和大量的开销。下面是一个示例,它只是些相当普通的代码(从语法上讲): Foo.bar.blah.baz=Foo.bar.blah.qaz(1) IfFoo.bar.blah.zaq=Foo.bar.blah.abcThen'... 在运行这段代码时,将发生下列事件: 变量 Foo 被解析为全局变量。 变量 bar 被解析为 Foo. 的成员。这将产生COM方法调用。 变量 blah 被解析为 Foo.bar 的成员。这也将产生COM方法调用。 变量 qaz 被解析为 foo.bar.blah 的成员。是的,这也将产生COM方法调用。 调用 Foo.bar.blah.quaz(1) 。又一次产生COM方法调用。理解这幅图了吗? 执行步骤1到3将再次解析 baz 。系统不知道调用 qaz 是否更改对象模型,因此步骤1到3必须再次执行解析 baz 。 将 baz 解析为 Foo.bar.blah 的成员。进行属性置入。 再次执行步骤1到3并解析 zaq 。 再次执行步骤1到3并解析 abc 。 正如所见,这是非常可怕的低效率(而且非常慢)。用VBScript编写该代码实现的快速方法为: Setmyobj=Foo.bar.blah'对blah做一次解析 Myobj.baz=myobj.qaz(1) IfMyobj.zaq=Myobj.abcThen'... 如果您使用的是VBScript5.0或更高版本,则可用 With 语句来写这段代码: WithFoo.bar.blah .baz=.qaz(1) If.zaq=.abcThen'... ... EndWith 请注意该技巧对VB编程同样有效。 技巧13:避免重新定义数组 尽量避免 Redim 数组。从关心性能的角度来说,如果计算机受物理内存的限制,最好一开始将数组的维数设置为最差方案-而不要将维数设置为最佳方案,再根据需要重新定义维数。这并不意味着明知道不需要那么多而就是应该分配太多的内存。 下面代码展示了您没有必要地使用了 Dim 和 Redim 来解决。 <% DimMyArray() RedimMyArray(2) MyArray(0)="hello" MyArray(1)="good-bye" MyArray(2)="farewell" ... '一些别的代码中,这里您不需要更多的空间,然后... RedimPreserveMyArray(5) MyArray(3)="morestuff" MyArray(4)="evenmorestuff" MyArray(5)="yetmorestuff" %> 更好的办法是只须一开始 Dim 数组为正确的大小(本例中为5),而不是 Redim 数组,再加大数组。这可能会浪费一点儿内存(如果没有用尽所有元素),但是获得的是速度。 技巧14:使用响应缓冲 您可以通过打开"响应缓冲区"来缓冲值得输出的整个页。这将写入浏览器的数据量降为最小,从而提高总体性能。每次写入都会有大量开销(包括IIS和通过电缆发送的数据量),因此写入的越少越好。TCP/IP的工作效率,在发送少量大的数据块时明显高于发送大量小的数据块时,原因在于它的低速启动和Nagling算法(用于最小化网络阻塞)。 打开响应缓冲有两种方法。第一种,可以使用"Internet服务管理器"为整个应用程序打开响应缓冲。这是推荐的方法,在IIS4.0和IIS5.0中,在默认情况下,为新的ASP应用程序打开响应缓冲。第二种,逐页将下列代码行放在ASP页的开头,从而启用响应缓冲: <%Response.Buffer=True%> 该行代码必须在任何响应数据写入浏览器之前执行(也就是说,在任何HTML出现在ASP脚本中之前和任何Cookies被使用 Response.Cookies 集合设置之前)。通常,最好是为整个应用程序打开响应缓冲。这允许省略上面每页中的代码行。 Response.Flush 响应缓冲的通病是用户感觉ASP页响应迟钝(尽管总体响应时间改善了),因为他们需要等到整个页生成后才能看见该页。对于长时间运行的页面,可以通过设置 Response.Buffer=False 关闭响应缓冲。但是,更好的策略是使用 Response.Flush 方法。该方法刷新由ASP绘入浏览器的所有HTML。例如,绘制了具有1,000行的表的100行后,ASP可以调用 Response.Flush 强制将结果绘制到浏览器;这允许用户在其余的行准备好之前先看到头100行。该技术给了您两个举世无双的好东西-响应缓冲与浏览器中数据的逐步显示的组合。 (注意,在上面1,000行表的示例中,许多浏览器,在看到</table>结束标记之前不会开始绘制表。请检查目标浏览器的支持性。要解决该问题,请将表分割为具有较少行的多个表,然后在每个表后面调用 Response.Flush 。新版本的InternetExplorer将在表完全下载之前绘制表,特别是如果指定表的列宽则绘制速度更快;这避免强制InternetExplorer通过度量每个单元格的内容来计算列宽。) 响应缓冲的另一个通病是在生成大型页时将使用服务器的大量内存。对于该问题,除了要求生成大型页的技巧外,还可以通过巧妙地使用 Response.Flush 来解决。 技巧15:批处理内嵌脚本和Response.Write语句 VBScript语法 <%=expression%> 将" 表达式 "的值写入ASP输出流。如果响应缓冲没有打开,则这些语句的每一句都会导致通过网络,以许多小型包的形式,向浏览器写入数据。这是非常慢的。另外,解释少量脚本和HTML,将导致在脚本引擎和HTML之间切换,也降低了性能。因此,请使用下面技巧:用对 Response.Write 的一个调用,替换内嵌的密集组合表达式。例如,在下面范例中,每行每字段有一个对响应流的写入,每行都有许多VBScript和HTML之间的切换: <table> <%ForEachfldinrs.Fields%> <th><%=fld.Name%></th> <% Next WhileNotrs.EOF %> <tr> <%ForEachfldinrs.Fields%> <td><%=fld.Value%></td> <%Next </tr> <%rs.MoveNext Wend%> </table> 下面是更有效的代码,每行中有一个对响应流的写入。所有代码均包含在一个VBScript块内: <table> <% Foreachfldinrs.Fields Response.Write("<th>"&fld.Name&"</th>"&vbCrLf) Next WhileNotrs.EOF Response.Write("<tr>") ForEachfldinrs.Fields%> Response.Write("<td>"&fld.Value&"</td>"&vbCrLf) Next Response.Write"</tr>" Wend %> </table> 当响应缓冲被禁用时,本技巧的作用更大。最好启用响应缓冲,然后观察批处理 Response.Write 是否对性能有帮助。 (在这一特例中,构建表的主体的嵌套循环( WhileNotrs.EOF... )可以被精心构造的、对GetString的调用所替代。) 技巧16:在开始长时间的任务之前先使用Response.IsClientConnected 如果用户失去耐心,他们可以在开始执行他们的请求之前放弃ASP页。如果他们单击了Refresh或跳转到服务器的其他页上,在ASP请求队列的末尾将有一个新的请求,而在队列的中间有一个断开连接的请求。这通常发生在服务器处于高负荷的情况下(它有一个很长的请求队列,相应的响应时间也很长),这只能使情况更糟。如果用户不再连接,将没有执行ASP页的点(特别是低速、重量级的ASP页)。可以使用 Response.IsClientConnected 属性检查这种情况。如果它返回 False ,则应调用 Response.End 并放弃该页的剩余内容。实际上,每当ASP要执行新的请求时,IIS5.0便将该方法编码,来检查队列中的请求有多长。如果在那里超过了3秒钟,ASP会检查客户是否仍然连接着,如果客户已断开连接,就立即结束该请求。您可以使用metabase中的 AspQueueConnectionTestTime 设置,调整这3秒的超时时间。 如果有某页执行了很长时间,您可能还想按一定的时间间隔检查 Response.IsClientConnected 。在启用响应缓冲之后,按一定的时间间隔执行 Response.Flush ,告诉用户正在进行的是哪些事情,是个好办法。 注意在IIS4.0中, Response.IsClientConnected 将不能正常工作,除非首先执行 Response.Write 。如果启用了缓冲,也需要执行 Response.Flush 。在IIS5.0中则不必如此- Response.IsClientConnected 工作得很好。在任何情况下, Response.IsClientConnected 都要有些开销,所以,只有在执行至少要用500毫秒(如果想维持每秒几十页的吞吐量,这是一个很长的时间了)的操作前才使用它。作为通常的规则,不要在紧密循环的每次迭代中调用它,例如当绘制表中的行,可能每20行或每50行调用一次。 技巧17:使用<OBJECT>标记实例化对象 如果需要引用不能在所有代码路径中使用的对象(尤其是服务器-或应用程序-作用域的对象),则使用Global.asa中的 <objectrunat=serverid=objname> 标记来声明它们,而不是使用 Server.createObject 方法。 Server.createObject 立刻创建对象。如果以后不使用那个对象,就不要浪费资源。 <objectid=objname> 标记声明了objname,但实际上objname此时并没有创建,直到它的方法或属性第一次被使用时才创建。 这是迟缓计算的另一个例子。 技巧18:使用ADO对象和其他组件的TypeLib声明 当使用ADO时,开发人员经常包含 adovbs.txt 来获得对ADO不同常量的访问权。该文件必须包含在要使用这些常量的每一页中。该常量文件非常大,给每个ASP页增加了很多编译时间和脚本大小方面的开销。 IIS5.0提供了绑定到组件类型库的能力。允许您在每个ASP页上引用一次类型库并使用它。每页不需要为编译常量文件付出代价,并且组件开发人员不必为在ASP中的使用而生成VBScript#include文件。 要访问ADO类型库,请将下列语句之一放入Global.asa中。 <!--METADATANAME="MicrosoftActiveXDataObjects2.5Library" TYPE="TypeLib"UUID=""--> 或者 <!--METADATATYPE="TypeLib" FILE="C:/ProgramFiles/CommonFiles/system/ado/msado15.dll"--> 技巧19:利用浏览器的验证能力 流行的浏览器具有对以下功能的高级支持,例如XML、DHTML、Java小程序以及远程数据服务。请尽量利用这些功能。所有这些技术,都可以通过执行客户端的验证和数据缓存,减少了与Web服务器之间的往返。如果您正在运行智能浏览器,该浏览器可以为您进行一些验证(例如,在运行POST之前检查信用卡的校验和否有效)。重申一次,请尽量使用这些功能。由于削减了客户端到服务器的往返路程,将减少对Web服务器的压力,并且削减了网络通信量(虽然发送给浏览器的初始页面可能更大),服务器访问的所有后端资源也削减了。而且用户不必经常提取新页,使用户的感受好一些。这并不减轻对服务器端验证的需要。还是应该经常进行服务器端的验证。这样能够防止由于某些原因从客户端来的坏数据,例如黑客,或者不运行客户端验证程序的浏览器。 许多站点由独立于浏览器创建的HTML组成。这一点经常阻碍开发人员利用可以提高性能的流行浏览器功能。对于真正高性能的、必须关心浏览器的站点,良好的策略是针对流行的浏览器优化您的页面。在ASP中使用"浏览器性能组件",很容易检测到浏览器的功能。诸如MicrosoftFrontPage等工具,能帮助您设计使用所希望的目标浏览器和HTML版本的代码。更详细的讨论,请查看WhenisBetterWorse?WeighingtheTechnologyTrade-Offs(英文)。 技巧20:在循环中避免字符串串联 许多人在循环中创建类似这样的字符串: s="<table>"&vbCrLf ForEachfldinrs.Fields s=s&"<th>"&fld.Name&"</th>" Next WhileNotrs.EOF s=s&vbCrLf&"<tr>" ForEachfldinrs.Fields s=s&"<td>"&fld.Value&"</td>" Next s=s&"</tr>" rs.MoveNext Wend s=s&vbCrLf&"</table>"&vbCrLf Response.Writes 这种方法有几个问题。首先,重复连接字符串所花费的时间,以二次方曲线的速率增长;粗略地计算,运行循环所花费的时间,与记录数乘以字段数的平方成正比。举一个简单的例子,便能清楚地说明这一点。 s="" Fori=Asc("A")toAsc("Z") s=s&Chr(i) Next 在第一次迭代中,得到一个字符的字符串 "A" 。在第二次迭代中,VBScript必须重新分配字符串并复制两个字符 "AB" 到 s 。在第三次迭代中,它必须再次重新分配 s ,并复制三个字符到 s 。在第N次(26次)迭代中,它必须重新分配并复制N个字符到 s 。就是1+2+3+...+N的和,为N*(N+1)/2次复制。 在以上记录集的例子中,如果有100条记录和5个字段,则内部的循环将执行100*5=500次,并且完成所有复制和重新分配所花费时间,将与500*500=250,000成正比。对一个大小适度的记录集,将有很多次复制。 在该例子中,代码可以改进:字符串的连接将被 Response.Write() 或内嵌脚本( <%=fld.Value%> )所替代。如果打开响应缓冲,这个操作将会很快,因为 Response.Write 仅仅将数据添加到响应缓冲的末尾。不再重新分配,因而非常有效。 特别是在将ADO记录集转换到HTML表时,请考虑使用GetRows或GetString。 如果用JScript连接字符串,强烈建议使用 += 操作符;即用 s+="某字符串", 而不是 s=s+"某字符串" 。 技巧21:启用浏览器和代理缓存 默认情况下,ASP禁用浏览器和代理中的缓存。这将很有意义,因为ASP生来就是动态的,具有潜在地对时间敏感的信息。如果有一个不需要对每次查看进行刷新的页,则应该启用浏览器和代理缓存。这使得浏览器和代理能在某一段时间内,使用某一页的缓存副本,这时间的长短可以控制。缓存能明显减轻服务器负荷,使用户的感受好一些。 哪种动态页可以缓存?举例说明: 天气页,每5分钟更新一次。 列出新闻的主页或新闻发布的主页,每天更新2次。 公共基金运营列表,基本的统计数小时更新1次。 请注意,使用浏览器或代理缓存,只有很少的命中被记录到Web服务器上。如果想精确测量所有页面查看或者张贴广告,也许不喜欢使用浏览器和代理缓存。 浏览器缓存是由Web服务器发往浏览器的HTTP截至期限标题控制的。ASP提供了两种发送标题的机制。要将页面设置为在未来某个分钟数后过期,请设置 Response.Expires 属性。以下的例子通知浏览器:内容在10分钟后过期: <%Response.Expires=10%> 设置 Response.Expires 为负数或0则禁用缓存。一定要使用较大的负数,例如-1000(大于一天),来克服服务器时钟和浏览器时钟之间的差异。第二个属性 Response.ExpiresAbsolute ,允许设置内容过期的指定时间: <%Response.ExpiresAbsolute=#May31,200113:30:15#%> 如果不想使用Response对象设置过期时间,可以将<META>标记写入HTML,通常写在HTML文件的<HEAD>内部。一些浏览器会响应这条指令,但代理不会。 <METAHTTP-EQUIV="Expires"VALUE="May31,200113:30:15"> 最后,可以标识内容对HTTP代理缓存是否有效,请使用 Response.CacheControl 属性。设置属性为"Public",允许代理缓存内容。 <%Response.CacheControl="Public"%> 默认情况下,该属性设置为"Private"。注意,不应当为显示某用户专用数据的页启用代理缓存,因为代理也许为属于其他用户的用户页面服务。 技巧22:尽可能使用Server.Transfer替代Response.Redirect Response.Redirect 通知浏览器,请求一个不同的页面。该函数经常用于重定向用户到登录或错误页面。既然重定向强制一个新页请求,浏览器就必须做两次到Web服务器的往返,而且Web服务器必须处理额外的请求。IIS5.0引入一个新的函数, Server.Transfer ,该函数执行传送到相同服务器上的不同ASP页。这样避免了额外的、从浏览器到Web服务器的往返,从而改善了整体系统性能,同时改善了对用户的响应时间。请查看重定向中的新方向(英文),它讨论了 Server.Transfer 和 Server.Execute 。 也可以查看LeveragingASPinIIS5.0中有关IIS5.0和ASP3.0新功能的完全列表。(英文) 技巧23:在目录URL尾部加斜线 相关的技巧是,一定要定在指向目录的URL尾部加斜线 (/) 。如果省略了斜线,浏览器将向服务器提出请求,仅通知它正寻找一个目录。然后浏览器发出第二个请求,在URL末尾添加斜线,然后服务器将那个目录的默认文档作为响应,或者如果没有默认文档并且目录浏览已被启用,就以目录列表作为响应。添加了斜线便省去了第一个没用的往返。出于对用户的友好,也许想要在显示的名称的末尾省略斜线。 例如,写: <ahref="http://msdn.microsoft.com/workshop/"title="MSDNWeb Workshop">"target=_blank>http://msdn.microsoft.com/workshop</a> 它还适用于指向在Web站点主页的URL:请使用下面的:<ahref=",不要用'target=_blank>http://msdn.microsoft.com/">,不要用<ahref="./'target=_blank>http://msdn.microsoft.com">. 技巧24:避免使用服务器变量 访问服务器变量将引起Web站点向服务器提出特殊的请求,然后收集所有的服务器变量,并不止是需要的那个。这好像从发霉的阁楼中的文件夹中检索某条特殊的信息一样。当想要某条信息时,在访问该信息之前必须先上阁楼取得文件夹。这与请求服务器变量时,性能访问出现第一次请求服务器变量所发生的一样。后续的对其他服务器变量的访问不会引起性能访问。 从不访问不合格的Request对象(例如, Request("Data") )。对于不在 Request.Cookies 、 Request.Form 、 Request.QueryString 或 Request.ClientCertificate 中的项,有对 Request.ServerVariables 的隐含调用。 Request.ServerVariables 集合比其他集合慢很多。 |
改进性能和样式的 24个 ASP 技巧(zz)
最新推荐文章于 2019-06-22 23:53:51 发布