الأحد، 26 أبريل 2009

ASP Tips to Improve Performance and Style

ASP Tips to Improve Performance and Style

Cache Data and HTML on the Web Server's Disks
Sometimes, you may have too much data to cache in memory. "Too much" is a judgment call; it depends on how much memory you want to consume, as well as the number of items to cache and the frequency of which these items will be retrieved. In any case, if you have too much data for in-memory caching, consider caching data in text or XML files on the Web servers' hard disks. You can combine caching data on disks and in memory to build the optimum caching strategy for your site.
Note that when measuring the performance of a single ASP page, retrieving data on disk may not always be faster than retrieving the data from a database. But caching reduces load on the database and on the network. Under high loads, this will greatly improve overall throughput. Caching can be very effective when caching the results of an expensive query, such as a multitable join or a complex stored procedure, or caching large result sets. As always, test competing schemes.
ASP and COM provide several tools for building disk-based caching schemes. The ADO recordset Save() and Open() functions save and load recordsets from disk. You could use these methods to rewrite the sample code from the Application data caching tip, above, substituting a Save() to file for the code that writes to the Application object.
There are a few other components that work with files:
  • Scripting.FileSystemObject allows you to create, read, and write files.
  • MSXML, the Microsoft® XML parser that comes with Internet Explorer, supports saving and loading XML documents.
  • The LookupTable object (sample, used on MSN) is a great choice for loading simple lists from disk.
Finally, consider caching the presentation of data on disk, rather than the data itself. Prerendered HTML can be stored as an .htm or .asp file on disk; hyperlinks can point to these files directly. You can automate the process of generating HTML using commercial tools such as XBuilder, or Microsoft® SQL Server™ Internet publishing features. Alternatively, you can #include HTML snippets into an .asp file. You can also read HTML files from disk using the FileSystemObject, or use XML for early rendering.

 

Avoid Caching Non-Agile Components in the Application or Session Objects

While caching data in the Application or Session object can be a good idea, caching COM objects can have serious pitfalls. It is often tempting to stuff frequently-used COM objects into the Application or Session objects. Unfortunately, many COM objects, including all of those written in Visual Basic 6.0 or earlier, can cause serious bottlenecks when stored in the Application or Session objects.
Specifically, any component that is not agile will cause performance bottlenecks when cached in the Session or Application objects. An agile component is a component marked ThreadingModel=Both that aggregates the Free-threaded marshaler (FTM), or is a component that is marked ThreadingModel=Neutral. (The Neutral model is new to Windows® 2000 and COM+.) The following components are not agile:
  • Free-threaded components (unless they aggregate the FTM).
  • Apartment-threaded components.
  • Single-threaded component.
Configured components (Microsoft Transaction Server (MTS)/COM+ library and server packages/applications) are not agile unless they are Neutral-threaded. Apartment-threaded components and other non-agile components work best at page scope (that is, they are created and destroyed on a single ASP page).
In IIS 4.0, a component marked ThreadingModel=Both was considered agile. In IIS 5.0, that is no longer sufficient. The component must not only be marked Both, it must also aggregate the FTM. Agility in Server Components describes how to make C++ components written with the Active Template Library aggregate the FTM. Be aware that if your component caches interface pointers, those pointers must themselves be agile, or must be stored in the COM Global Interface Table (GIT). If you can't recompile a Both-threaded component to aggregate the FTM, you can mark the component as ThreadingModel=Neutral. Alternatively, if you don't want IIS performing the agility check (thus, you want to allow non-agile components to be stored at Application or Session scope), you can set AspTrackThreadingModel to True in the metabase. Changing AspTrackThreadingModel is not recommended.
IIS 5.0 will throw an error if you attempt to store a non-agile component created with Server.CreateObject in the Application object. You can work around this by using <object runat=server scope=application ...> in Global.asa, but this is not recommended, as it leads to marshaling and serialization, as explained below.
What goes wrong if you cache non-agile components? A non-agile component cached in the Session object will "lock down" the Session to an ASP worker thread. ASP maintains a pool of worker threads that services requests. Normally, a new request is handled by the first-available worker thread. If a Session is locked down to a thread, then the request has to wait for its associated thread to become available. Here's an analogy that might help: you go to a supermarket, select some groceries, and pay for them at checkout stand #3. Thereafter, whenever you pay for groceries at that supermarket, you always have to pay for them at stand #3, even though other checkout stands might have shorter or even empty lines.
Storing non-agile components at Application scope has an even worse effect on performance. ASP has to create a special thread to run non-agile, Application-scoped components. This has two consequences: all calls have to be marshaled to this thread and all calls are serialized. Marshaling means that the parameters have to be stored in a shared area of memory; an expensive context switch is made to the special thread; the component's method is executed; the results are marshaled into a shared area; and another expensive context switch reverts control to the original thread. Serialization means that all methods are run one at a time. It is not possible for two different ASP worker threads to be executing methods on the shared component simultaneously. This kills concurrency, especially on multiprocessor machines. Worse still, all non-agile Application-scoped components share one thread (the "Host STA"), so the effects of serialization are even more marked.
Confused? Here are some general rules. If you are writing objects in Visual Basic (6.0) or earlier, do not cache them in the Application or Session objects. If you don't know an object's threading model, don't cache it. Instead of caching non-agile objects, you should create and release them on each page. The objects will run directly on the ASP worker thread, so there will be no marshaling or serialization. Performance will be adequate if the COM objects are running on the IIS box, and if they don't take a long time to initialize and destroy. Note that Single-threaded objects should not be used this way. Be careful—VB can create Single-threaded objects! If you have to use Single-threaded objects this way (such as a Microsoft Excel spreadsheet) don't expect high throughput.
ADO recordsets can be safely cached when ADO is marked as Free-threaded. To mark ADO as Free-threaded, use the Makfre15.bat file, which is typically located in the directory \\Program Files\Common\System\ADO.
Warning:   ADO should not be marked as Free-threaded if you are using Microsoft Access as your database. The ADO recordset must also be disconnected In general, if you cannot control the ADO configuration on your site (for example, you are an independent software vendor [ISV] who sells a Web application to customers who will manage their own configurations), you are probably better off not caching recordsets.
Dictionary components are also agile objects. The LookupTable loads its data from a data file and is useful for combo-box data as well as configuration information. The PageCache object from Duwamish Books provides dictionary semantics, as does the Caprock Dictionary. These objects, or derivatives thereof, can form the basis of an effective caching strategy. Note that the Scripting.Dictionary object is NOT agile, and should not be stored at Application or Session scope.


Invite your mail contacts to join your friends list with Windows Live Spaces. It's easy! Try it!

ASP Tips to Improve Performance and Style 1

ASP Tips to Improve Performance and Style
 
Cache Frequently-Used Data on the Web Server
 
A typical ASP page retrieves data from a back-end data store, then paints the results into Hypertext Markup Language (HTML). Regardless of the speed of your database, retrieving data from memory is a lot faster than retrieving data from a back-end data store. Reading data from a local hard disk is also usually faster than retrieving data from a database. Therefore, you can usually increase performance by caching data on the Web server, either in memory or on disk.
Caching is a classic space-for-time tradeoff. If you cache the right stuff, you can see impressive boosts in performance. For a cache to be effective, it must hold data that is reused frequently, and that data must be (moderately) expensive to recompute. A cache full of stale data is a waste of memory.
Data that does not change frequently makes good candidates for caching, because you don't have to worry about synchronizing the data with the database over time. Combo-box lists, reference tables, DHTML scraps, Extensible Markup Language (XML) strings, menu items, and site configuration variables (including data source names (DSNs), Internet Protocol (IP) addresses, and Web paths) are good candidates for caching. Note that you can cache the presentation of data rather than the data itself. If an ASP page changes infrequently and is expensive to cache (for example, your entire product catalog), consider pregenerating the HTML, rather than repainting it for every request.
Where should data be cached, and what are some caching strategies? Data is often cached either in the Web server's memory or on the Web server's disks. The next two tips cover these options.
 
Cache Frequently-Used Data in the Application or Session Objects
 
The ASP Application and Session objects provide convenient containers for caching data in memory. You can assign data to both Application and Session objects, and this data will remain in memory between HTTP calls. Session data is stored per user, while Application data is shared between all users.
At what point do you load data into the Application or Session? Often, the data is loaded when an Application or Session starts. To load data during Application or Session startup, add appropriate code to Application_OnStart() or Session_OnStart(), respectively. These functions should be located in Global.asa; if they are not, you can add these functions. You can also load the data when it's first needed. To do this, add some code (or write a reusable script function) to your ASP page that checks for the existence of the data and loads the data if it's not there. This is an example of the classic performance technique known as lazy evaluation-don't calculate something until you know you need it. An example:
***
<% Function GetEmploymentStatusList    Dim d    d = Application("EmploymentStatusList")    If d = "" Then       ' FetchEmploymentStatusList function (not shown)       ' fetches data from DB, returns an Array       d = FetchEmploymentStatusList()       Application("EmploymentStatusList") = d    End If    GetEmploymentStatusList = d End Function %> 
***
Similar functions could be written for each chunk of data needed.
In what format should the data be stored? Any variant type can be stored, since all script variables are variants. For instance, you can store strings, integers, or arrays. Often, you'll be storing the contents of an ADO recordset in one of these variable types. To get data out of an ADO recordset, you can manually copy the data into VBScript variables, one field at a time. It's faster and easier to use one of the ADO recordset persistence functions GetRows(), GetString() or Save() (ADO 2.5). Full details are beyond the scope of this article, but here's a function that demonstrates using GetRows() to return an array of recordset data:
*******
' Get Recordset, return as an Array Function FetchEmploymentStatusList    Dim rs    Set rs = CreateObject("ADODB.Recordset")    rs.Open "select StatusName, StatusID from EmployeeStatus", _            "dsn=employees;uid=sa;pwd=;"    FetchEmploymentStatusList = rs.GetRows() " Return data as an Array    rs.Close    Set rs = Nothing End Function
 
*********** 
A further refinement of the above might be to cache the HTML for the list, rather than the array. Here's a simple sample:
*******
' Get Recordset, return as HTML Option list Function FetchEmploymentStatusList    Dim rs, fldName, s    Set rs = CreateObject("ADODB.Recordset")    rs.Open "select StatusName, StatusID from EmployeeStatus", _            "dsn=employees;uid=sa;pwd=;"    s = "<select name=""EmploymentStatus">" & vbCrLf    Set fldName = rs.Fields("StatusName") ' ADO Field Binding    Do Until rs.EOF      ' Next line violates Don't Do String Concats,      ' but it's OK because we are building a cache      s = s & " <option>" & fldName & "</option>" & vbCrLf      rs.MoveNext    Loop    s = s & "</select>" & vbCrLf    rs.Close    Set rs = Nothing ' See Release Early    FetchEmploymentStatusList = s ' Return data as a String End Function
 
************* 
Under the right conditions, you can cache ADO recordsets themselves in Application or Session scope. There are two caveats:
If you cannot guarantee that these two requirements will be met, do not cache ADO recordsets. In the Non-Agile Components and Don't Cache Connections tips below, we discuss the dangers of storing COM objects in Application or Session scope.
When you store data in Application or Session scope, the data will remain there until you programmatically change it, the Session expires, or the Web application is restarted. What if the data needs to be updated? To manually force an update of Application data, you can call into an administrator-access-only ASP page that updates the data. Alternatively, you can automatically refresh your data periodically through a function. The following example stores a time stamp with the cached data and refreshes the data after a certain time interval.
*****************
<% ' error handing not shown... Const UPDATE_INTERVAL = 300 ' Refresh interval, in seconds ' Function to return the employment status list Function GetEmploymentStatusList UpdateEmploymentStatus GetEmploymentStatusList = Application("EmploymentStatusList") End Function ' Periodically update the cached data Sub UpdateEmploymentStatusList Dim d, strLastUpdate strLastUpdate = Application("LastUpdate") If (strLastUpdate = "") Or _ (UPDATE_INTERVAL < DateDiff("s", strLastUpdate, Now)) Then ' Note: two or more calls might get in here. This is okay and will simply ' result in a few unnecessary fetches (there is a workaround for this) ' FetchEmploymentStatusList function (not shown) ' fetches data from DB, returns an Array d = FetchEmploymentStatusList() ' Update the Application object. Use Application.Lock() ' to ensure consistent data Application.Lock Application("EmploymentStatusList") = d Application("LastUpdate") = CStr(Now) Application.Unlock End If End Sub
 
*******
For another example, see World's Fastest ListBox with Application Data.
Be aware that caching large arrays in Session or Application objects is not a good idea. Before you can access any element of the array, the semantics of the scripting languages require that a temporary copy of the entire array be made. For example, if you cache a 100,000-element array of strings that maps U.S. zip codes to local weather stations in the Application object, ASP must first copy all 100,000 weather stations into a temporary array before it can extract just one string. In this case, it would be much better to build a custom component with a custom method to store the weather stations—or to use one of the dictionary components.
One more comment in the spirit of not throwing out the baby with the bath water: Arrays provide fast lookup and storage of key-data pairs that are contiguous in memory. Indexing a dictionary is slower than indexing an array. You should choose the data structure that offers the best performance for your situation.
 
 
 


Invite your mail contacts to join your friends list with Windows Live Spaces. It's easy! Try it!