Difference in performance: two loops vs one loop - vba

Say I need to do two operations i times. Would it be better performance-wise to put both operations in a single for loop or create two separate for loops and but one operations in each?
Using two loops:
Sub Test()
Dim i As Long
Dim numbers() As Integer
Dim numbersMultiplied() As Integer
ReDim numbers(0 To 9) As Integer
ReDim numbersMultiplied(0 To 9) As Integer
For i = 0 To 9
numbers(i) = i
Next
For i = 0 To 9
numbersMultiplied(i) = numbers(i) * i
Debug.Print numbersMultiplied(i)
Next
End Sub
One loop:
Sub Test2()
Dim i As Long
Dim numbers() As Integer
Dim numbersMultiplied() As Integer
ReDim numbers(0 To 9) As Integer
ReDim numbersMultiplied(0 To 9) As Integer
For i = 0 To 9
numbers(i) = i
numbersMultiplied(i) = numbers(i) * i
Debug.Print numbersMultiplied(i)
Next
End Sub
Which one would be faster? Why? Or the performance of both ways is the same?

You can think about the fillowing parameters and assist your code
some CPUs contains hardware loops. This loop works as hardware cache for loop instructions but this requires that your loop don't exceed certain number of CPU instructions. Using hardware loops may increase your CPU performance.
Cache friendly code can increase the performance. In your code the single loop solution can make use of the value cached in this iteration - and it may be also in a register which is a more better thing - and this will reduce memory access time and enhance your performance
compiler optimization settings can also do things that you can't expect. In some cases the 2 loop solution may produce the same code as the single loop operation as the optimizer will detect that they are equivelant.
Those are only a few things to think about.

Related

Need a method for storing obscenely long numbers in number format (not scientific)

How would I go about storing a very large number in number format and not scientific.
Please bear in mind that the number I will be storing is too big for the Long data type.
I've set it as a String.
I have a userform with a command button and a textbox.
Below is the sample code:
Private Sub Cmd_Click()
Dim bits As Integer
Dim out As String
bits = 64
out = 2 ^ (bits - 1)
Txt_Output.Value = out
End Sub
The above will return: 9.22337203685478E+18.
But I want 9223372036854775807.
Can anyone explain how to avoid this?
Thanks in advance.
P.S. I'm hoping to avoid having to use an array.
You can achieve that specific calculation using Decimal data types and a modification to the calculation routine:
Private Sub Cmd_Click()
Dim bits As Integer
Dim out As Variant
Dim i As Long
bits = 64
out = CDec(1)
For i = 1 to bits - 1
out = out * 2
Next
Txt_Output.Value = out
End Sub
By forcing out to be a Variant/Decimal, the calculation does not lose precision as it is being calculated. However some things, such as CDec(2) ^ CDec(63) would still lose precision as the calculation would be done using an intermediate Double precision, so you will need to be very careful as to what calculations you do.
This might give you clues as to how to generalise that method to achieve what you need.
If you have 64-bit Excel, you can use the LongLong data type.

What does this Do While Loop code do?

I'm very new to Visual Basic and I'm having a hard time understanding "Do While" loops. The code I'm struggling with is below. Can someone walk me through what the code is doing? I understand the variables and assigning the label, I'm just not sure how to translate the loop.
Dim intSum As Integer
Dim intY As Integer
Do While intY < 3
For intX As Integer = 1 To 4
Next intX
intY += 1
Loop
lblSum.Text = intSum.ToString
Let's assume the example has simply been extracted from a larger program and the interesting bits of what the For loop and other stuff inside the While loop have been removed.
A Do While loop coded this way is similar to coding a For loop like:
For intY = 0 to 3
Next
But without actually specifying the starting condition for intY. It starts out as whatever value it has coming into the loop, which is sometimes handy.
The example doesn't modify intSum in any way, but I'm guessing that part of the code is just not included here.

VB.NET Is there a quicker way to scan through a RichTextBox?

I have a VB.NET application that I use to load various files into a RichTextBox and then scan through the document to find specific words. It's similar to the Find function in Word. The app was running fine until a 5,150 line .sql document run through it and it's taking upwards of 10 minutes to run to completion.
Can anyone recommend a better way of coding it than I have below?
If sqlText.Contains("GRANT") Then
Dim searchstring As String = "GRANT"
Dim count As New List(Of Integer)()
For i As Integer = 0 To rtbFile.Text.Length - 1
If rtbFile.Text.IndexOf(searchstring, i) <> -1 Then
count.Add(rtbFile.Text.IndexOf(searchstring, i))
End If
Next
Try
For i As Integer = 0 To count.Count - 1
rtbFile.Select(count(i), searchstring.Length)
rtbFile.SelectionBackColor = Color.Yellow
rtbFile.SelectionFont = New Font(rtbFile.Font, FontStyle.Bold)
count.RemoveAt(i)
Next
Catch ex As Exception
End Try
rtbFile.Select(rtbFile.Text.Length, 0)
rtbFile.SelectionBackColor = Color.White
rtbFile.SelectionFont = New Font(rtbFile.Font, FontStyle.Regular)
End If
That first loop is killing the performance, you are calling IndexOf for every character in the string. Also the two loops can be merged in to one. Change it to:
rtbFile.SelectionBackColor = Color.Yellow
rtbFile.SelectionFont = New Font(rtbFile.Font, FontStyle.Bold)
For Each m As Match in Regex.Matches(sertbFile.Text, searchstring)
rtbFile.Select(m.Index, searchstring.Length)
Next
This can also be done with a While loop and RichTextBox.Find():
Dim searchstring As String = "GRANT"
Dim index As Integer = rtbFile.Find(searchstring, 0, RichTextBoxFinds.None)
While index <> -1
rtbFile.Select(index, searchstring.Length)
rtbFile.SelectionBackColor = Color.Yellow
rtbFile.SelectionFont = New Font(rtbFile.Font, FontStyle.Bold)
index = rtbFile.Find(searchstring, index + searchstring.Length, RichTextBoxFinds.None)
End While
You've got a few bad things going on here:
First, the following code:
For i As Integer = 0 To rtbFile.Text.Length - 1
If rtbFile.Text.IndexOf(searchstring, i) <> -1 Then
count.Add(rtbFile.Text.IndexOf(searchstring, i))
End If
Next
This is looping through every character in your string, and calling IndexOf on the entire string from that point forward. So your 50,000-character string is running IndexOf 50,000 times, on large strings.
You only need to call IndexOf as many times as you find a string. When your string is found, you increment your start index to that point, and keep searching only from that point.
Next thing, this code:
For i As Integer = 0 To count.Count - 1
...
count.RemoveAt(i)
Next
The RemoveAt line is unnecessary. You're already looping through a list, so you don't need to remove the items as you go along. The way it stands, your loop will skip every other item in your list.
Whoops. I missed a very important point about the IndexOf (and incorrectly assumed it was fed with the end of the last match). See Magnus's answer.
I am not sure where the bottleneck is (and it might very well be from setting the selection itself), but here are my suggestions, roughly in order of priority:
Invoke rtbFile.Text once to avoid any roundtrips to underlying control (perhaps a native Windows control?) and use a variable to store the resulting string. Once the string is obtained in .NET, just keep using it directly unless/until the text may change. If the control is native then a lot of work may be required to simply "get the text".
Use normal item iteration over the count collection (not indexing) and do not remove from the front-of the List when assigning the selections. Removing from the front of a List is "expensive" in that it must shift all items down internally. Also, removing the element is unneeded here and dubious at best: since the collection being modified is also is being iterated which likely leads to incorrect behavior (skipped items), regardless of performance.
Only call IndexOf once per loop and use a variable to avoid a duplicate search. This likely won't have an overall impact, but it does avoid some "extra" work. IndexOf itself is fine and doesn't need to be replaced.
YMMV.

Funny Behavior of Excel VBA Random number routine

I am trying to generate a bunch of random permutations via the the following algorithm through vba:
Function RandNumber(Bottom As Integer, Top As Integer, _
Amount As Integer) As Integer()
Dim iArr As Variant
Dim i As Integer
Dim r As Integer
Dim temp As Integer
Dim bridge() As Integer
'Application.Volatile
ReDim iArr(Bottom To Top)
For i = Bottom To Top
iArr(i) = i
Next i
Randomize
For i = Top To Bottom + 1 Step -1
r = Int(Rnd() * (i - Bottom + 1)) + Bottom
temp = iArr(r)
iArr(r) = iArr(i)
iArr(i) = temp
Next i
ReDim Preserve bridge(1 To Amount)
For i = Bottom To Bottom + Amount - 1
bridge(i - Bottom + 1) = iArr(i)
Next i
RandNumber = bridge
End Function
What RandNumber essentially does is that it randomly gives a permutation based on the bottom and top values provided by the user. Example RandNumber(1,2,1) will either be 1 or 2 randomly.
Now I am testing this function through the following routine
Sub RandNos()
Dim z() As Variant
ReDim Preserve z(1 To 2560)
For i = 1 To 2560
z(i) = RandNumber(1, 2, 1)
Next i
For i = 1 To 2560
ThisWorkbook.Sheets("Sheet2").Range("A1").Offset(i - 1, 0) = z(i)
Next i
End Sub
To my utter amazement the 'random' numbers are repeating exactly after 256 runs! (Ok, the value 2560 for i above is not exactly a co-incidence. I was suspecting that there is some pattern going on around 256 rows and thus took i as 2560)
Moreover, when I test the above function with slightly modified subroutine:
Sub RandNos2()
For i = 1 To 2560
ActiveSheet.Range("A1").Offset((i - 1) Mod 256 + 1, Int((i - 1) / 256)) = RandNumber(1, 2, 1)
Next i
End Sub
the pattern is gone. (At least the pattern of repeating after 256 values is gone. Not sure if another pattern emerges).
Now based on my knowledge the Randomize is supposed to control the randomness by generating appropriate seeds and Randomize in vba takes the seed from the timer. So my hypothesis is that in the 1st RandNos() subroutine the updates happen so quickly that the seeds don't get updated fast enough. The fact that pattern is gone when I test with the second subroutine, since in the second routine excel takes longer to write the code in the worksheet and hence the gives the code some chance to update the timer and with it the seed of the Random numbers - supports my hypothesis.
So my question here is
Is my hypothesis correct.
Should I still hope to generate a 'random' pattern in Excel VBA.
Am I making a wrong use of Randomize here
Thanks in advance for your suggestions on the issue.
EDIT: One of the suggestions in the comment was that we should call Randomize only once. I tried doing that and it seems to work. However, I would still like to know what goes wrong using Randomize as above.
in answer to your edit, your hypothesis is correct.
Randomize takes the time and generates a seed. A seed is just a starting point for Rnd.
However, note that Randomize is not one-to-one which means that for any given input into Randomize (i.e. the time) it doesn't generate the same seed every time. You appear to have discovered that Randomize has a sequence of 256 seeds for every given input. Therefore, you get a repeating sequence of 256 numbers which were supposed to be random but which clearly are not.
Reference: The VBA help page for Randomize and CS classes
You should call Randomize once only. If RandNos() is called more than once, use Randomize in the method that calls RandNos().
just for future reference to anyone else who encounters this problem, I decided to just try "slowing down" the subroutine enough to allow the system timer to reset. Application.wait did not work well, but I found that by including a simple debug.print line above the randomize call, it slowed the execution down just enough to get it to not repeat every 256. This not dramatically increase the overall run time of the subroutine. Just a thought for folks who would not mind sacrificing a little bit of optimization for a very simple fix on the pseudo-randomness.

Same random number generated when run in test Fixture setup

I am trying to generate a random number during testing using NUnit, but it keeps generating the same number. I am using the following function for this purpose.
dim dCount As Integer = Math.Floor((High - Low + 1) * Rnd() + Low)
dim divName As String = "abc" & dCount
Any idea why it is doing this?
Regards,
Sam
Presumably you're executing many tests in quick succession. I don't know exactly what Rnd() does in VB, but it sounds like it's got the typical "new RNG per call" problem.
Create a single instance of Random and use it repeatedly. Note that your maths can be replaced by a simple:
dim dCount as Integer = myRandom.Next(Low, High+1)
One caveat - Random isn't thread-safe. If you need to generate random numbers from different threads, either use locking or thread statics.
On another point: using random numbers will make your unit tests non-deterministic. Are you sure you have to? Sometimes it's appropriate, but not often IME.
Dim dCount As Integer = between(low, high)
Dim divName As String = "abc" & dCount
Dim myRandom As New Random
Private Function between(ByVal low As Integer, ByVal high As Integer) As Integer
between = myRandom.Next(low, high + 1)
End Function