Back

Originally published in FoxTALK in 1998


How to make your programs fast, or How to take the slow parts out of your programs.

Everybody wants speed. Faster is better, they say. How do you optimize your code? Is there any structured approach you can use to get the most performance out of a program? This month we will examine some principles that can be implemented to the code optimization process. This article will not be a treatise on Rushmore, Rushmore has and will be covered in other places.  Instead we will focus on coding practices and techniques for getting the most performance out of your applications.

Data

Certain data types are handled faster than others in Visual FoxPro are.  This has to do with the way the data is stored in a dbf file versus the way the data is stored in memory. For example, Integers are stored in the DBF exactly as they are used in memory, however, Numerics are stores as a string of digits and used in memory as a floating point binary number. The Numeric data type must be converted back and forth, as you use it. Whenever it is possible, use an Integer to store numeric values (or a Double for floating point numbers).

DateTime and Date data both occupy the same amount of space in the dbf.  However, Dates are stored in the ANSI standard date format of YYYYMMDD, while DateTime fields are stored as an 8-byte number. In memory, both Dates and DateTimes use the 8-byte number format, but Dates must be converted and DateTimes don’t need to be converted. You can use the DateTime data type for all of your calendar based data and get the most out of VFP. If you only need the date, then leave the time at the default of 00:00:00.

When is a number not a number? When it never gets used in calculations.  For example, telephone numbers, zip codes, customer numbers, invoice numbers are all examples of numbers that are not numbers. When was the last time you needed to calculate the average invoice number in a table? Make these types of numbers character data and them limit the characters to only digits. Character data is manipulated faster than numeric data, so only use numeric when it is really a number (even then consider using Integer or Double).

Memo fields allow you to store variable length data, but they can be abused through laziness. Don’t use a Memo field just because you don’t feel like figuring out what the maximum length for the data will be. Doing this will make your work a small bit faster but slow the application down considerably. Memos take longer to search on and to process. Use Character fields whenever possible.

Constructs

Some programming constructs are faster than others.  The FOR/ENDFOR loop is faster than an equivalent DO WHILE/ENDDO loop. SCAN/ENDSCAN is faster than the equivalent DO WHILE NOT EOF()/SKIP/ENDDO loop. Use the faster constructs to get faster code, or as George Goley once said, “Make your code faster by taking the slow parts out.”

Creating Objects

Object instantiation is a time consuming operation. There is no way around that.  So then, how do you make object creation faster?

IF TYPE(“THIS.Parent.Container1.Name”) = “C”

  RETURN

ENDIF

THIS.Parent.AddObject(“Container1”,”YourSavedClass”)

THIS.Parent.Refresh()

With automation servers use the GetObject() function rather than the CreateObject() function. GetObject() will find any existing instance of the object while CreateObject() will always create another instance if the object already exists. If the object you want exists, you can save the instantiation hit by using the GetObject() function.

Refreshing Displays

If you really want to see something interesting, try turning event tracking for the Refresh event and running one of your applications. Just run it for awhile doing stuff and then look at the refresh events that fired.  You may be in for a surprise when you see how many times the refresh has been fired.

It is often faster to call the refresh of individual controls than it is to fire the form’s refresh. Try to limit the refresh calls to only those that are essential.  Remember that calling a form’s refresh will fire the refresh of all the controls in the form.  The same is true of refreshing a Grid, a Page in a PageFrame, and the other container classes.

Name Expression instead of Macros

Whenever possible, use a name expression instead of a macro. Macros must be expanded and interpreted at runtime every time they are encountered. Macros not only make your code unreadable and the logic obtuse, they also take longer to execute.

Name expressions, on the other hand, are much faster to execute.  Name expressions can be used wherever VFP is expecting to see the name of something. Names like table names, file names, field names, etc are prime candidates for name expressions. The following two code examples show the idea;

* Example 1 Macros

LOCAL lcFieldName

USE SomeReallyBigTable

USE SomeOtherTable IN 0

SCAN

  lcFieldName = “SomeReallyBigTable.YTDAmt”

       SELECT SomeOtherTable

       SCAN FOR SomeOtherTable.ForeignKey = SomeReallyBigTable.PrimaryKey

    REPLACE &lcFieldName WITH &lcFieldName + SomeOtherTable.SalesAmt

       ENDSCAN

ENDSCAN

 

* Example 2 Name expression

LOCAL lcFieldName

USE SomeReallyBigTable

USE SomeOtherTable IN 0

SCAN

  lcFieldName = “SomeReallyBigTable.YTDAmt”

       SELECT SomeOtherTable

       SCAN FOR SomeOtherTable.ForeignKey = SomeReallyBigTable.PrimaryKey

    REPLACE (lcFieldName) WITH EVALUATE(lcFieldName) + SomeOtherTable.SalesAmt

       ENDSCAN

ENDSCAN

The second example will execute faster than the first one. Notice the use of the EVALUATE() function to get the value stored in the field.

Referencing Object Properties

Referencing object properties is slower than referencing memory variables. When you need to write code that will use an object property multiple times it will be faster to put the value of the property in a memory variable and then use the variable in subsequent code. Two examples follow;

* Example 1

USE SomeTable

SCAN FOR SomeField = THISFORM.SomeProperty

  Blah

  Blah

  Blah

ENDSCAN

 

* Example 2

USE SomeTable

LOCAL lcValue

lcValue = THISFORM.SomeProperty

SCAN FOR SomeField = lcValue

  Blah

  Blah

  Blah

ENDSCAN

The second example will run faster.

Another method of optimizing property accesses is to use the WITH/ENDWITH construct. When you need to access a number of properties of the same object you can do it faster using WITH/ENDWITH.

* Example 1

THISFORM.Property1 = “ABC”

THISFORM.Property2 = “ABC”

THISFORM.Property3 = “ABC”

THISFORM.Property4 = “ABC”

THISFORM.Property5 = “ABC”

THISFORM.Property6 = “ABC”

THISFORM.Property7 = “ABC”

THISFORM.Property8 = “ABC”

 

* Example 2

WITH THISFORM

   .Property1 = “ABC”

   .Property2 = “ABC”

   .Property3 = “ABC”

   .Property4 = “ABC”

   .Property5 = “ABC”

   .Property6 = “ABC”

   .Property7 = “ABC”

   .Property8 = “ABC”

ENDWITH

Miscellaneous Optimizations

Open tables only once whenever possible, opening additional instances of a table adds to memory consumption and slows other operations down.

Send output to the currently active window whenever possible, it takes longer to update and non-current window than it does to update the currently active one.

You can make use of the LockScreen property of a form to prevent interactive updating of the form’s contents during operations.  Set LockScreen to .T. before beginning the operation and the set it to .F. after the operation is completed. Although this doesn’t give a large time improvement, it makes the interface seem snappier to the user as they don’t watch the refreshes occurring in sequence.

Always SET DOHISTORY OFF in your start up program.  In addition, whenever possible, SET TALK OFF. Both DOHISTORY and TALK take time to do their thing and shutting them off stops them from taking time.

Individual forms load faster than formsets. That stands to reason, because formsets must create multiple forms. Whenever possible use separate individual forms so that you only instantiate the ones the user is going to use.

Using the DataEnvironment of a form or report is much faster at opening the tables and views than write USE statements to open them is.

Clear objects from memory as soon as you are finished with them.  This frees more memory for the objects that are being used.

Write IF statements so that their expression is usually true.

For conditional method code, write an IF statement at the beginning of the code that RETURNS when its expression is true to short circuit the method.  This will execute faster than making VFP parse the entire method to find out it doesn’t need to execute any of the code.

Write DO CASE constructs so that the earlier CASE statements are the most often executed.

Summary

This article presents a number of different things you can do in writing your applications to improve their performance. Optimizing an application is a painstaking process, by following these principles when you first write the code you will lessen the complexity of optimizing the code later.