Microsoft Visual FoxPro
 
Performance Tuning Tips in Microsoft Visual FoxPro

Erik Svenson, Microsoft Corporation

Introduction

Performance has always been one of the hallmarks of the Microsoftâ FoxPro® database management system, particularly in the database engine. With the introduction of the Visual FoxPro™ relational database development system and its associated object model, enhanced engine, and client/server features, the overall product becomes much more powerful. The downside of all that power, however, is that the product is more complex. So, while developing robust, object-oriented applications that may also use data from remote data stores becomes easier, it also becomes easier to create slow applications.

This document is intended to provide you with practical tips and techniques for improving the performance of your applications. By applying these techniques to your applications appropriately, you can make significant improvements to your user interface and data access performance.

While some of these tips might seem obvious, we expect that the overall collection will provide you with useful information for improving your overall application's performance. And as these techniques evolve, we encourage feedback and additions to help your fellow developers who use Visual FoxPro improve the performance of their applications.

Hardware Configuration

This section describes tips for tuning the operating system so that it runs optimally with Visual FoxPro. In some cases, you should play with some of the disk caching values to get optimal performance to meet your applications' needs.

Microsoft Windows 95 and Windows NT

For the most part, the Microsoft® Windows® 95 and Windows NT® operating systems and Visual FoxPro work extremely well together. The only exception is when you run Visual FoxPro on a Windows NT Server. The Windows NT Server uses far more memory than Visual FoxPro expects, and therefore Visual FoxPro can tend to over-allocate memory for its own uses.

To remedy this, you should use the SYS (3050) system function to adjust Visual FoxPro's memory buffer pool to approximately one third of its default (see the Memory Management section below). For example, you could add the following code to your startup code or your Config.fpw file:

=SYS (3050, VAL (SYS (3050, 1, 0)) / 3)

Local Data Performance

Rushmore and Indexes

Chapter 15 in the Developer's Guide provides an in-depth discussion of Rushmoreä techniques. The following tips are supplementary.

Use Indexes

It is a simple fact that using indexes improves data queries. Indexes in Visual FoxPro (and previous versions) are data structures based on a tree, so lookups into that index table are very fast.

One of the most unique aspects of Visual FoxPro is the support of indexes based on expressions. Most DBMSs allow indexing on one or more fields. Visual FoxPro allows arbitrary expressions to be used in indexes, even user-defined functions.

You should be aware that, as you add more indexes to your table, updates and inserts to the table are slower because Visual FoxPro needs to update each index. In general, you should only index data that is used in filters and joins.

You should avoid using index expressions in the form of FOR <condition> or NOT <condition> as these expressions are not optimizable for the index. For example:

INDEX ON DELETED() TAG DEL

is Rushmore optimizable, but:

INDEX ON NOT DELETED()TAG NOTDEL

is not. As a special case for queries that you don't want deleted records in, using an index like the first example above will speed up operations when you have SET DELETED ON.

If you use a FOR clause in the expression, Visual FoxPro will not use the index for optimization. For example:

INDEX ON ORDNUM FOR DISCOUNT > 10 TAG ORDDISC

Finally, be careful not to use indexes in which only a few discreet values would be used (like logical fields).

When are Queries Rushmore Optimized?

It is important to understand the cases in which queries or filters made against Visual FoxPro tables will be optimized by Rushmore and when they are not, or only partially optimized.

Visual FoxPro uses Rushmore technology to optimize filter conditions by looking for index key expressions that match the left side of the filter expression. Often, developers mistakenly attempt to match the tag name of an index with a filter expression, only to find that Visual FoxPro cannot optimize queries that way. Here is an example of the wrong way to use indexes:

USE CUSTOMER
INDEX ON UPPER(contact) TAG name

SELECT * FROM customer ;
 WHERE contact="BILL" && not optimized. No index on "contact"

Here is the right way to take advantage of an index in a filtered query:

SELECT * FROM customer WHERE UPPER(contact)="BILL" && Optimized!

You can also make use of the SYS(3054) function to determine the level of Rushmore query optimization. SYS(3054) enables or disables the display of Rushmore optimization levels for queries. Issue SYS(3054,1) to display the level of Rushmore optimization; issue SYS(3054, 0) to stop display of the Rushmore optimization level.

Database Engine Performance Features

Use Primary and Candidate Indexes

In FoxPro 2.x, the typical technique for adding records with unique ids was to create an index on your key field and then, when it was time to add a record, run a procedure that used SEEK to see if the id was already used. While this was pretty fast, it was still not optimal.

By using Primary and Candidate keys, inserts (or updates) into Visual FoxPro tables are validated by the database engine for uniqueness, which occurs at a low level and is extremely fast.

International Considerations

Improved Performance with Diacritics

In Visual FoxPro, we've improved the way in which lookups to data containing international characters (characters with diacritic marks like à, ê, ö, etc.) are optimized. If your index expression result is such that the characters can never support diacritic marks (like B, or M), lookups will be faster.

Non-Machine Collate Sequences

There are several reasons why a non-machine collate sequence (such as "General") is slower than the "Machine" collate sequence:

1. Non-machine index keys are twice as large because they contain the diacritic information.

2. Non-machine collation uses many special rules for indexing characters to return proper results.

Because the machine collate sequence is faster, it is usually preferred for joins and seeks, while other collate sequences are perfect for ordering records.

Note Visual FoxPro only utilizes indexes that were created using the current setting of SET COLLATE. So the typical workaround is to have two indexes on your primary search/sort fields:

SET COLLATE TO "GENERAL"
INDEX ON lastname TAG lastname		&& sort index
SET COLLATE TO "MACHINE"
INDEX ON lastname TAG _lastname		&& seek/select index

Now, when you want to do a seek, select, or join on the field "lastname" using the faster "machine" index, just SET COLLATE TO "MACHINE" before performing the seek/select/join. Rushmore will use the index created in the machine collate sequence, and the seek/select/join will be very fast.

Using SQL SELECT

If an SQL SELECT is performed in the machine collate sequence, any ORDER BY or GROUP BY will also use the MACHINE sequence. If you need to order the resulting records by a non-machine collate sequence, you can either perform the SELECT in that collate sequence, which may be much slower, or split it into two steps as follows:

* Step 1: SELECT records using the "Machine" collate sequence
SET COLLATE TO "MACHINE"	&& use only 'machine' indexes
SELECT * FROM table INTO CURSOR temp1 WHERE lastname = "Müller"
* Step 2: Order the records using the "general" collate sequence
SET COLLATE TO "GENERAL"	&& use only 'general' indexes
SELECT * FROM temp1 INTO TABLE output ORDER BY lastname

Use New Data Types

We introduced four new data types to Visual FoxPro: DateTime, Integer, Double, and Currency. All data that use these types are stored as binary data on disk (Integer is a four-byte binary value; the others are eight-byte binary).

There are two advantages here. First, because the volume of data stored on disk is smaller (an eight digit number stored as Numeric takes eight bytes, whereas it only takes four bytes if it is stored as Integer), loading data and indexes into memory from disk is faster because more actual data can be loaded into memory pages. The second advantage is that no data translation needs to occur. Internally, Visual FoxPro represents integers as four byte binary values and has to translate numerics, which are stored as ASCII on disk. So, every read has to be translated from ASCII to binary and back again. With Integers Doubles, DateTimes, and Currency, such translation doesn't occur so data access is faster.

Of the new data types, Integer is the most important for speed. Wherever possible, use Integer data types for primary and foreign key values. The benefits will be smaller DBFs, smaller indexes, and (perhaps most importantly) much faster joins!

Buffering and Transactions

Keep Transactions Tight

Transactions need to be kept short and to the point. Consider the following example:

BEGIN TRANSACTION
DO FORM BIGFORM TO nResult
IF nResult = IDCOMMIT
	END TRANSACTION
ELSE
	ROLLBACK
ENDIF

What's happening here? While you might think that this is properly wrapping a transaction around an operation, it is. However, transactions are designed to wrap updates to data only. By doing more than that (with user-interface, as shown above, or even programming constructs like CASE, WHILE, or IF statements), you introduce inefficiencies into the actual updating of the data.

This is significant in Visual FoxPro because the use of transactions requires the locking of records. While in the transaction and updating records, locks on those records are taken and not released until the transaction is committed or rolled back. Even if you issue RLOCK( ) or FLOCK( ) followed by UNLOCK, the record locks are held until the END TRANSACTION or ROLLBACK statement. Further, appending records requires Visual FoxPro to lock the header and keep the header lock until the transaction is completed (committed or rolled back). Therefore, in a high-volume, multi-user system, it becomes crucial to minimize the amount of time record locks are held. Thus, a more appropriate application of the above example would be:

* Save method from the cmdSave command button
BEGIN TRANSACTION
UPDATE PRODUCTS SET reorder_amt = 0 ;
	WHERE discontinued = .T.
END TRANSACTION

Use SYS(3051)

This SYS function controls the amount of time, in milliseconds, that Visual FoxPro will wait before attempting to lock a record, table, index, or memo after an unsuccessful locking attempt. The default is 333 milliseconds.

If there is a lot of lock contention on your database files, you can improve the performance of your application by adjusting this value up (the maximum is 1000). As a result, your application doesn't waste time trying to get locks across a network very quickly.

However, if the lock contention is not expected to be high, you could adjust this value down (the minimum is 100) to attempt a lock operation more quickly.

Remote Data Performance

Data retrieval from any back-end database is time consuming. In order to get data from a server database, the following steps must occur:

· The client issues the query to the back-end server database.

· The server has to parse and compile the query.

· A result set is generated.

· The server notifies the client that the result is complete.

· The client fetches the data over the network from the server.

In order to speed the retrieval (or update) of data, many techniques can be used.

Retrieve Only the Data You Need

It is the exception rather than the norm that a functional unit in an application (a form or report in most cases) needs to access all the data from a table. By creating remote views that only fetch (or update) the fields you want and the rows you want, the amount of data that moves across the wire is optimized. For example, if you had a remote view that was bound to a form with four controls (customer_id, company, contact, and address from the customers table), executing the view with a SELECT statement like:

SELECT * FROM customers

would be grossly inefficient because you're retrieving many more fields of data than is required. It would be better to have your view's SELECT statement read:

SELECT customer_id, company, contact, address FROM customers

Use a WHERE Clause

To further limit the amount of data retrieved (or updated), the proper use of a WHERE clause speeds the data access. Using the above example, if you only wanted the customers in the Northwest region, you could have your view's SELECT read:

SELECT customer_id, company, contact, address FROM customers
	WHERE region = 'NORTHWEST'

Parameterize Your WHERE Clauses

The flexibility of the views and SQL Pass Through technology in Visual FoxPro allows you to use runtime parameters in the WHERE clause of your SQL for SELECT, UPDATE, and DELETE. For example, using the above example, you can retrieve information from any region at runtime:

SELECT customer_id, company, contact, address FROM customers
	WHERE region = ?pcRegion

where "pcRegion" is the name of the parameter. This parameter should exist (but doesn't have to) when the view is used or re-queried to retrieve just the information required.

Use the Correct Update Criteria

The Update Criteria tab in the View Designer allows you to specify how you want the data from the view to be updated, inserted, and deleted. The section of the tab labeled "SQL WHERE clause includes" lets you to control the content of the WHERE clause for UPDATE and DELETE operations. This becomes critical for controlling data conflicts on the back end, as well as being important for performance. Let's consider the above view and assume that the key field is "customer_id". Let's further assume you want to be able to update all fields except the key field (which would be rare, since changes to key fields imply a delete followed by an insert), which are "company", "contact", and "address". Based on the option you pick in the "SQL WHERE clause includes" option group, your WHERE clause could be any of the following:

Setting

Resulting WHERE Clause

Key Fields Only

WHERE OLDVAL (customer_id) = CURVAL (customer_id)

Key and Modified Fields
(the default setting)

WHERE OLDVAL (customer_id) = CURVAL (customer_id) AND

OLDVAL (<first_modified_field>) = CURVAL (<first_modified_field>) AND

OLDVAL (<next_modified_field>) = CURVAL (<next_modified_field>) AND

etc.

Key and Updatable Fields

WHERE OLDVAL (customer_id) = CURVAL (customer_id) AND

OLDVAL (company) = CURVAL (company) AND

OLDVAL (contact) = CURVAL (contact) AND

OLDVAL (address) = CURVAL (address)

Key and Timestamp

WHERE OLDVAL (customer_id) = CURVAL (customer_id) AND

OLDVAL (timestamp) = CURVAL (timestamp)

It should become clear from the above table that the WHERE clause can greatly affect performance, especially if there are a lot of fields that are updated (for the Key and Modified case) or if there are a lot of fields that could be updated (for the Key and Updatable case). This is because Visual FoxPro needs to compare the values in its "oldval" data buffer (which is just a system cursor) against the values on disk (the server database) to adequately report when an update conflict has occurred (another user or process has changed the data since you first fetched it). The process of such a comparison can be time consuming if a large update operation is occurring.

Therefore, we recommend that you use the Key and Modified Fields in most cases. The fields you update to the back end are almost always a subset of the total number of fields that you could update (and certainly a subset of the total number of fields in the view).

For server databases that support timestamps, we recommend that you use the Key and Timestamp setting, even above the Key and Modified Fields choice. A timestamp is a row version and is updated each time a change to the row is made. (By the way, a timestamp data type does not imply a date and time, just a numeric value. You don't need to worry about system clocks on different machines causing update problems.) By using this setting, only one column is compared in the WHERE clause and thus the processing is faster.

Note These settings can also be made using the WhereType property on views via the DBSETPROP ( ) or CURSORSETPROP ( ) functions.

Use the BatchUpdateCount Property

Some server databases (like Microsoft® SQL Server™) allow batches of statements to be sent to the server as a single packet. For example, if you set the value of this property to 4 and made updates to the first 10 records, the SQL statements that Visual FoxPro sends to the server database are:

UPDATE customer SET contact = 'John Jones' WHERE customer_id = 1;
UPDATE customer SET contact = 'Sally Park' WHERE customer_id = 2;
UPDATE customer SET company = 'John Jones' WHERE customer_id = 3;
UPDATE customer SET contact = 'John Jones' WHERE customer_id = 4

This technique speeds updates, deletes, and inserts because the sheer number of network packets sent over the wire is reduced, and the statements can be compiled by the server database as one batch instead of piecemeal.

You should experiment with different values for this property and the PacketSize property to optimize your updates.

This property can be set in the Advanced Options dialog box on the Query menu, or through code using DBSETPROP ( ) or CURSORSETPROP ( ). Note that to get the benefit of this, manual transactions should be enabled using DBSETPROP ( ) or SQLSETPROP ( ) with the "Transactions" property.

Use the PacketSize Property

This property controls the size of the network packet (in bytes) that is sent to and retrieved from the database server. It is actually set by ODBC and can be any non-negative value. Different network providers will handle this property differently, so you should consult your network service documentation. Novellâ NetWareâ, for example, has a maximum packet size of 512 bytes, so setting the PacketSize property to a value greater than this will have no additional benefit.

The default setting for this is 4096 bytes. If your network supports larger packet sizes, you could increase the value of this property to have maximum data throughput per request (SELECT, INSERT, UPDATE, DELETE).

Use the FetchMemo Property

This property controls whether or not memo and binary (general field) data is fetched when the row is fetched from the back end. By setting this field to .F., binary data is not transferred to your machine until the field is accessed. This will improve the fetch speed of your query because you're delaying the fetch until the data is needed.

Keep Lookup Tables Locally

There are many instances in which read-only data is accessed fairly often by your application. For example, a states table or a table of employee titles may be widely used in your application. By keeping that data locally (that is, not having every query uploaded to the server), lookups become extremely fast. The types of data for which this technique would be useful are data that never change or change very rarely.

Use Local Rules

It is a little known fact that Visual FoxPro rules support field- and record-level rules on local and remote views. These rules can be used to prevent data that doesn't conform to some data or business rule from getting into the database. The advantage to putting this rule in the view, as opposed to the back-end table, is that you trap the invalid data before it is sent over the wire. The disadvantage is that these rules don't automatically match rules on the back-end server tables. So, if your back-end table rules change in their definition, you would, in most cases, have to change your rules on the view definitions locally. However, if the rules are simple, this is not a huge burden. Also, rule definitions don't usually change that frequently, so you shouldn't expect to update local rules frequently.

To enable local rules, you use DBSETPROP( ). Below is an example:

* Make sure the order number field is > 0
=DBSETPROP("orders.ordnum")

Form/Object Performance

This section describes tips for improving the performance of objects in your application, particularly with respect to forms.

Use the Data Environment

By using the Data Environment of the Form Designer or Report Designer, table open performance is much faster than executing USE, SET ORDER, and SET RELATION commands in the form's Load event. This is because Visual FoxPro uses low-level engine calls to open the tables and set up the indexes and relations.

Limit the Number of Forms in a Formset

Formsets should only be used when it is necessary to have a group of forms share a private data session. When you run a Formset, you're essentially telling Visual FoxPro to create all the form instances in the Formset, and to create all the controls on all of those forms, even though the first form in the Formset is the only one displayed. This can be very time consuming and unnecessary if the forms don't have to share a private data session. Instead, you should execute DO FORM for other forms when they are needed.

On the other hand, if you do use Formsets, you will gain performance in accessing the forms in the Formset because they are already loaded but not visible.

Dynamically Load Page Controls on a Pageframe

Pageframes, like Formsets, load all the controls for each page when the Pageframe is loaded. You can use the technique of creating a class out of the controls in each page of your Pageframe.

Your goal is to postpone instantiation of the controls on unseen pages until the user accesses those pages. These are the steps:

1. Design your form as you normally would, including all controls on all pages.

2. When your design is complete, go to the second page of your page frame and save the controls you find there as a class.

3. Open the class you created and ensure the controls are still properly laid out.

4. Repeat steps 2 and 3 for the third and subsequent pages of the page frame.

5. In the Activate method of the second and subsequent pages of the page frame, place code like the following:

IF This.ControlCount=0
 This.Addobject("cnrpage1","cnrpage1")
 This.cnrpage1.visible=.T.
ENDIF

By using this technique, you speed the loading of forms because the controls on the second and subsequent pages of the Pageframe are not loaded until they are needed.

Dynamically Bind Controls to Data

You can also see a significant boost in performance for forms that have many data-bound controls if you delay the binding of those controls until they are needed.

You can put the tables and views for your form in the Data Environment so that they are opened when the form is loaded. Then, when a control—e.g., a combo box—gets focus, you would bind the control to the data value. In the example below, a combo box has been bound to the "customer.company" field in the "GotFocus" event:

* Check to see if the control has been bound yet.
IF this.RecordSource = ""
	
	* Set the record source to the right value
	* and set the record source type to "fields"
	this.RecordSource = "customer.company"
	this.RecordSourceType = 6

	* Refresh the control
	this.Refresh
ENDIF

By using this technique for data-bound controls, form load time will dramatically improve.

Use LockScreen

This property allows you to delay the screen refresh for any changes made to controls on the form. For example, if you make controls visible or invisible, change control colors, or move records in bound controls, it is much more efficient to delay the painting of those controls until after the they've all completed:

thisform.LockScreen = .T.
thisform.MyButton.Caption = "Save"
thisform.MyGrid.BackColor = RGB (255, 0, 0) 	&& Red
SKIP IN customers
SKIP IN orders
thisform.Refresh
thisform.LockScreen = .F.

In this example, we set a caption, changed a background color, and moved records in all my controls bound to customers and orders. Without the LockScreen property, you would see each of these operations cause a paint operation of the affected controls, and the overall update performance would seem sluggish rather than snappy.

You should typically use this technique when multiple changes to the display of a form are needed. It is not recommended that you use LockScreen with every display change, but rather with a group of changes as the example above shows.

Use WITH … ENDWITH or Use Object References

When you reference a property of an object with the "object.property" syntax, Visual FoxPro must first search for the object before it can access the property. For example, the following code causes Visual FoxPro to search through four objects (thisform, pgfCustInfo, pagCustName, and cboName) to find the property to be set, and does this three times:

thisform.pgfCustInfo.pagCustName.txtName.Value = "Fred Smith"
thisform.pgfCustInfo.pagCustName.lblName.Caption = "Name"
thisform.pgfCustInfo.pagCustName.grdOrders.BackColor = RGB (0,0,0)
lcSeek = ALLTRIM(thisform.pgfCustInfo.pagCustName.txtName.Value)

If you will be setting two or more properties of an object in succession, use the WITH … ENDWITH construct. This will cause Visual FoxPro to locate and find the object only once. With the above property settings, using WITH … ENDWITH will be faster:

WITH thisform.pgfCustInfo.pagCustName
	.txtName.Value = "Fred Smith"
.lblName.Caption = "Name"
	.grdOrders.BackColor = RGB (0,0,0)
	lcSeek = ALLTRIM(.txtName.Value)
ENDWITH

Note You can use the <.objectname> syntax anywhere in the WITH … ENDWITH construct as shown with the ALLTRIM function.

Another way to do this is to use object references. Object references are nothing more than variables that hold a reference to an object. Using the example above, you could also do the following:

oPageFrame = thisform.pgfCustInfo.pagCustName
oPageFrame.txtName.Value = "Fred Smith"
oPageFrame.lblName.Caption = "Name"
oPageFrame.grdOrders.BackColor = RGB(0,0,0)

The example above won't necessarily yield a significant performance boost, but if the object (the pagCustName PageFrame object in this case) is referenced several times in your application or in a loop, performance will improve.

Use Variables for Multiple Assignments

Accessing the full <container.object.property> is slower than accessing a variable. This can be especially noticeable within program loops. Typical object-oriented programming practice is to copy the contents of an object property to a variable if it is to be used repeatedly. For example, the following code fills a property array:

FOR x = 1 to 256
	thisform.aCharArray[x] = SUBSTR(thisform.cCharString, x, 1)
ENDFOR

The following code is much faster even though more code is executed because the <object.property> code has been factored out of the loop:

* Copy string to a local variable
lcChar = thisform.cCharString

* Create a local array
LOCAL laCharArray[256]

FOR nCounter = 1 to 256
	laCharArray[x] = SUBSTR(laChar, x, 1)
ENDFOR

* Copy the local array to the property array
=ACOPY(laCharArray, thisform.aCharArray)

Avoid Large Amount of Xbase Code in Init/Refresh/Paint Methods

These events occur very frequently. You should eliminate (or reduce) the amount of code you have in these methods. This is especially true for Refresh and Paint methods since they happen very often.

Move Init code to less frequently used methods such as Activate, Click, and GotFocus. Then, you can use a property on the control to keep track of whether the control has already run code that only needs to be run once.

Use the NoDataOnLoad Property on Views

The NoDataOnLoad property on a Data Environment Cursor object for a view is identical to the NODATA clause on the USE command for views. It causes the view to be opened but the view doesn't actually fetch any data. This is true for both local and remote views.

You would typically use this technique with parameterized views in your Data Environment (see Parameterize Your WHERE Clauses section above). For example, you could have a customer information form that uses a view that is parameterized on customer_id. You would enter a valid customer_id and press a "Search" button. The code of the search button would be something like the following (assuming that the parameter for the view is called "vpCustID" where "vp" is the Hungarian notation used for designating a variable as a view parameter):

* Set the view parameter to the value in the customer id textbox
vpCustID = thisform.txtCustomerID

* Lock the screen to delay painting
thisform.LockScreen = .T.

* Issue the requery on the view 
=REQUERY('customerview')

* Refresh the databound controls
thisform.Refresh

* Remove the lock on the screen
thisform.LockScreen = .F.

This code is a simple example but it is typical for processing local and remote parameterized views.

The advantage here is that form load time is greatly reduced because the view doesn't cause any data to be brought back to you, the client. The controls that are bound to fields of the view still get bound because there is an open work area (just with no data in it).

OLE Performance

Start OLE Servers Before You Access OLE Data

Controls bound to general fields will generally perform better when the servers for those data types (like Microsoft® Word or Microsoft® Excel) are already running on the client's machine.

Automation Performance

Use GetObject Instead of CreateObject

In some cases Automation servers (like Microsoft Excel) will always start a new instance of itself, even if one is already running. To remedy this (and improve performance), use the GetObject function instead of CreateObject. For example, the following call:

x = GetObject(, "excel.Application")

will always use an existing instance if it exists, whereas:

x = CreateObject("excel.Application")

will create a new instance.

In the case in which you use GetObject( ), you will get an error from Visual FoxPro if the server is not already running so you should trap for the error and call CreateObject( ).

Don't Repeatedly Evaluate Long Expressions with Sub-Objects

Executing expressions that use objects within the OLE server can be time consuming, particularly when evaluated multiple times. It is much faster to cache sub-objects in variables for reference (see Use Variables for Multiple Assignments above for details).

Insert objects "As Icon"

When you insert an OLE object into a field, you can insert it as an icon or placeholder, rather than as the entire object. This will reduce the amount of storage space required because Visual FoxPro stores a "presentation" image with the object. Also, an increase in the performance for drawing the object will occur because just the icon needs to be rendered.

Use Image Controls

If single bitmaps are to be used in an application like a company logo, Image Controls are much faster than OLEBoundControls.

Use Manual Links When Possible

Manual links to objects are faster because of the notification time required with Automatic links and also because the OLE server doesn't need to be started to draw the object. If you don't need to update an object frequently, use Manual links.

Memory Management

Use SYS(3050)

This SYS function allows you to optimize the amount of foreground and background memory is used for data buffers. Foreground memory refers to memory that is available to Visual FoxPro when it is the foreground (active) application. Background memory refers to memory that is available to Visual FoxPro when it is one of the background applications.

You can experiment with these values to consume more or less memory for consumption by Visual FoxPro for its data buffers to optimize your application.

Don't Use MEMLIMIT

MEMLIMIT is not recognized by Visual FoxPro. In FoxPro 2.6, MEMLIMIT was a configuration setting that specified the maximum amount of memory FoxPro would allocate for its use. Do not use this configuration setting to limit the amount of memory Visual FoxPro will use. Use SYS (3050) if necessary.

Acknowledgments

Although I put this paper together, it would have been nearly impossible without the contributions of several members of the Visual FoxPro team: Geoff Kizer, Tom Cooper, Lori Sargent, Rodney Hill, Dave Berliner, Matt Pohle, and the rest of the Visual FoxPro performance team.

I would also like to thank George Goley of MicroEndeavors, Inc. and David T. Anderson of Alden Anderson Consulting, Inc. for providing many of these tips and for reviewing this paper.

The information contained in this document represents the current view of Microsoft Corporation on the issues discussed as of the date of publication. Because Microsoft must respond to changing market conditions, it should not be interpreted to be a commitment on the part of Microsoft, and Microsoft cannot guarantee the accuracy of any information presented after the date of publication.

This document is for informational purposes only. MICROSOFT MAKES NO WARRANTIES, EXPRESS OR IMPLIED, IN THIS DOCUMENT.

© 1996 Microsoft Corporation. All rights reserved.

Microsoft, FoxPro, Windows, Windows NT, Rushmore, and SQL Server are registered trademarks and Visual FoxPro is a trademark of Microsoft Corporation. Novell and NetWare are registered trademarks of Novell, Inc.


© 1997 Microsoft Corporation. All rights reserved.