Stuff About Optimization

This section deals with how to best optimize your applications. Notice that the word "code" didn't appear in the preceding sentence. To correctly optimize the way we work and the speed with which we can ship products and solutions, we need to look beyond the code itself. In the following pages, I'll describe what I think are the most effective ways to optimize applications.

Choosing the Right Programmers

In my opinion, there's a difference between coding and programming. Professional programming is all about attitude, skill, knowledge, experience, and last but most important, the application of the correct algorithm. Selecting the right people to write your code will always improve the quality, reuse, and of course execution time of your application. See Chapter 17 (on recruiting great developers) for more on this subject.

Using Mixed Language Programming

Correctly written Visual Basic code can easily outperform poorly written C code. This is especially true with Visual Basic 6. (Visual Basic 6 native code is faster than p-code.) Whatever language you use, apply the correct algorithm.

At times, of course, you might have to use other languages, say, to gain some required speed advantage. One of the truly great things about Windows (all versions) is that it specifies a linkage mechanism that is defined at the operating system level. In MS-DOS, all linkages were both early and defined by the language vendor. The result was that mixed-language programming was something that only the very brave (or the very foolish) would ever have attempted. It used to be impossible, for example, to get some company's FORTRAN compiler to produce object files that could be linked with other object files generated by another company's C compiler. Neither the linker supplied with the FORTRAN compiler nor the one that came with the C compiler liked the other's object file format. The result was that mixed-language programming was almost impossible to implement. This meant, of course, that tried-and-tested code often had to be ported to another language (so that the entire program was written in one language and therefore linked).

Trouble is that these days we've largely forgotten that mixed language programming is even possible. It is! Any language compiler that can produce DLLs can almost certainly be used to do mixed-language programming. For example, it's now easy to call Microsoft COBOL routines from Visual Basic. Similarly, any language that can be used to create ActiveX components can be used to create code that can be consumed by other, language-independent, processes.

At The Mandelbrot Set (International) Limited (TMS), when we really need speed—and after we've exhausted all the algorithmic alternatives—we turn to the C compiler. We use the existing Visual Basic code as a template for writing the equivalent C code. (We have an internal rule that says we must write everything in Visual Basic first—it's easier, after all.) We then compile and test (profile) this code to see whether the application is now fast enough. If it's not, we optimize the C code. Ultimately, if it's required, we get the C compiler to generate assembly code, complete with comments (/Fc and /FA CL.EXE switches are used to do this), and discard the C code completely. Finally, we hand-tune the assembly code and build it using Microsoft's Macro Assembler 6.11.

Controlling Your Code's Speed

Don't write unnecessarily fast code. What I mean here is that you shouldn't produce fast code when you don't need to—you'll probably be wasting time. Code to the requirement. If it must be fast, take that into account as you code—not after. If it's OK to be slow(er), then again, code to the requirement. For example, you might decide to use nothing but Variants if neither size nor execution speed is important. Such a decision would simplify the code somewhat, possibly improving your delivery schedule. Keep in mind that each project has different requirements: code to them!

Putting On Your Thinking Cap

The best optimizations usually happen when people really think about the problem1. I remember once at TMS we had to obtain the sine of some number of degrees many times in a loop. We used Visual Basic's Sin routine to provide this functionality and ultimately built the application and profiled the code. We found that about 90 percent all our recalculating execution time was spent inside the Sin routine. We decided therefore to replace the call to Visual Basic's routine with a call to a DLL function that wrapped the C library routine of the same name. We implemented the DLL, rebuilt, and retested. The results were almost identical. We still spent most of the time inside the Sin routine (although now we had another external dependency to worry about—the DLL!). Next we got out the C library source code for Sin and had a look at how we might optimize it. The routine, coded in an assembly language, required detailed study—this was going to take time! At this point, someone said, "Why don't we just look up the required value in a previously built table?" Brilliant? Yes! Obvious? Of course! 1. See the famous book Programming Pearls by Jon Bentley for more on this approach. (Addison-Wesley, 1995, ISBN 0-201-10331-1.)

Staying Focused

Don't take your eyes off the ball. In the preceding example, we lost our focus. We got stuck in tune mode. We generated the lookup table and built it into the application, and then we rebuilt and retested. The problem had vanished.

"Borrowing" Code

Steal code whenever possible. Why write the code yourself if you can source it from elsewhere? Have you checked out MSDN and all the sample code it provides for an answer? The samples in particular contain some great (and some not so great) pieces of code. Unfortunately, some programmers have never discovered the VisData sample that shipped with Visual Basic 5, let alone looked through the source code. If you have Visual Basic 5, let's see if I can tempt you to browse this valuable resource. VISDATA.BAS contains the following routines. Could they be useful?

ActionQueryType AddBrackets AddMRU
CheckTransPending ClearDataFields CloseAllRecordsets
CloseCurrentDB CompactDB CopyData
CopyStruct DisplayCurrentRecord DupeTableName
Export GetFieldType GetFieldWidth
GetINIString GetODBCConnectParts GetTableList
HideDBTools Import ListItemNames
LoadINISettings NewLocalISAM MakeTableName
MsgBar OpenLocalDB NewMDB
ObjectExists RefreshErrors OpenQuery
OpenTable SetFldProperties RefreshTables
SaveINISettings ShowError SetQDFParams
ShowDBTools StripConnect ShutDownVisData
StripBrackets StripOwner StripFileName
StripNonAscii stTrueFalse UnloadAllForms
vFieldVal    

There's more! The BAS files in the SETUP1 project contain these routines—anything useful in here?

AbortAction AddActionNote AddDirSep
AddHkeyToCache AddPerAppPath AddQuotesToFN
AddURLDirSep CalcDiskSpace CalcFinalSize
CenterForm ChangeActionKey CheckDiskSpace
CheckDrive CheckOverwrite-PrivateFile CommitAction
CopyFile CopySection CountGroups
CountIcons CreateIcons CreateOSLink
CreateProgManGroup CreateProgManItem CreateShellLink
DecideIncrement-RefCount DetectFile DirExists
DisableLogging EnableLogging EtchedLine
ExeSelfRegister ExitSetup Extension
fCheckFNLength fCreateOS-ProgramGroup fCreateShellGroup
FileExists fIsDepFile fValidFilename
FValidNT-GroupName fWithinAction GetAppRemo-valCmdLine
GetDefMsgBoxButton GetDepFileVerStruct GetDiskSpaceFree
GetDrivesAllocUnit GetDriveType GetFileName
GetFileSize GetFileVersion GetFileVerStruct
GetGroup GetLicInfoFromVBL GetPathName
GetRemoteSupport-FileVerStruct GetTempFilename GetUNCShareName
GetWindowsDir GetWindowsSysDir GetWinPlatform
IncrementRefCount InitDiskInfo intGetHKEYIndex
IntGetNextFldOffset IsDisplayNameUnique IsNewerVer
IsSeparator IsUNCName IsValidDestDir
IsWin32 IsWindows95 IsWindowsNT
IsWindowsNT4-WithoutSP2 KillTempFolder LogError
LogNote LogSilentMsg LogSMSMsg
LogWarning LongPath MakeLongPath
MakePath MakePathAux MoveAppRemovalFiles
MsgError MsgFunc MsgWarning
NewAction NTWithShell PackVerInfo
ParseDateTime PerformDDE Process-CommandLine
PromptForNextDisk ReadIniFile ReadProtocols
ReadSetupFileLine ReadSetupRemoteLine RegCloseKey
RegCreateKey RegDeleteKey RegEdit
RegEnumKey RegisterApp-RemovalEXE RegisterDAO
RegisterFiles RegisterLicense RegisterLicenses
RegisterVBLFile RegOpenKey RegPathWin-CurrentVersion
RegPathWinPrograms RegQueryNumericValue RegQueryRefCount
RegQueryStringValue RegSetNumericValue RegSetStringValue
RemoteRegister RemoveShellLink ReplaceDouble-Quotes
ResolveDestDir ResolveDestDirs ResolveDir
ResolveResString RestoreProgMan SeparatePath-AndFileName
SetFormFont SetMousePtr ShowLoggingError
ShowPathDialog SrcFileMissing StartProcess
StrExtractFile-nameArg strExtractFilenameItem strGetCommon-FilesPath
StrGetDAOPath strGetDriveFromPath strGetHKEYString
StrGetPredefined-HKEYString strGetProgramsFilesPath StringFromBuffer
StripTerminator strQuoteString strRootDrive
StrUnQuoteString SyncShell TreatAsWin95
UpdateStatus WriteAccess WriteMIF

Calling on All Your Problem-Solving Skills

Constantly examine your approach to solving problems, and always encourage input and criticism from all quarters on the same. Think problems through. And always profile your code!

A truly useful code profiler would include some way to time Visual Basic's routines. For example, how fast is Val when compared with its near functional equivalent CInt? You can do some of this profiling using the subclassing technique discussed in Chapter 1 (replacing some VBA routine with one of your own—see Tip 11), but here's a small example anyway:

Declarations Section

 Option Explicit Declare Function WinQueryPerformanceCounter Lib "kernel32"  _  Alias "QueryPerformanceCounter" (lpPerformanceCount As LARGE_INTEGER) _ As Long Declare Function WinQueryPerformanceFrequency Lib "kernel32" _ Alias "QueryPerformanceFrequency" (lpFrequency As LARGE_INTEGER) _ As Long Type LARGE_INTEGER     LowPart     As Long     HighPart    As Long End Type 

In a Module

 Function TimeGetTime() As Single         Static Frequency     As Long     Dim CurrentTime      As LARGE_INTEGER     If 0 = Frequency Then             Call WinQueryPerformanceFrequency(CurrentTime)                Frequency = CurrentTime.LowPart / 1000                TimeGetTime = 0            Else             Call WinQueryPerformanceCounter(CurrentTime)             TimeGetTime = CurrentTime.LowPart / Frequency          End If  End Function 

Replacement for Val

 Public Function Val(ByVal exp As Variant) As Long     Dim l1 As Single, l2 As Single          l1 = TimeGetTime()          Val = VBA.Conversion.Val(exp)          l2 = TimeGetTime()          Debug.Print "Val - " & l2 - l1 End Function 

The TimeGetTime routine uses the high-resolution timer in the operating system to determine how many ticks it (the operating system's precision timing mechanism) is capable of per second (WinQueryPerformanceFrequency). TimeGetTime then divides this figure by 1000 to determine the number of ticks per millisecond. It stores this value in a static variable so that the value is calculated only once.

On subsequent calls, the routine simply returns a number of milliseconds; that is, it queries the system time, converts that to milliseconds, and returns this value. For the calling program to determine a quantity of time passing, it must call the routine twice and compare the results of two calls. Subtract the result of the second call from the first, and you'll get the number of milliseconds that have elapsed between the calls. This process is shown in the "Replacement for Val" code.

With this example, one can imagine being able to profile the whole of VBA. Unfortunately, that isn't possible. If you attempt to replace certain routines, you'll find that you can't. For example, the CInt routine cannot be replaced using this technique. (Your replacement CInt is reported as being an illegal name.) According to Microsoft, for speed, some routines were not implemented externally in the VBA ActiveX server but were kept internal—CInt is one of those routines.

Using Smoke and Mirrors

The best optimization is the perceived one. If you make something look or feel fast, it will generally be perceived as being fast. Give your users good feedback. For example, use a progress bar. Your code will actually run slower (it's having to recalculate and redraw the progress bar), but the user's perception of its speed, compared to not having the progress bar, will almost always be in your favor.

One of the smartest moves you can ever make is to start fast. (Compiling to native code creates "faster to start" executables.) Go to great lengths to get that first window on the screen so that your users can start using the application. Leave the logging onto the database and other such tasks until after this first window is up. Look at the best applications around: they all start, or appear to start, very quickly. If you create your applications to work the same way, the user's perception will be "Wow! This thing is usable and quick!" Bear in mind that lots of disk activity before your first window appears means you're slow: lots after, however, means you're busy doing smart stuff!

Because you cannot easily build multithreaded Visual Basic applications (see Chapter 13 to see some light at the end of this particular tunnel), you might say that you'll have to block sometime; that is, you're going to have to log on sometime, and you know that takes time—and the user will effectively be blocked by the action. Consider putting the logging on in a separate application implemented as an out-of-process ActiveX server, perhaps writing this server to provide your application with a set of data services. Use an asynchronous callback object to signal to the user interface part of your application when the database is ready to be used. When you get the signal, enable those features that have now become usable. If you take this approach, you'll find, of course, that the data services ActiveX server is blocked—waiting for the connection—but your thread of execution, in the user interface part of the application, is unaffected, giving your user truly smooth multitasking. The total effort is minimal; in fact, you might even get some code reuse out of the ActiveX server. The effect on the user's perception, however, can be quite dramatic.

As I've said before, compiled code is faster than p-code, so of course, one "easy" optimization everyone will expect to make is to compile to native code. Surely this will create faster-executing applications when compared to a p-code clone?

Using the TimeGetTime routine, we do indeed see some impressive improvements when we compare one against the other. For example the following loop code, on my machine (300 MHz, Pentium Pro II, 128 MB RAM), takes 13.5 milliseconds to execute as compiled p-code and just 1.15 milliseconds as native code—almost 12 times faster (optimizing for fast code and the Pentium Pro). If this kind of improvement is typical, "real" compilation is, indeed, an easy optimization.

 Dim n As Integer Dim d As Double For n = 1 To 32766     ' Do enough to confuse the optimizer.     d = (n * 1.1) - (n * 1#) Next 


Ltd Mandelbrot Set International Advanced Microsoft Visual Basics 6. 0
Advanced Microsoft Visual Basic (Mps)
ISBN: 1572318937
EAN: 2147483647
Year: 1997
Pages: 168

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net