Improving ASP Application Performance
From Mynoteswiki.com
J.D. Meier
March 28, 2000
Contents |
Introduction
Developers often ask how to get more performance out of their ASP applications. Because I frequently review Active Server Pages (ASP) applications for performance, I figured I'd share the set of questions I ask to identify problems and recommend improvements. Some of these tips may seem like common sense, while others may be less obvious; however, all of the recommendations are backed by real world experience.
ASP Page Performance
- Are you storing Visual Basic objects in Session or Application scope?
Visual Basic® and other Single-Threaded Apartment (STA) objects should be used only at page scope. Storing STAs in a Session variable locks the object down to the thread that created the object, defeating the purpose of a thread pool. Storing an STA in Application scope serializes access for all users. See the following articles for more information on this issue:
- Don Box's "ASP and COM Apartments"
- Q243543 "INFO: Do Not Store STA Objects in Session or Application"
- Q243548 "INFO: Design Guidelines for Visual Basic Components Under ASP"
- Q243815 "PRB: Storing STA COM Component in Session Locks to Single Thread"
- Are you storing the Scripting.Dictionary in Session or Application scope?
The Scripting.Dictionary is Apartment threaded and should be used only at page scope, or your application will suffer serious serialization issues (see Q194803 "PRB: Scripting.Dictionary Object Fails in ASP Application Scope" ). This limitation usually raises the question of what dictionaries can be used at Session or Application scope. Options are somewhat limited, but include the Commerce Dictionary and the LookupTable Object. See "Abridging the Dictionary Object: The ASP Team Creates a Lookup-Table Object."
- Are your ASP scripts hundreds of lines long?
Script is interpreted line by line, so eliminating redundant script or creating more efficient script can improve performance. If you have hundreds of lines of ASP script in a single page, perhaps you can better partition your user, business, and data services.
ASP script is great for gathering input or formatting output, but when it comes to business and data services, components offer some additional benefits -- such as early binding and protection of intellectual property. In his article "Component vs. Component Part II," Jason Taylor discusses significant performance gains he found porting his ASP script to custom components, using Windows Script Components (WSC) as an intermediate step.
If you aren't using components, you can still partition your services with functions. For example, if your script is rendering several tables, make a generic function to generate your tables. You can then put these functions into includes, or lay the groundwork for a future port to components. The following sample illustrates using functions and includes for improved maintenance.
<script language="vbscript" runat="server">
Sub Main()
WriteHeader
WriteBody
WriteFooter
End Sub
Sub WriteBody()
...
End Sub
Main 'call sub Main </script>
- Are your #include files too big?
There are no hard and fast rules for include-file sizes, but knowing how includes work can help you gauge whether you are using them efficiently. When ASP processes includes, it reads the entire file into memory. Because ASP will cache the entire expanded code (your page + the include), not just the functions you call, you may end up with large, inefficient namespaces that ASP must search when calling methods or looking up variables. Note that this process occurs for each page that uses the include. A good guide is to create fine-grained includes, so that you can be more selective about which pages will include them. For a more detailed explanation of how ASP processes includes, see "The Implications of ASP #include."
- Are you using global variables?
Global variables increase the namespace used by ASP to retrieve values from memory. Variables declared within subroutines or functions are faster. For more information, see "25+ ASP Tips to Improve Performance and Style."
- Does your script excessively intersperse ASP and HTML?
Keep blocks of ASP server-side script together, rather than switching back and forth between server-side and client-side code. This switching usually happens when concatenating HTML with simple values from ASP, as when you are writing out an HTML table:
<% For iCtr = 1 to 10 %><TR><TD>Counting ... <%= iCtr %> <% Next %> You can improve the code's by making it a single script block: <% For iCtr = 1 to 10 Response.Write "<tr><td>Counting " & iCtr & "" Next %> This technique has shown measurable and significant performance improvements.
- Are you buffering output?
- Are you using Session state?
- Have you disabled Session state if you aren't using it?
- Are you using third-party components designed for ASP?
- Are you using some form of caching for data?
- Are you using CDO?
- "Collaboration Data Objects Roadmap"
- Q195683 "INFO: Relationship between 1.x CDO Libraries and CDOSYS.DLL"
- Q177850 "INFO: What is the Difference Between CDO 1.2 and CDONTS?"
- Are you redimensioning arrays?
- Are you using multiple languages on a page?
- Are you checking Response.IsClientConnected before processing long routines?
- Are you using Server.MapPath unnecessarily?
- Are you parsing strings?
- Are you using the same object many times?
Component Performance
- Are you storing your objects in Session or Application scope?
Storing references to objects in ASP's Session or Application scope will cause many performance and scalability issues if those objects aren't designed to be shared across threads or activities. Only agile components -- or, in Windows 2000, components marked Neutral -- can be referenced in Session or Application variables with direct access by client threads. Components are considered agile if they are marked Both and aggregate the free-threaded marshaler (FTM). See "Agility in Server Components" for a discussion on how to aggregate the FTM. Components with other threading models impose restrictions when stored in Session or Application scope.
As mentioned earlier, Visual Basic or other STA components should only be used within page scope. Note that solidly written Visual Basic components can perform extremely well if you follow this rule. Single- and Free-threaded components are not recommended, because of security issues and expensive proxy/stubs that get created when marshaling across apartment boundaries.
See the following articles for additional information:
- "ASP Component Guidelines"
- Q243544 "INFO: Component Threading Model Summary Under ASP"
- Q150777 "INFO: Descriptions and Workings of OLE Threading Models"
- Are you concatenating strings in components?
Use fixed-length strings in Visual Basic for string concatenation. Don't just keep adding to a string, or you'll reallocate it multiple times -- and reallocation is expensive. Bad:
Public Function BadConcatenation() As String
Dim intLoop As Integer
Dim strTemp As String 'this will be expensive
For intLoop = 1 To 1000
strTemp = strTemp & "<tr><td>Counting "
strTemp = strTemp & CStr(i)
strTemp = strTemp & ""
Next intLoop
BadConcatenation = strTemp
End Function
Good:
Public Function GoodConcatenation() As String
Dim intCtr As Integer
Dim intLoop As Integer
Dim strTemp As String * 32000 'this improves performance
intCtr = 1
For intLoop = 1 To 1000
Mid(strTemp, intCtr) = "<tr><td>Counting "
intCtr = intCtr + 17
Mid(strTemp, intCtr) = CStr(intLoop)
intCtr = intCtr + Len(CStr(intLoop))
Mid(strTemp, intCtr) = ""
intCtr = intCtr + 10
Next intLoop
GoodConcatenation = strTemp
End Function
Several large-scale sites found major performance gains from this tip alone. If you've got extensive string concatenation in your application, put this technique high on your priority list. Experiment with different string sizes, and test to see which size will work best for your particular routine.
- Are you using SQL Server for your middle-tier cache?
- Are you using transactions when you don't need to?
- "Scalable Design Patterns"
- "Simplify MTS Apps with a Design Pattern"
- "FMStocks Application: Start Here"
- Are you calling SetComplete/SetAbort in each method?
- Are you creating child MTS components with CreateInstance?
- Are your components in Server packages or Library packages?
- Are you crossing processes effectively?
- Are you using remote components?
- Do your client and server machines use the same protocols in DCOM?
- Move TCP/IP to the top when you can.
- Remove protocols that you don't need.
- Any changes to the protocol list require a reboot.
- Are you using ASP built-in objects from a remote component?
- Are you using Visual Basic?
- Use Visual Basic 6.0 Service Pack 3.
- Set Unattended Execution and Retain in Memory in your projects (See Figure below). See Q186273 "BUG: AV Running VB-Built Component in Multi-Threaded Environment" for additional information.
- Read Q243548 "INFO: Design Guidelines for VB Components Under ASP" to avoid many common development mistakes.
- Are you using MFC?
- Are you using Java?
- Are you waiting for stuff that could be done asynchronously?
- Q173339 "HOWTO: Use MSMQ from an ASP Page"
- Q181839 "Mqasp.exe MSMQ Basic Queue Operations Using IIS/ASP"
- Q243546 "PRB: ASP Does Not Support Events"
- Are you looping through large datasets in the middle tier?
Data Access Performance
- Are you using indexes in your database?
Indexes provide immediate impact on your application's performance. Poor indexes will slow your application to a crawl, while good indexes will help optimize your application's performance. For information on tuning indexes, see "Top Ten Tips: Accessing SQL Through ADO and ASP." or SQL Books Online.
- Are you calling stored procedures rather than dynamic SQL?
Using stored procedures prevents your database from having to recompile your SQL statements repeatedly. Use stored procedures or parameterized SQL strings.
- Are you returning just the required data?
Check your SELECT statements to ensure that you're returning only the required columns and only the necessary rows. If you have queries that can potentially return a lot of records, consider paging through your recordsets. See the following articles for more information:
- "Recordset Paging with ADO 2.0"
- "Ad Hoc Web Reporting with ADO 2.0"
- "6 Ways to Boost ADO Application Performance"
- Are you using DAO or RDO?
Remote Data Objects (RDO) and Data Access Objects (DAO) are intended for a single-client application process, and weren't tested for the Web. ADO is designed and tested for Web use. Which version of MDAC are you using?
Updated versions of MDAC provide improved reliability and performance. You should be using MDAC version 2.1 Service Pack 2 or later. MDAC 2.5 is recommended, because it's the most stable and it's been tested extensively. If you don't know which version you have on your box, you can grab the Component Checker tool from http://www.microsoft.com/data/ .
- Are you following MDAC best practices?
See "Improve MDAC Application Performance."
- Are you pooling connections?
Pooling allows you to reuse the effort of connecting to a database. ODBC Connection pooling is on by default in MDAC 2.0. In MDAC 2.1 or later, OleDb Session pooling is the default. Remember that in order for pooling to work, the user name, password, and resource in the connection string need to match (it's a byte-by-byte comparison).
See the following articles for additional information on pooling:
- "Pooling in the Microsoft Data Access Components"
- Q169470 "INFO: Frequently Asked Questions About ODBC Connection Pooling"
- Q187874 "CnPool.exe Test Connection Pooling with Tempdb Objects"
- Q191572 "INFO: Connection Pool Management by ADO Objects Called From ASP"
- Are you storing ADO connection in Session or Application scope?
This defeats the purpose of connection pooling and creates resource contention. Create connection at page scope or within the functions that need them, and set the connections to nothing to free the connection back to the pool.
Are you explicitly closing Recordset and Connection variables?
Recordsets need to be closed if they are going to be reused (but reusing recordsets is discouraged). Closing Connection variables as soon as you can releases them back to the pool, so that they can be pooled for reuse. It is always good practice to explicitly close your object variables.
- Are you reusing Recordset and Command variables?
Create new Recordset and Command variables rather than reusing existing ones. This won't necessarily improve your application's performance but it will make your application more reliable and easier to maintain. See Q197449 "PRB: Problems Reusing ADO Command Object on Multiple Recordsets" for more information.
- Are you disconnecting the recordsets?
Disconnecting recordsets frees the Connection object back to the pool, allowing the Connection to be closed and reused sooner. Are you using the right cursor and lock-type for the job?
Use "Firehose" (forward-only, read-only) cursors when you need to make a single pass through the data. Firehose cursors, the default in ADO, provide the fastest performance and have the least amount of overhead. See ADO documentation for more information on cursor and lock types.
- Are you using DSN-less connections?
In general, DSN-less connections are faster than System DSNs (data source names), which are faster than File DSNs. Are you using Access?
Microsoft Access is a file-based database, so don't expect it to perform well with concurrent users under IIS. Are you using SQL Server?
Use SQL Server 7.0. SQL Server 7.0 is superior to earlier versions of SQL Server, and provides row-level locking, as well as other performance benefits. You've heard SQL scales -- but for proof, see http://www.tpc.org/ , and read Jim Gray's paper at http://research.microsoft.com/scalable/ .
- Are you using TCP/IP for your network library?
Use TCP/IP for your network library for performance and scalability.
- Are you using the OLEDB SQL provider?
The SQLOLEDB, the SQL provider, is recommended over MSDASQL, the OLEDB provider for ODBC for performance and reliability. Are you using Oracle?
Oracle performance really depends on a combination of the right MDAC bits with the correct Oracle client patches. If you're using Oracle, take the time to read the following articles:
- "Microsoft OLE DB Provider for Oracle: Tips, Tricks, and Traps"
- "Fitch & Mather Stocks: Data Access Layer for Oracle 8"
If you're using Oracle and MTS, be sure to review the following articles:
- Q193893 "INFO: Using Oracle Databases with Microsoft Transaction Server"
- Q191168 "INFO: Failed to Enlist on Calling Object's Transaction"
- Are you using the same database for reporting and transactions?
Many large Web sites maintain separate databases for read-only and transactional data as an effective way to boost performance. This has the added benefit of allowing you to design your database schema to be optimized for reporting.
IIS Settings
- Is ASP debugging enabled?
Check the ISM. If ASP debugging is enabled, the application is locked down to a single thread of execution. See Q216580 "PRB: Blocking/Serialization When Using InProc Component (DLL) from ASP."
- Is ASP configured to have enough threads/script engines?
Read "The Art and Science of Web Server Tuning with Internet Information Services 5.0" and "Navigating the Maze of Settings for Web Server Performance Optimization."
- Are you using SSL?
Secure Sockets Layer (SSL) is expensive in terms of bandwidth and CPU usage. If you're using SSL, it's because of security needs. Your best bet is to restrict SSL usage to where you need it, and keep the pages simple.
Stress Testing
It's a common misconception that performance equals scalability. Performance for ASP means the rate at which pages can be served. Scalability is measured by how much performance degrades under additional load. To put these terms in perspective, your ASP application may perform well with 10 users, but does not scale to 1000 users, because performance becomes unacceptable. Use stress testing to measure the performance and scalability of your ASP application. For more information on scalability of WinDNA applications, see "A Blueprint for Building Web Sites Using the Microsoft Windows DNA Platform."
- What are your performance expectations?
Performance can be measured in terms of the number of ASP requests per second that your servers can handle. Using ASP's performance counter, ASP Requests Per Second, you can set benchmarks to measure against.
- Are you trying to stress test with a browser?
Stress testing with a browser may be throwing off your results. As mentioned earlier, ASP will serialize concurrent requests from the same Session. For example, if Cookies are enabled and you're hitting the server from one machine, then those requests will serialize. Use the Web Application Stress tool (WAS -- formerly known as Homer). For an introduction to WAS, see "I Can't Stress It Enough -- Load Test Your ASP Application."
- Have you performed end-to-end testing?
While it's important and feasible to stress test your database, components, and ASP layers separately, end to end testing is how you'll find your application's real bottlenecks.
Conclusion
Reviewing ASP applications for performance means taking a look at several things. By breaking the application down into its various layers, you can build a framework for analyzing performance issues. While there are many guidelines and recommendations, nothing replaces stress testing your application and using sound judgment.
J.D. Meier was born and reared on the U.S. East Coast. Since heeding Horace Greeley's advice, he has worked as a Developer Support engineer specializing in server-side components and Windows DNA applications involving MTS and ASP technology.
