Until version 5 of Visual Basic, VB apps were never known for exceptional speed. With the new native code compiler and vastly improved forms engine, that's no longer the case. There's no reason not to expect snappy performance from a VB app, but you need to apply a few common sense techniques to get the most from VB and the hardware your apps are running on.
Before you jump on the bandwagon and start tuning every line of code, however, keep in mind that a good application design and architechture can do more for performance than even the most finely tuned code.
You can broadly categorize performance into three areas:
- Startup Performance
This is most critical at application start time, but also applies to loading any form. If your application is visible and ready for input by the user it will at least present the appearance of having good performance.
- Real Performance
Raw speed is important, but often speed can be sacrificed for flexibility.
- Perceived Performance
What may be more important than pure speed is a feeling of responsiveness from the perspective of the user. For example, most word processing applications hand off printing to a background task. The actual printing process may take longer if it's running in the background, but the application is more responsive from the user's perspective because they can immediately continue with another task.
Improving Startup Performance
Several factors can affect startup performance for the application overall and for indiviual forms.
- Reduce the amount of work done in Sub Main and the Initialize and Load events for forms.
Any code that runs before your first form is visible means time that the user must wait before seeing any response from your application. If you can't eliminate initialization code for the application or its forms, at least try to make something visible to the user as quickly as possible. You can use the Show method in the Load event for forms to force the form to display itself before the initialization code is done running (but make sure you don't have later code that moves the form or rearranges controls or you'll get a visible flicker effect).
- Reduce the number of controls on your forms.
Controls consume memory and resources, and custom controls generally consume more than standard windows controls. Although it's easy and tempting to create forms with a lot of controls or tabbed dialog boxes with potentially dozens of controls, it's a performance killer.
- Use control arrays.
Control arrays help reduce the consumption of resources and memory somewhat, and can also reduce coding effort by consolidating event procedures into a single procedure for the entire array.
- Keep graphics to a minimum.
Rich graphics in your interfaces, while attractive, consume a lot of memory and take a lot of compute cycles to draw. Reducing the amount of graphics can have a significant impact on the load times of forms.
- Use a splash screen.
Most non-trivial applications need to do some setup work at start time. Rather than leave the user sitting there wondering if the program is working, display a simple splash screen to give them something to look at while the initialization code finishes running. While a splash won't do anything to make the app start faster, it gives a polished appearance to the startup and lets the user know the app is doing something. Make sure you design the splash to work correctly regardless of the hardware platform the app is running on. If you designed it for a 486/33 machine and it worked nicely, it may be nothing more than a flicker on a 200 MHz Pentium machine with plenty of RAM. Don't take any more time than is necessary away from the user, but make sure the splash is visible long enough for the user to see it.
- Load only the data you need.
Even with today's high performance disk subsystems, file i/o is orders of magnitude slower than direct memory access. Fetching data from disk, whether it be in initialization files, text files, random access files, custom binary files, or a local or remote database, is a time consuming process. By loading only the data you need you can potentially reduce the amount of file i/o overhead.
Remember, the key is to get some visible element of the application in front of the user as rapidly as possible. If it takes a form a second or two to finish initializing after it is first displayed, the user will not be ready to work with it anyway so the time is "free".
Getting the user to the point where they can interact with the application quickly is important, but you also need to code the application so that it does its work as fast as possible. You can apply a variety of tuning techniques to your code to speed up performance.
- Avoid the use of Variants.
Variant variables, although flexible, are notoriously slow. Unless the situation requires the use of Variants for handling Nulls or data types that are unknown at design time, stick with the basic data types.
- Use Integers when possible.
The 32-bit long integer has been the native data type of every Intel processor since the 386, so the hardware will always run integer operations faster than floating point or Variant operations. Some operations that would seem to require the use of floating point math may be able to use integers. For example, if you use only twips or pixels when moving forms or controls, you can safely use integers. Don't forget to use the the integer division operator (\) instead of the normal floating point division operator (/).
- Use With...End With when possible.
Each object reference takes compute cycles to resolve. By using With...End With, VB only needs to resolve object references once. Without With...End With, the object references need to be resolved for every statement.
- Cache property values in local variables.
Property reads and writes are expensive operations. If you will access a property value more than once in a procedure, cache its value in a local variable and use the variable instead of reading the property directly.
- Remove as much code as possible from loops.
Loop constructions can be speeded significantly by removing as much code as possible from the loop. If loops are nested, try to move code toward the outermost level of nesting. If possible, avoid making object references or property reads and writes in the body of a loop.
- Cache files in memory.
Frequently used data stored on disk should be cached in memory. However, don't overdo it or you'll force windows to swap more data from RAM to the paging (virtual memory) file and negate the benefit of having cached data.
- Cache remote data locally.
If you must go to a network drive or remote database server for your data, cache the most frequently used data locally if possible. Even on the latest Fast Ethernet networks, accessing data from memory or even the local hard disk is faster than going over the network. If you need to go over a slow WAN link for remote data, a local caching strategy may be critical for adequate performance. In addition to gaining performance, your network administrator will thank you for reducing traffic on the network.
- Don't fetch data you don't need.
Only the truly lazy programmer routinely fetches data that isn't required by the application. If, for example, you routinely use SELECT * in database queries, you are almost certainly returning data you don't need. Since every byte of data that's moved from disk to you application consumes time (even more so over a network) and memory, it's a waste to fetch data you won't use.
- Profile raw performance.
If you aren't measuring performance benchmarks, you're just guessing. Don't guess. Use a profiling tool or roll your own code to measure the performance of your application and then tune the code that needs tuning.
Raw speed is great and it's a satisfying feeling to cut the time required for a code segment to run, but the user doesn't care about raw speed. Users want an application to be responsive to their input and to finish tasks as quickly as possible. In some cases, that may mean raw speed, but in most cases it means that the application should work smarter, not faster. Additionally, in those situations where the user must wait for a process to finish, the application must provide feedback to the user that it is in fact performing the task the user commanded.
There are two basic techniques available for providing a better feeling of responsiveness for the user.
- Delegating the work.
Since windows is a multitasking operating system, handing off tasks to another application can give a big boost to performance. Beginning with Visual Basic 4 this was made much simpler. You can use OLE Automation and ActiveX server components to hand long running tasks to another process. In some cases, this may require setting up pooling or queue structures, but if it saves the user a considerable amount of time it may be worth the effort. The ultimate means of delegating work is to pass it to another computer. This is available with the Enterprise edition of VB via Remote Automation and now DCOM. With VB5, in some situations you can even create multithreaded applications. The May 1997 issue of Visual Basic Programmer's Journal includes an article that demonstrates using VB5's AddressOf operator to create threads in a VB app.
Even if you are using an older version such as VB3 on a 16-bit platform, you can still hand off work using the Shell function, DDE, OLE Automation (VB3 can use, but not create server applications), or NetDDE. If you're willing to hack the appropriate APIs, you can call standard windows IPC (interprocess communication) routines to communicate locally or over a network, passing data and jobs around to the machines or processes with compute cycles available to handle the work.
- Providing feedback to the user.
Sometimes there's just no way to avoid leading the user into a long running process. In those cases, it's vital that you provide some feedback mechanism to the user. If the delay is very short - a few seconds at most - a simple hourglass cursor may be sufficient. If the delay is longer, you will need something more explicit, such as a progress meter. However, keep in mind that in some situations you will not be able to use a progress meter. For example, if you start a long running database query control will not return to your application until the query completes. In this case, you may have no alternative but to display a simple message (such as "Working. Please wait...", etc.) until control is returned to your application and you can pass control back to the user.
If possible, delegation techniques should be used instead of feedback mechanisms. In the vast majority of situations, rethinking the design of the application will reveal an alternative approach that allows the user to continue working while a long running process completes its work.
One additional technique that may prove useful is "just in time" delivery of data. Taken in small chunks, access to data overall may require more total compute cycles in your application compared to mass fetching of data, but if only the necessary data is loaded only when it is needed, the application may be able to deliver the data as fast as the user can deal with it. If this is the case, the overall performance penalty is more than made up for by the added responsiveness of the application.
The key factor to remember is that it is irrelevant how long it takes your code to complete a task. The purpose of an application is to provide a service to the users of the application, so the important thing is to provide the users with tools that allow them to finish their work quickly. If an application takes five minutes to perform a task (a frighteningly long time in software) it may still be less than the 10 minutes it would take the user to perform it without the application. If you also provide the user with a means to continue with another task, user productivity really takes off - and that's the purpose of the application to begin with.
There are three general areas where performance can be improved:
- Startup and Load Time
Getting your application and its forms on screen faster means the user can go to work sooner. Reduce the complexity of your forms and startup code to get them on screen as fast as possible.
- Real Performance
Efficent coding practices always benefit your application. Use standard code optimization techniques to avoid wasting CPU cycles.
- Perceived Performance
Responsiveness, rather than raw speed, is how users measure your apps. Delegate tasks to other threads, applications, or even other computers to get the user back to work as quickly as possible. If a long process that forces the user to wait is unavoidable, provide feedback so the user doesn't think you application has quit working.
These basic techniques are not the only tools available for speeding up your applications, but will give you a good start. Finally, remember to measure the performance of your application so that you optimize what needs optimization. If you waste time tuning procedures that are seldom called, its time not spent optimizing more important code or building other productivity tools for the users.
By Joe Garrick