Enhancing Performance in ASP - Part I

 

ASP developers are always striving to acquire higher levels of performance and scalability in their projects.  Fortunately, there are many books and web sites that provide excellent recommendations on achieving this goal.  However, these recommendations are often based on conclusions drawn from the mechanics of how the ASP platform works without any quantitative measurement of actual performance gains.  And since these recommendations often require more complex coding procedures and reduce the readability of the code, developers are left with the task of weighing these costs against the added performance of their ASP applications without seeing actual performance metrics.

 

In this two-part article, I will present some performance test results to help developers in deciding if a particular practice is not only worthwhile for future projects, but whether they should consider updating older projects as well.  In Part One, I will review some of the fundamental issues of ASP development.  In Part Two, I will cover optimizing ADO functions and compare these results with ASP pages calling VB COM objects performing the same ADO functions.  The results are eye-opening and in some cases downright surprising.

 

In this article we will answer the following questions:

·         What is the most effective way to write ASP generated content to the response stream?

·         Should I enable buffering?

·         Should I be concerned about adding comments to my ASP code?

·         Should I explicitly set the default language for the page?

·         Should I disable the Session State if it is not required?

·         Should I place my script logic in sub and function blocks?

·         What is the impact of using include files?

·         How much overhead is imposed when implementing error handling?

·         Is there a penalty for setting a transaction context?

 

All of the tests were performed with Microsoft’s Web Application Stress Tool (WAST), a free utility that can be found at http://webtool.rte.microsoft.com/.  I created a simple test script in WAST to repeatedly call the test ASP pages described below (over 70,000 times each).  The response times are based on the average Total Time to Last Byte (TTLB), that is, the amount of time from the initial request to when the tool receives the last byte of data from the server.  Our test server is a Pentium 166 with 196 MB of memory; the client machine is a Pentium 450 with 256 MB of memory.  You may be thinking that these machines are not exactly high performance wonders, but it is important to remember that we are not testing server capacity.  We are simply testing how long the server will take to process one page at a time.  No other work was being done on these machines during testing.  The WAST test script, the test report and all the test ASP pages have been included in ZIP file for you to review and test on your own.

What is the most effective way to write ASP generated content to the response stream?

One of the primary reasons for using ASP is to generate dynamic content on the server.  So the obvious starting point in our testing is to determine what is the most appropriate way to send that dynamic content to the response stream.  There are two basic options with some variations: using inline ASP tags and using the Response.Write statement.

 

To test these variations, I have created a simple ASP page that defines a number of variables and then inserts their values into a table.  While the page is simple and not very practical, it will allow us to isolate and test individual issues.

Using the ASP inline tags

The first test involves using the inline ASP tag, <%= x %>, where x is a variable with some value. This approach is by far the easiest to implement and it leaves the HTML portions of the page in an easy to read and maintainable format.

 

<% OPTION EXPLICIT

Dim FirstName

Dim LastName

Dim MiddleInitial

Dim Address

Dim City

Dim State

Dim PhoneNumber

Dim FaxNumber

Dim EMail

Dim BirthDate

 

FirstName = "John"

MiddleInitial = "Q"

LastName = "Public"

Address = "100 Main Street"

City = "New York"

State = "NY"

PhoneNumber = "1-212-555-1234"

FaxNumber = "1-212-555-1234"

EMail = "john@public.com"

BirthDate = "1/1/1950"

%>

<html>

<head>

       <title>Response Test</title>

</head>

<body>

<h1>Response Test</h1>

<table>

<tr><td><b>First Name:</b></td><td><%= FirstName %></td></tr>

<tr><td><b>Middle Initial:</b></td><td><%= MiddleInitial %></td></tr>

<tr><td><b>Last Name:</b></td><td><%= LastName %></td></tr>

<tr><td><b>Address:</b></td><td><%= Address %></td></tr>

<tr><td><b>City:</b></td><td><%= City %></td></tr>

<tr><td><b>State:</b></td><td><%= State %></td></tr>

<tr><td><b>Phone Number:</b></td><td><%= PhoneNumber %></td></tr>

<tr><td><b>Fax Number:</b></td><td><%= FaxNumber %></td></tr>

<tr><td><b>EMail:</b></td><td><%= EMail %></td></tr>

<tr><td><b>Birth Date:</b></td><td><%= BirthDate %></td></tr>

</table>

</body>

</html>

Complete code for /app1/response1.asp

 

Previous Best:    

8.28

msec/page

 

 

Using Response.Write statements for each line of HTML

Many best practice documents recommend avoiding the previous approach because of an issue known as context switching.  This occurs when the web server has to switch between delivering pure HTML and processing script while outputting the page, imposing latency in the processing of the page.  When most programmers hear this, their first reaction is to wrap each line of the original HTML in Response.Write functions.

 

Response.Write("<html>")

Response.Write("<head>")

Response.Write("       <title>Response Test</title>")

Response.Write("</head>")

Response.Write("<body>")

Response.Write("<h1>Response Test</h1>")

Response.Write("<table>")

Response.Write("<tr><td><b>First Name:</b></td><td>" & FirstName & "</td></tr>")

Response.Write("<tr><td><b>Middle Initial:</b></td><td>" & MiddleInitial & "</td></tr>")

Snippet from /app1/response2.asp

 

Previous Best:    

8.28

msec/page

 

 

Response Time:

8.08

msec/page

 

 

Difference:

-0.20

msec

2.4%

decrease

 

Here we see a surprisingly small gain in performance over the inline version, probably because the page is loading the server with a flurry of function calls.  The big disadvantage, however, is that the script code has become more tedious since the HTML is now embedded in the script and is now more difficult to read and maintain.

Using wrapper functions

One of the most frustrating discoveries made when one tries the previous approach is that the Response.Write function does not place a CRLF (carriage return – line feed) at the end of the line.  Therefore, what was once carefully laid out HTML now appears in a single endless line when one views the source code in the browser.  The horror continues when one next discovers that there is no sister Writeln function in the Response object.  So, the obvious reaction is to create a wrapper function for the Response.Write function to append a CRLF to every line.

 

writeCR("<tr><td><b>First Name:</b></td><td>" & FirstName & "</td></tr>")

SUB writeCR(str)

       Response.Write(str & vbCRLF)

END SUB

Snippet from /app1/response4.asp

 

Previous Best:    

8.08

msec/page

 

 

Response Time:

10.11

msec/page

 

 

Difference:

2.03

msec

25.1%

increase

 

YIKES! Of course, since this approach effectively doubles the number of function calls, the effect on performance is significant and should be avoided at all costs.  The irony of this is that the CRLFs are also adding an additional two bytes per line to the response stream that the browser does not need to render the page.  All well-formatted HTML does is make it easy for your competitors to read your HTML source and understand your design.

Concatenating consecutive Response.Write into a single statement

Disregarding our last test with wrapper functions, the next logical step is to take all the strings from the separate Response.Write statements and concatenate them into a single statement, thereby reducing the number of function calls and greatly improving the performance of the page.

 

Response.Write("<html>" & _

       "<head>" & _

       "<title>Response Test</title>" & _

       "</head>" & _

       "<body>" & _

       "<h1>Response Test</h1>" & _

       "<table>" & _

       "<tr><td><b>First Name:</b></td><td>" & FirstName & "</td></tr>" & _

       "<tr><td><b>Birth Date:</b></td><td>" & BirthDate & "</td></tr>" & _

       "</table>" & _

       "</body>" & _

       "</html>")

Snippet from /app1/response3.asp

 

Previous Best:    

8.08

msec/page

 

 

Response Time:

7.05

msec/page

 

 

Difference:

-1.03

msec

12.7%

decrease

 

This isthe most optimal configuration so far.

Concatenating consecutive Response.Write into a single statement with CRLF added to the end of each line

For those who are purist about what their source code look like in the browser, I inserted some carriage returns using the vbCRLF constant to the end of each line in the previous test and ran it again.

 

Response.Write("<html>" & vbCRLF & _

       "<head>" & vbCRLF & _

       "       <title>Response Test</title>" & vbCRLF & _

       "</head>" & vbCRLF & _

Snippet from /app1/response5.asp

 

Previous Best:    

7.05

msec/page

 

 

Response Time:

7.63

msec/page

 

 

Difference:

0.58

msec

8.5%

increase

 

The result is a small decrease in performance probably due to the extra concatenations and the additional character throughput.

Review and Observations

Several rules can be derived from the previous tests concerning ASP output:

 

·         Avoid excessive use of Inline ASP.

·         Always concatenate consecutive Response.Write statements into a single statement.

·         Never use wrapper functions around the Response.Write statement to append CRLFs.

·         If you must format your HTML output, append CRLFs directly within the Response.Write statements.

Should I enable buffering?

When buffering is enabled, the server will not send the page contents to the browser until the entire page is processed.  Buffering can be enabled in two ways -- through function calls within the ASP page or through server settings.  Each scenario is tested below.

Enabling Buffering through Script

By including Response.Buffer=True at the top of your ASP script, IIS will buffer the contents of that page.

 

<% OPTION EXPLICIT

Response.Buffer = true

Dim FirstName

Snippet from /app1/buffer__1.asp

 

Previous Best:    

7.05

msec/page

 

 

Response Time:

6.08

msec/page

 

 

Difference:

-0.97

msec

13.7%

decrease

 

A big performance boost.  But wait, it gets better.

Enabling Buffering through Server Configuration

Although buffering will be enabled by default in IIS 5.0, you must still do it manually in IIS 4.0. To do this, go to Properties dialog for the site. From there, select the configuration button on the Home Directory tab.  Then select “enable buffering” under “App options”.    For this test, the Response.Buffer statement was removed from the script.

 

Previous Best:    

7.05

msec/page

 

 

Response Time:

5.57

msec/page

 

 

Difference:

-1.48

msec

21.0%

decrease

 

This is by far the fastest response we have seen yet with a 21% decrease in response time from our previous best.  From this point on through the rest of the testing, we will use this response time as our benchmark.

Review and Observations

Buffering is an excellent way to increase performance, so it makes a lot of sense to make buffering the default setting for the server  .  If for some reason a page will not behave correctly with buffering, simply use Response.Buffer=False command.  The one drawback of buffering is that the user does not see anything from the server until the entire page is processed.  There fore, on complex pages it is a good idea to occasionally call Response.Flush to update the user during the page processing.

 

Let’s add the following to our list of rules:

 

·         Always enable buffering through server settings.

Should I be concerned about adding comments to my ASP code?

Most HTML developers know that including HTML comments is a bad idea because, first, they increase the size of the data transfer, and second, they only provide information to other developers on the organization of your page.  But what about comments in ASP pages?  They never leave the server, but they do increase the size of the page that must be parsed by ASP.

 

In this test, we added 20 lines of comments at 80 characters each for a total of 1600 characters.

 

<% OPTION EXPLICIT

'-------------------------------------------------------------------------------

… 20 lines …

'-------------------------------------------------------------------------------

Dim FirstName

Snippet from /app2/comment_1.asp

 

Benchmark:        

5.57

msec/page

 

 

Response Time:

5.58

msec/page

 

 

Difference:

0.01

msec

0.1%

increase

 

This test result is very surprising. Although the comments more than doubled the size of the file, their presence has little effect on the response time.  Therefore, let’s add the following rule:

 

·         When used in moderation, ASP Comments have little or no impact on performance.

Should I explicitly set the default language for the page?

IIS is set to process VBScript by default, however, most examples that I have seen explicitly set the language to VBScript with the declarative <%@LANGUAGE=VBSCRIPT%> anyway.  Our next test examines the effect of the presence of this statement on performance.

 

<%@ LANGUAGE=VBSCRIPT %>

<% OPTION EXPLICIT

Dim FirstName

Snippet from /app2/language1.asp

 

Benchmark:        

5.57

msec/page

 

 

Response Time:

5.64

msec/page

 

 

Difference:

0.07

msec

1.2%

increase

 

As you can see there is a slight performance hit for including the language declarative.  Therefore:

 

·         Set the server’s default language configuration to match the language used on the site.

·         Do not set the language declarative unless you are using the non-default language.

Should I disable the Session State if it is not required on a page?

There are lots of reasons for avoiding the use of IIS’s Session context but that could be an article itself.  However, the question we will try to answer is whether there is a performance gain by disabling the session context when a page does not require it.  The theory is that there should be because the session context will not have to be instantiated with the page.

 

As with buffering, session state can be configured in two ways: through script and through server settings.

Disable Session Context through Script

For this test, to disable the session context in the page, I added the session state declarative.

 

<%@ ENABLESESSIONSTATE = FALSE %>

<% OPTION EXPLICIT

Dim FirstName

Snippet from /app2/session_1.asp

 

Benchmark:        

5.57

Msec/page

 

 

Response Time:

5.46

Msec/page

 

 

Difference:

-0.11

Msec

2.0%

decrease

 

This results in a nice little boost for very little effort.  Let’s look at the second part.

Disable Session Context through Server Configuration

To disable the session context on the server, go to Properties dialog for the site. From there, select the configuration button on the Home Directory tab.  Then unselect “enable session state” under “App options”.    We ran this test without ENABLESESSIONSTATE declarative.

 

Benchmark:        

5.57

Msec/page

 

 

Response Time:

5.14

Msec/page

 

 

Difference:

-0.43

Msec

7.7%

decrease

 

Another significant increase in performance.  So as a result our rule is:

 

·         Always disable Session state at the page or application level when not required.

Does using Option Explicit make a substantial difference in performance?

Setting Option Explicit at the top of an ASP page requires that all variables be declared on the page before they are used. This is often recommended for two reasons. First, the application can handle variable access quicker. Second, it prevents you from accidentally misnaming variables.  For this test we will remove the Option Explicit reference and the Dim Statements for the variables.

 

 

Benchmark:        

5.57

msec/page

 

 

Response Time:

6.12

msec/page

 

 

Difference:

0.55

msec

9.8%

increase

 

Despite the fact that a number of lines of code were removed from the page, there was still increase in response time.  Although using Option explicit can be a bit tedious at times, its presence has a marked effect on performance. Therefore, we can add the following rule:

 

·         Always Use Option Explicit in VBScript.

Should I place my script logic in sub and function blocks?

Using functions and subroutines is a great way to organize and maintain code, especially if that block of code is used multiple times on a page.  The disadvantage is that an extra function call is imposed on the system to do the same work.  Another issue with subs and functions is variable scope.  In theory, variables assigned within a function block a more efficient.  Let’s see how these two issues come into play.

Move the Response.Write statement to a subroutine

This test simply moves the Response.write statement into a subroutine block.

 

CALL writeTable()

 

SUB writeTable()

       Response.Write("<html>" & _

              "<head>" & _

              "<tr><td><b>EMail:</b></td><td>" & EMail & "</td></tr>" & _

              "<tr><td><b>Birth Date:</b></td><td>" & BirthDate & "</td></tr>" & _

              "</table>" & _

              "</body>" & _

              "</html>")

END SUB

Snippet from /app2/function1.asp

 

Benchmark:        

5.57

msec/page

 

 

Response Time:

6.02

msec/page

 

 

Difference:

0.45

msec

8.1%

increase

 

As expected, the subroutine call has imposed an additional burden on the page.

Move all the script to a sub

In this test, the Response.write statement is moved into a subroutine block along with the variable declarations. 

 

<% OPTION EXPLICIT

CALL writeTable()

 

SUB writeTable()

       Dim FirstName

       Dim BirthDate

      

       FirstName = "John"

       BirthDate = "1/1/1950"

      

       Response.Write("<html>" & _

              "<head>" & _

              "       <title>Response Test</title>" & _

              "</head>" & _

              "<body>" & _

              "<h1>Response Test</h1>" & _

              "<table>" & _

              "<tr><td><b>First Name:</b></td><td>" & FirstName & "</td></tr>" & _

              "<tr><td><b>Birth Date:</b></td><td>" & BirthDate & "</td></tr>" & _

              "</table>" & _

              "</body>" & _

              "</html>")

END SUB

Snippet from /app2/function2.asp

 

Benchmark:        

5.57

msec/page

 

 

Response Time:

5.22

msec/page

 

 

Difference:

0.35

msec

6.3%

decrease

 

Very Interesting!  Moving the variables into the function scope actually improved performance over our benchmark, despite the extra function call.  Let’s add the following rules:

·         Encapsulate code into function blocks when the code will be used more than once in a page.

·         Move variables declarations into function scope when appropriate.

What is the impact of using include files?

A powerful feature of ASP programming is the ability to include code from other pages.  Through this feature, programmers can share common functions across multiple pages, making code much easier to maintain.  The drawback, however, is that the server must assemble the page from multiple sources.

 

Below are two tests using Include files.

Include file with Inline Code

In this test, a piece of the original page is moved to an Include file.

 

<% OPTION EXPLICIT

Dim FirstName

Dim BirthDate

 

FirstName = "John"

BirthDate = "1/1/1950"

%>

<!-- #include file="inc1.asp" -->

Snippet from /app2/include_1.asp

 

Benchmark:        

5.57

msec/page

 

 

Response Time:

5.93

msec/page

 

 

Difference:

0.36

msec

6.5%

increase

 

It should be no surprise that there is overhead in using Include files.

Include file with Function Block

Here the code is wrapped in a subroutine in the Include file.  The Include reference is made at the top of the page and the subroutine is called at the appropriate location in the ASP script.

 

<% OPTION EXPLICIT

Dim FirstName

Dim BirthDate

 

FirstName = "John"

BirthDate = "1/1/1950"

 

CALL writeTable()

%>

<!-- #include file="inc2.asp" -->

Snippet from /app2/include_2.asp

 

Benchmark:        

5.57

msec/page

 

 

Response Time:

6.08

msec/page

 

 

Difference:

0.51

msec

9.2%

increase

 

Here the performance is impacted even more when using functions calls.  Therefore,

 

·         Use Include files only when code can be shared across pages.

How much overhead is imposed when implementing error handling?

Error handling is essential for any serious application.  For this test we have simply invoked the error handler by calling the On Error Resume Next function.

 

<% OPTION EXPLICIT

On Error Resume Next

Dim FirstName

Snippet from /app2/error___1.asp

 

Benchmark:        

5.57

msec/page

 

 

Response Time:

5.67

msec/page

 

 

Difference:

0.10

msec

1.8%

increase

 

As you can see, error handling comes with a price.  Therefore, we propose the following:

 

·         Only use error handling when a condition can occur that is outside your ability to test or control. 

 

A prime example is using COM objects that access other resources such as ADO or FileSystem objects.

Is there a penalty for setting a transaction context?

Setting the Transaction context on a page allows the script to rollback operations if an error has occurred. This is set in the page by using the transaction declarative.

 

<%@ TRANSACTION = REQUIRED %>

<% OPTION EXPLICIT

Dim FirstName

Snippet from /app2/transact1.asp

 

Benchmark:        

5.57

msec/page

 

 

Response Time:

13.39

msec/page

 

 

Difference:

7.82

msec

140.4%

increase

 

WHOA!  This is the most dramatic result yet.  So please take heed of the following rule.

 

·         Only use transactions when two or more operations MUST be performed as a unit. 

 

Conclusion

The important lesson in this first article is that little things add up.  To reinforce this issue, I have set up one last test that does several of the seemingly innocent but bad practices we tested previously.  I have included multiple Response.Write statements, disabled buffering, set the default language, removed the Option Explicit reference, and initialized the error handler.

 

<%@ LANGUAGE=VBSCRIPT %>

<%

On Error Resume Next

 

FirstName = "John"

BirthDate = "1/1/1950"

 

Response.Write("<html>")

Response.Write("<head>")

Response.Write("       <title>Response Test</title>")

Response.Write("</head>")

Response.Write("<body>")

Response.Write("<h1>Response Test</h1>")

Response.Write("<table>")

Response.Write("<tr><td><b>First Name:</b></td><td>" & FirstName & "</td></tr>")

Response.Write("<tr><td><b>Birth Date:</b></td><td>" & BirthDate & "</td></tr>")

Response.Write("</table>")

Response.Write("</body>")

Response.Write("</html>")

%>

Snippet from /app2/final___1.asp

 

Benchmark:        

5.57

msec/page

 

 

Response Time:

8.85

msec/page

 

 

Difference:

3.28

msec

58.9%

increase

 

It may sound obvious, but it is important to understand the impact that the code we put into our pages can have on performance.  Small changes on each page can do wonders to increase  response times.  In summary, lets review the all the rules we have developed thus far:

Summary of rules

·         Avoid excessive use of Inline ASP.

·         Always concatenate consecutive Response.Write statements into a single statement.

·         Never use wrapper functions around the Response.Write statement to append CRLFs.

·         If you must format your HTML output, append CRLFs directly within the Response.Write statements.

·         Always enable buffering through server settings.

·         When used in moderation, ASP Comments have little or no impact on performance.

·         Set the server’s default language configuration to match the language used on the site.

·         Do not set the language declarative unless you are using the non-default language.

·         Always Use Option Explicit in VBSCript.

·         Always disable Session state at the page or application level when not required.

·         Use Include files only when code can be shared across pages.

·         Encapsulate code into function blocks when the code will be used more than once in a page.

·         Move variables into function scope when appropriate.

·         Only use error handling when a condition can occur that is out of your ability to test or control. 

·         Only use transactions when two or more operations MUST be performed as a unit. 

 

In review, there are several issues that you can use as general guidelines:

 

·         Avoid redundancy – don’t set properties that are already set by default.

·         Limit the number of function calls.

·         Narrow the scope of your code.

 

Look for Part II of this article where we will explore further issues concerning ADO and COM objects.

 

LINK TO ZIP FILE