Slow VB6 String Operations on Windows 7 - performance

We have a VB6 program that does some string processing in a loop (approximately 500 times) and appends info to a textbox. Each iteration on the loop includes basic operations like Trim, Len, Left, Mid, etc, and finally appends the string to a textbox (the whole form is still invisible at this point). Finally, after the loop, the code calls Show on the form.
On Windows XP, these 500 loops take about 4 seconds. On Windows 7, the exact same code runs in about 90 seconds.
Any suggestions on how to fix this?
Thanks.

I'm guessing you append the text box on each loop iteration... If you can, store everything in a variable and append it to the TextBox once after the loop is finished. Displaying text in a text box takes a lot of time in VB6.
EDIT:
After further investigation and testing, I came to a conclusion that performance on directly assigning strings to the Text property of a TextBox degrades dramatically when the length of the control reaches maximum. The maximum on my PC is 65535 for some reason, even though according to MSDN its
Windows NT 4.0, Windows 2000, Windows 2000 Professional, Windows 2000 Server, Windows 2000 Advanced Server, Windows XP Home Edition, Windows XP Professional x64 Edition, Windows Server 2003 Platform Note: For single line text box controls, if the MaxLength property is set to 0, the maximum number of characters the user can enter is 2147483646 or an amount based on available memory, whichever is smaller
Basically what seems to be happening, if you keep adding text to the TextBox each iteration, it isn't that slow until you reach maximum. What's even more puzzling, when you try to add text beyond the maximum, there will be no errors, but performance degrades significantly.
In my test loop I go from 0 to 12773 I have this:
Text2.Text = Text2.Text + CStr(a) + " "
So when the loop is completed in 4 seconds, the Text2.Text is 65534 characters long. Now, when I double the loop to go beyond the maximum allowed length of the TextBox, it takes three times as much time to complete it.
12773 - 4 seconds
12773*2 - 16 seconds
After realizing this, my first thought was to replace the TextBox with a RichTextBox. But the performance of the latter is even worse. This is assuming you update it every iteration.
It seems that you are stuck with a dilemma - suffer slow performance or change the code to update text box only once after the loop is completed. Further, due to TextBox's maximum length limit, I recommend switching to a RichTextBox or depending on the purpose of this - some other object.
I hope my findings are helpful - it has certainly been fun finding out all these little programming quirks.

Try LockWindowUpdate to switch off updating for your form.
Declare Function LockWindowUpdate Lib "user32" (ByVal hWnd As Long) As Long
'To turn it on just call it like this, passing it the hWnd of the window to lock.
LockWindowUpdate Form1.hWnd
'intensive updating here
'to turn it off just call it and pass it a zero.
LockWindowUpdate 0
From here

I would recommend that you find out exactly what is slow. Redo your timings before and after the string concatenation, and then before and after the copy of the string into the text box. Have the Ole Automation string operations somehow become slower, or has the copying of text into a VB text box become slower?
Once you know this, we can continue with phase 2 ... :-)

Related

autohotkey script speed & prevent spam click

First question is: Is this script as fast as possible as it can get?
I'm talking about the commands in the start are they necessary/unnecessary? do they help at all when its about simple key remapping?
I would like it to run as fast as possible since I do pretty intense & fast stuff with this script.
The 2nd question is: How to prevent this script from spamclicking?
If I keep "E or R" held down, it will spam click which I do not want it to do.
How to fix this?
#NoEnv
#MaxHotkeysPerInterval 99000000
#HotkeyInterval 99000000
#KeyHistory 0
ListLines Off
Process, Priority, , A
SetBatchLines, -1
SetKeyDelay, -1, -1
SetMouseDelay, -1
SetDefaultMouseSpeed, 0
SetWinDelay, -1
SetControlDelay, -1
SendMode Input
*e::Click
return
*r::Click
return
Ins::Suspend
return
Answer to the second question, add the KeyWait command to your hotkeys, KeyWait - Syntax & Usage | AutoHotkey
*e::
Click
KeyWait, e
return
*r::
Click
KeyWait, r
return
Answer to the first question, it looks like you may have gotten the header lines from here: How to optimize the speed of a script as much as possible. The post explains each line (see below) and even includes some benchmarks for other things.
In your case, if you're only doing simple key remapping, it's probably not worth including any of these lines with exception to SendMode , Input, as that is the most reliable send mode. You mention doing "pretty intense & fast stuff with this script"; what this means exactly will determine if the header lines are necessary.
Notes
1. #NoEnv is recommended for all scripts, it disables environment variables.
2. The default #MaxHotkeysPerInterval along with #HotkeyInterval will stop your script by showing message boxes if you have some kind of rapid autofire loop in it. Just put some insane unreachable high number to ignore this limit.
3. ListLines and #KeyHistory are functions used to "log your keys". Disable them as they're only useful for debugging purposes.
4. Setting an higher priority to a Windows program is supposed to improve its performance. Use AboveNormal/A. If you feel like it's making things worse, comment or remove this line.
5. The default SetBatchLines value makes your script sleep 10 milliseconds every line. Make it -1 to not sleep (but remember to include at least one Sleep in your loops, if any!)
6. Even though SendInput ignores SetKeyDelay, SetMouseDelay and SetDefaultMouseSpeed, having these delays at -1 improves SendEvent's speed just in case SendInput is not available and falls back to SendEvent.
7. SetWinDelay and SetControlDelay may affect performance depending on the script.
8. SendInput is the fastest send method. SendEvent (the default one) is 2nd place, SendPlay a far 3rd place (it's the most compatible one though). SendInput does not obey to SetKeyDelay, SetMouseDelay, SetDefaultMouseSpeed; there is no delay between keystrokes in that mode.
9. If you're not using any SetTimer, the high precision sleep function is useful when you need millisecond reliability in your scripts. It may be problematic when used with SetTimer in some situations because this sleep method pauses the entire script. To make use of it, here's an example that waits 16,67 milliseconds:
DllCall("Sleep",UInt,16.67)
10. When using PixelSearch to scan a single pixel of a single color variation, don't use the Fast parameter. According to my benchmarks, regular PixelSearch is faster than PixelSearch Fast in that case.
11. According to the documentation (this text is found in the setup file), the Unicode x64bit version of AHK is faster, use it when available.

How to profile/debug VBA script that works fine on XLS files, but hangs on XLSX files?

I have a VB script that does row processing on a small Excel file (35 columns, 2000 rows, no hidden content). It takes about 3 seconds to process the file when applied to an .xls file. If I save the .xls as a .xlsx file, the same script takes about 30 to 60 seconds, completely freezing Excel several times in the process (although it finishes and the results are correct).
I suspect some loop parts of the code are responsible, but I need to find out which. I have found scripts like https://stackoverflow.com/a/3506143/5064264 which basically amount to timing all parts of the script by hand - similar to printf-debugging, which is not very elegant and also consumes much time for large scripts.
Is there a better alternative to semi-manual profiling, like performance analyzers in most IDEs, or exist any other approaches to this problem? I do not want to post the code as it is rather long and I also want to solve it for other scripts in the future.
yes - kind of. I use this to test how fast things are running and experiment with different approaches to get better timings. I stole this from somewhere here on SO.
sub thetimingstuff()
Dim StartTime As Double
Dim SecondsElapsed As Double
StartTime = Timer
'your code goes here
SecondsElapsed = Round(Timer - StartTime, 2)
MsgBox "This code ran successfully in " & SecondsElapsed & " seconds", vbInformation
end sub
Another EDIT
you could bury debug.print lines sporadically through your code to see how much each chunk takes to execute.
The error in my case was as Comintern's comment suggested - a method was copying whole columns into a dictionary to reorder them, instead of just the cells with values in them. To preserve this for future readers, I am copying his/her excellent comment here, thanks again!
An xls file has a maximum of 65,536 rows. An xlsx file has a maximum of 1,048,576 rows. I'd start by searching for .Rows. My guess is that you have some code iterates the entire worksheet instead of only rows with data in them.
As for generic profiling or how to get there, I used a large amount of breakpoints in the editor (set/remove with left-click in the line number column of the code editor):
I first set them every 20 lines apart in the main method, ran the program with the large dataset, then after each press of continue (F5) manually measured if it was slow or not.
Then I removed the breakpoints in the fast regions and added additional ones in the slow regions to further narrow it down into methods and then into lines inside the methods.
At the end, I could verify if by commenting out the responsible line of code and fix it.

Excel/VBA performance

I have a certain set of formula in Range("E10001:E20000") which i'd like to copy to Columns("F:CZ") total 1M cells. Options:
Range("E10001:E20000").Copy
Range("F10001:CZ20000").PasteSpecial xlPasteAll
Range("F10001:CZ20000").formula = Range("E10001:E20000").formula
Range("E10001:CZ20000").fillRight
Range("E10001:CZ20000").Select
followed by Ctrl+R in Excel
At the end, to make the sheet light, i replace formulae with Values,
Range("F10001:CZ20000").Value = Range("F10001:CZ20000").Value
What i notice is that Option 4 is way faster than the rest. Can anybody explain the performance penalties in the first 3 options. Note, i'm unfamiliar with time functions and measured physically using seconds clock.
Short answer: Do not use shortcuts with the SendKeys method!
Longer answer: Shortcuts can lead to unexpected results, in my local settings "^r" would (start to) insert a table.
Feel free to use a timer with starttime = Now at the beginning of your code and
timetaken = Now - starttime
MsgBox (Hour(timetaken) & ":" & Minute(timetaken) & ":" & Second(timetaken))
to measure the performance though.
As a rule of thumb, your time is worth way more than system time and corrupting your data by accidentally sending the wrong set of instructions is never worth the risk.
Your operation with 2 × 10,000 cells is not worth optimising.

Windows 7 file problem

I am using VB6 SP6
This code has work correctly for years but I am now having a problem on a WIN7 to WIN7 network. It also works correctly on an XP to Win7 network.
Open file for random as ChannelNum LEN =90
'the file is on the other computer on the network
RecNum = (LOF(ChannelNum) \ 90) + 2
Put ChannelNum, RecNum, MyAcFile
'(MyAcFile is UDT that is less than 90 long)
.......... other code that does not reference file or RecNum - then
RecNum = (LOF(ChannelNum) \ 90) + 2
Put ChannelNum, RecNum, MyAcFile
Close ChannelNum
The second record overwrites the first.
We had a similar problem in the past with OpportunisticLocking so we turn that off at install - along with some other keys that cause errors in data in Windows networks.
However we have had no problems like this for years, so I think MS have some new "better" option that they think will "improve" networking.
Thanks for your help
I doubt there is any "bug" here except in your approach. The file metadata that LOF() interrogates is not meant to be updated immediately by simple writes. A delay seems like a silly idea, prone to occasional failure unless a very long delay is used and sapping performance at best. Even close/reopen can be iffy: VB6's Close statement is an async operation. That's why the Reset statement exists.
This is also why things like FlushFileBuffers() and SetEndOfFile() exist at the API level. They are also relatively expensive operations from a performance standpoint.
Track your records yourself. Only rely on LOF() if necessary after you first open the file.
Hmmm... is file (as per in the open statement at the top of the code sample) UNC filename or similar to x:\ where x is the mapped drive? Are you not incrementing RecNum? Judging by the code, the RecNum is unchanged and hence appears to overwrite the first record...Sorry for sounding ummm no pun intended... basic...It would be of help to show some more code here...
Hope this helps,
Best regards,
Tom.
It can be just timing issue. In some runs your LOF() function returns more updated information than in other runs. The file system API is asynchronous, for example when some write function is called it will not be immediately reflected as the increazed size.
In short: you code have shown an old bug, which is just easier to reproduce on Windows 7.
To fix the bug the cheapest way: you may decide to add a delay (it can be significant delay of say 5 seconds).
More elaborate fix is to force the size update by closing and reopening file.

(MS-DOS) Time Delays

I came past a few ways to cause a time delay such as pings and dirs. Though none of them are really precise, is there anny proper way to cause a time delay?
I heard about a few things though they don't work on all computers, not on my Windows XP nor the Windows NT at college.
It takes ages going through all files on Google finding a good answer, and since I didn't yet find the question on Stack Overflow I thought it might be good to just create the question myself ;)
Sleep
It will allow you to do this.
<warning>This is a hack</warning>
Use your favorite programming language (other than MS-DOS batch) and create an application which takes one argument, the number of milliseconds to wait, then, simply call this program from a batch file to sleep the required amount.
As far as I know, this is the only reliable way to do it in DOS.
If you don't have the ability to send another program along with the batch file, use DEBUG to write a sleep command on the fly, and execute it.
EDIT:
The above answer was kind of toungue-in-cheek. I wouldn't run a batch file that had some DEBUG trickery in it. I believe the traditional way to use a delay in a batch file is the CHOICE commad.
type nul|choice /c:y /t:y,nn > nul
Which of course, doesn't work in XP, since that would be WAAYY too convenient.
"...proper way...."
you can't do that in DOS.
It is possible to achieve a precision of a few miliseconds, depending on your machine's speed.
I have just finished creating such a batch, and though I won't share with you the actual code, I'll give you some pointers:
Use %time% variable, and devide into substrings (without ":" and ".") - one substring will get the seconds, the other - minutes (you may add hours, for delays of over an hour, and even incorporate the date)
Use set /A to transform the time variables into 1 integer representing the amount of seconds which have passed since a rounded hour (X:00:00.00). Add the inputed delay (in seconds) to that.
Create a short loop, comparing the value of the combined var (explained in section 2) to the current time (need to recalc the curent combined min+sec value, inside this loop), and exiting the loop when the match is true.
One major bugfix here - you'll need to truncate any preceeding zeros to variables which are about to get evaluated in a "set /A" command. I've noticed that the console's evaluator (for this command) returns an error when an integer with a preceeding 08 or 09 (or simply 08 or 09) is used.
Remark:
This will only work with command extensions enabled. To test it, use the following at the beginning of the routine:
verify other 2>nul
setlocal enableextensions
if errorlevel 1 goto err
And then add an error handler subroutine named "err".
If you'd want to continue your batch in the same file, use "endlocal" after the looping subroutine.
BTW, this applies ONLY to Windows XP service pack 2 or 3.

Resources