Local Dim vs. Static variable - vb.net

From the IL perspective, what are the main differences (performance wise) between a local Dim variable and a local Static variable?
I've always thought that Dim would allocate storage space every time, whilst Static would allocate storage space only once, hence faster. But as you can see below, this is not the case.
A: Dim
B: Static
Public Class Form1
Public Sub New()
Me.InitializeComponent()
Dim b As New Button() With {.Text = "Run", .Height = 30, .Location = New Point(10, 10)}
AddHandler b.Click, AddressOf Me.Run
Me.Controls.Add(b)
End Sub
Private Sub Run(sender As Object, e As EventArgs)
Dim count As Integer = 10000
Dim watch As New Stopwatch()
Dim list As New Test(Of Control)(count)
Dim last As Control = list.Items(count - 1)
Dim a, b As Double, i As Integer
For i = 1 To 10000
watch.Restart()
list.IndexOfA(last)
a += watch.Elapsed.TotalMilliseconds
Next
For i = 1 To 10000
watch.Restart()
list.IndexOfB(last)
b += watch.Elapsed.TotalMilliseconds
Next
watch.Stop()
Array.ForEach(Of Control)(list.Items, Sub(c As Control) c.Dispose())
list = Nothing
MessageBox.Show(String.Format("A: {0}{1}B: {2}", a.ToString("F4"), Environment.NewLine, b.ToString("F4")))
End Sub
Public Class Test(Of T As {Class, New})
Public Sub New(count)
If (count < 0) Then Throw New ArgumentOutOfRangeException("count")
Dim items As T() = New T(count - 1) {}
For index As Integer = (count - 1) To 0 Step -1
items(index) = New T()
Next
Me.Items = items
End Sub
Public Function IndexOfA(item As T) As Integer
Dim index As Integer
Dim length As Integer
Dim item2 As T
length = (Me.Items.Length - 1)
For index = 0 To length
item2 = Me.Items(index)
If (item2 Is item) Then
Return index
End If
Next
Return -1
End Function
Public Function IndexOfB(item As T) As Integer
Static index As Integer
Static length As Integer
Static item2 As T
length = (Me.Items.Length - 1)
For index = 0 To length
item2 = Me.Items(index)
If (item2 Is item) Then
Return index
End If
Next
Return -1
End Function
Public ReadOnly Items As T()
End Class
End Class
Edit
As per comment, I've edited the code so it don't "restart the stopwatch at every loop iteration".
watch.Start()
For i = 1 To 10000
list.IndexOfA(last)
Next
watch.Stop()
a = watch.Elapsed.TotalMilliseconds
watch.Restart()
For i = 1 To 10000
list.IndexOfB(last)
Next
watch.Stop()
b = watch.Elapsed.TotalMilliseconds
The results are almost the same:
A: 358,3624
B: 538.8570

The main difference in performance is not how the variables are allocated, it's in how they are accessed.
Local variables are allocated on the stack, which takes no time at all. Literally. The allocation is done by moving the stack pointer, and that is done anyway to create a stack frame for the method, so allocating local variables doesn't take any more time.
Static variables in an instance method are allocated as part of the data for the class, and that is done only once in this case. Allocating another variable only means that more data is allocated, so that doesn't increase the time.
Accessing the variables is a different story. A local variable is accessed by addressing part of the stack frame, which is easy. A static variable on the other hand is accessed by getting a pointer to the static data of the class, and then addressing the variable at an offset from that. That means that every access to a static variable needs a few more instructions.

VB local static variables are allocated once per instance if they occur within an instance method. They are only allocated once overall if they occur within a Shared method.

Related

VBA class instances

I'm having an issue in VBA where every item in the array is being replaced every time i add something to that array.
I am attempting to go through the rows in a given range and cast every row of that into a custom class (named 'CustomRow' in below example). there is also a manager class (called 'CustomRow_Manager' below) which contains an array of rows and has a function to add new rows.
When the first row is added it works fine:
https://drive.google.com/file/d/0B6b_N7sDgjmvTmx4NDN3cmtYeGs/view?usp=sharing
however when it loops around to the second row it replaces the contents of the first row as well as add a second entry:
https://drive.google.com/file/d/0B6b_N7sDgjmvNXNLM3FCNUR0VHc/view?usp=sharing
Any ideas on how this can be solved?
I've created a bit of code which shows the issue, watch the 'rowArray' variable in the 'CustomRow_Manager' class
Macro file
https://drive.google.com/file/d/0B6b_N7sDgjmvUXYwNG5YdkoySHc/view?usp=sharing
otherwise code is below:
Data
A B C
1 X1 X2 X3
2 xx11 xx12 xx13
3 xx21 xx22 xx23
4 xx31 xx32 xx33
Module "Module1"
Public Sub Start()
Dim cusRng As Range, row As Range
Set cusRng = Range("A1:C4")
Dim manager As New CustomRow_Manager
Dim index As Integer
index = 0
For Each row In cusRng.Rows
Dim cusR As New CustomRow
Call cusR.SetData(row, index)
Call manager.AddRow(cusR)
index = index + 1
Next row
End Sub
Class module "CustomRow"
Dim iOne As String
Dim itwo As String
Dim ithree As String
Dim irowNum As Integer
Public Property Get One() As String
One = iOne
End Property
Public Property Let One(Value As String)
iOne = Value
End Property
Public Property Get Two() As String
Two = itwo
End Property
Public Property Let Two(Value As String)
itwo = Value
End Property
Public Property Get Three() As String
Three = ithree
End Property
Public Property Let Three(Value As String)
ithree = Value
End Property
Public Property Get RowNum() As Integer
RowNum = irowNum
End Property
Public Property Let RowNum(Value As Integer)
irowNum = Value
End Property
Public Function SetData(row As Range, i As Integer)
One = row.Cells(1, 1).Text
Two = row.Cells(1, 2).Text
Three = row.Cells(1, 3).Text
RowNum = i
End Function
Class module "CustomRow_Manager"
Dim rowArray(4) As New CustomRow
Dim totalRow As Integer
Public Function AddRow(r As CustomRow)
Set rowArray(totalRow) = r
If totalRow > 1 Then
MsgBox rowArray(totalRow).One & rowArray(totalRow - 1).One
End If
totalRow = totalRow + 1
End Function
Your issue is using
Dim cusR As New CustomRow
inside the For loop. This line is actually only executed once (note that when you single F8 step through the code it does not stop on that line)
Each itteration of the For loop uses the same instance of cusR. Therefore all instances of manager added to the class point to the same cusR
Replace this
For Each row In cusRng.Rows
Dim cusR As New CustomRow
with this
Dim cusR As CustomRow
For Each row In cusRng.Rows
Set cusR = New CustomRow
This explicitly instantiates a new instance of the class

More effective loop

I'm making a player-match-up program in Visual Basic. The program is supposed to pick random registered players and and pair them. I'm currently working on the odd-number-of-players-part.
The solution I have is working but is perhaps not that effective. Is there a better way for me to write this code?
The code is supposed to pick the random players and make sure they are not picked again. As you see, for the code to work i must make it loop thousands of times. If I don't some of the players won't show up in the listbox. Is there a better solution???
In case it's confusing "spiller" is norwegian for "player"
For i As Integer = 0 To 100000
Dim spiller1 As Integer
Dim spiller2 As Integer
Do
spiller1 = CInt(Math.Floor(Rnd() * spillerListe.Count))
spiller2 = CInt(Math.Floor(Rnd() * spillerListe.Count))
Loop Until CBool(spiller1 <> spiller2)
If brukteSpillere(spiller1) = False And brukteSpillere(spiller2) = False Then
brukteSpillere(spiller1) = True
brukteSpillere(spiller2) = True
lstSpillere.Items.Add(spillerListe(spiller1).ToString + " VS. " + spillerListe(spiller2).ToString())
End If
Next i
This is a mess... Have a List(Of Integer) with all the available index.
Loop while availableIndex.Count > 1
Pick a random index from availableIndex and remove it from that list
Pick a random index from availableIndex and remove it from that list
Add these two index to the list of pairs
End Loop
that way you don't need to check if the random values are the same or if they were already picked.
Now, if you don't want to create a list. Then threat the random number not as an index, but as the number of items to check.
Delta = RandomNumber
x = 0
For i As Integer = 0 To itemList.Count-1
If Not itemList(i).IsChoosen Then
x += 1
If x = Delta Then
' i is now your item to pick
itemList(i).IsChoosen = True
Exit For
End If
End If
Next
There are two efficient ways in approaching this problem:
Sort your player list by random number, then match up 1 with 2, 3 with 4 and so on.
Dim r As New Random
Dim randomListe = spillerListe.OrderBy(Function() r.Next).ToList
Generate two random numbers from your range, match up those players into a separate List, remove players from the original list. General two more random numbers from a smaller range (original minus 2), match up, etc.
EDIT: Having looked at MSDN, List has O(n) performance for RemoveAt, so it's not quite efficient, better be using a dictionary, which is O(1) at removing items, so instead of spillerListe have some spillerDicte, where you would add entries in a form (key = index, value = item).
Instead of working with integers, what if you keep your players name in a list and, after picking a player you remove it from the list. Probably this will not be the best performant solution, but it is clear what are you trying to do
Dim lstSpillere = new List(Of String)() ' Just for the example below
Dim spillerListe = new List(Of String)() from {"Marc", "John", "Steve", "George", "David", "Jeremy", "Andrew" }
Dim rnd = new Random()
While spillerListe.Count > 1
Dim firstPlayer = spillerListe(rnd.Next(0, spillerListe.Count))
spillerListe.Remove(firstPlayer)
Dim secondPlayer = spillerListe(rnd.Next(0, spillerListe.Count))
spillerListe.Remove(secondPlayer)
lstSpillere.Add(firstPlayer + " VS. " + secondPlayer)
' for debug purpose....
Console.WriteLine(firstPlayer & " VS. " & secondPlayer)
End While
if spillerListe.Count > 0 Then
Console.WriteLine("Excluded from play list is:" & spillerListe(0))
End if
The important key here is the generation of Random instance that should be outside the loop to avoid to generate the same number in the short time period required by the loop to execute.
Try this:
Module Module1
Dim rnd As New Random
Sub Main()
Dim RegisteredPlayers As New List(Of Player)
' Fill List (example 100 players)
For index As Integer = 1 To 100
RegisteredPlayers.Add(New Player(String.Format("Player{0}", index)))
Next
'Sort Players using a random number
Dim SortedPlayersArray = RandomSortItems(RegisteredPlayers.ToArray())
'Pair players by selecting 2 consequative ones from randomly sorted array
Dim Matches As New List(Of Match)
For index As Integer = 1 To SortedPlayersArray.Length Step 2
Dim m As Match = New Match(SortedPlayersArray(index - 1), SortedPlayersArray(index))
Matches.Add(m)
Debug.WriteLine(m.ToString())
Next
' Match Player48 vs. Player79
' Match Player3 vs. Player53
' Match Player18 vs. Player43
' Match Player85 vs. Player1
' Match Player47 vs. Player56
' Match Player23 vs. Player66
' etc..
End Sub
Public Function RandomSortItems(Of T)(ByVal items As T()) As T()
Dim sorted As T() = New T(items.Length-1) {}
Array.Copy(items, sorted, sorted.Length)
Dim keys As Double() = New Double(items.Length-1) {}
For i As Integer = 1 To items.Length
keys(i - 1) = rnd.NextDouble()
Next
Array.Sort(keys, sorted)
Return sorted
End Function
End Module1
Public Class Player
Dim m_name As String
Public Sub New(ByVal player_name As String)
m_name = player_name
End Sub
Public ReadOnly Property Name() As String
Get
Return m_name
End Get
End Property
Public Overrides Function ToString() As String
Return m_name
End Function
End Class
Public Class Match
Dim m_player_1 As Player, m_player_2 As Player
Public Sub New(ByVal player_1 As Player, ByVal player_2 As Player)
m_player_1 = player_1
m_player_2 = player_2
End Sub
Public ReadOnly Property Player1() As Player
Get
Return m_player_1
End Get
End Property
Public ReadOnly Property Player2() As Player
Get
Return m_player_2
End Get
End Property
Public Overrides Function ToString() As String
Return String.Format("Match {0} vs. {1}", Player1, Player2)
End Function
End Class
Edit 1:
An alternate random sorter (which should be faster) is
Public Function RandomSortItems(Of T)(ByVal items As T()) As T()
Dim slist As New SortedList(Of Double, T)
For i As Integer = 1 to items.Length
slist.Add(rnd.NextDouble(), items(i-1) )
Next i
return slist.Values.ToArray()
End Function

Different Results using Parallel foreach everytime Excel Worksheet is read

Imports System.IO
Imports System.Threading
Imports System.Threading.Tasks
Imports System.Collections.Concurrent
Imports Excel = Microsoft.Office.Interop.Excel
Public Class TestCarDatas
Public Property RowID As Integer
Public Property ModelYear As Integer
Public Property VehMfcName As String
Public Property EmgVeh As Boolean
End Class
Module ExcelParallelDataGather2
Public Const ExcelVehDataPath As String = "D:\Users\Dell\Desktop"
Public rwl As New System.Threading.ReaderWriterLock()
Public rwl_writes As Integer = 0
Public FullTestVehData As New List(Of TestCarDatas)()
Public x1App As New Excel.Application
Public x1Workbook As Excel.Workbook
Public x1Worksheet1 As Excel.Worksheet
Public x1WkshtLrow As Integer
Sub main()
x1App.Visible = False
ErrorNotify = False
Console.WriteLine("Excel Parallel foreach operation program....")
Dim cki As ConsoleKeyInfo
x1Workbook = x1App.Workbooks.Open(Path.Combine(ExcelVehDataPath, "TestCarDatabase2014.xls"), , True)
x1Worksheet1 = x1Workbook.Sheets(1)
Do
Console.WriteLine("Press escape key to exit, 'a' key to reiterate, 'c' key to clear console")
cki = Console.ReadKey()
If Chr(cki.Key).ToString = "A" Then
Console.WriteLine("--> Processing...")
FullTestVehData.Clear()
rwl_writes = 0
Parallel.ForEach(Partitioner.Create(11, x1WkshtLrow + 1), _
Function()
' Initialize the local states
Return New List(Of TestCarDatas)()
End Function, _
Function(partrange, loopState, localState)
' Accumulate the thread-local computations in the loop body
localState = populateCardata(x1Worksheet1, partrange.Item1, partrange.Item2)
Return (localState)
End Function, _
Sub(finalstate)
' Combine all local states
Try
rwl.AcquireWriterLock(Timeout.Infinite)
Try
' It is safe for this thread to read or write
' from the shared resource in this block
FullTestVehData.AddRange(finalstate)
Interlocked.Increment(rwl_writes)
Finally
' Ensure that the lock is released.
rwl.ReleaseWriterLock()
End Try
Catch ex As ApplicationException
' The writer lock request timed out.
End Try
End Sub)
End If
If Chr(cki.Key).ToString = "C" Then
Console.Clear()
Console.WriteLine("Excel Parallel foreach operation program....")
End If
If Chr(cki.Key).ToString <> "A" And Chr(cki.Key).ToString <> "C" And _
cki.Key <> ConsoleKey.Escape Then
Console.WriteLine("")
Console.WriteLine("Invalid response via key press")
End If
Loop While (cki.Key <> ConsoleKey.Escape)
End Sub
Friend Function populateCardata(ByVal WksheetObj As Excel.Worksheet, ByVal rngStart As Integer, _
ByVal rngStop As Integer) As List(Of TestCarDatas)
Dim wkrng(12) As String
Dim PartVehData As New List(Of TestCarDatas)
PartVehData.Clear()
For i As Integer = rngStart To rngStop - 1
Dim data As New TestCarDatas
For j As Integer = 0 To 12
wkrng(j) = WksheetObj.Cells(i, j + 1).Address(RowAbsolute:=False, ColumnAbsolute:=False)
Next
With data
.RowID = i
.ModelYear = WksheetObj.Range(wkrng(0)).Value2
.VehMfcName = WksheetObj.Range(wkrng(1)).Value2
If WksheetObj.Range(wkrng(11)).Value2 = "Y" Then
.EmgVeh = True
Else
.EmgVeh = False
End If
End With
PartVehData.Add(data)
Next
Return PartVehData
End Function
End Module
I am trying to get the Excel Worksheet data using parallel foreach and range Partitioner, creating lists in thread local storage and finally adding them using thread safe methods like synclock or reader-writer locks
Rows from 11 to last row in worksheet are to be read and populated in a List(of T)
I observe following when I execute above code
When rows in worksheet are greater(example >2000), this code works as expected everytime
When rows in worksheet are less, this code returns partial list during first few iteration (data from some of the partitioner ranges are lost). If I re-iterate it (pressing key 'a') multiple times, then sometimes it returns expected results (final list count = no. of excel rows required to be read)
Why this phenomenon is observed?
What is the solution using parallel foreach, if I need correct results during first run/iteration irrespective of no. of rows in worksheet?

What's the proper use of WaitOne() function

I was experimented some Thread pool examples. I've started from Fibonacci example on MSDN web site, but this wasn't suitable for more than 64 calculations, so i've resolved with this code:
Imports System.Threading
Module Module1
Public Class Fibonacci
Private _n As Integer
Private _fibOfN
Private _doneEvent As ManualResetEvent
Public ReadOnly Property N() As Integer
Get
Return _n
End Get
End Property
Public ReadOnly Property FibOfN() As Integer
Get
Return _fibOfN
End Get
End Property
Sub New(ByVal n As Integer, ByVal doneEvent As ManualResetEvent)
_n = n
_doneEvent = doneEvent
End Sub
' Wrapper method for use with the thread pool.
Public Sub ThreadPoolCallBackMar(ByVal threadContext As Object)
Dim threadIndex As Integer = CType(threadContext, Integer)
Console.WriteLine("thread {0} started...", threadIndex)
_fibOfN = Calculate(_n)
Console.WriteLine("thread {0} result calculated...", threadIndex)
_doneEvent.Set()
End Sub
Public Function Calculate(ByVal n As Integer) As Integer
If n <= 1 Then
Return n
End If
Return Calculate(n - 1) + Calculate(n - 2)
End Function
End Class
<MTAThread()>
Sub Main()
Const FibonacciCalculations As Integer = 65
' One event is used for each Fibonacci object
Dim doneEvents(FibonacciCalculations) As ManualResetEvent
Dim fibArray(FibonacciCalculations) As Fibonacci
Dim r As New Random()
' Configure and start threads using ThreadPool.
Console.WriteLine("launching {0} tasks...", FibonacciCalculations)
For i As Integer = 0 To FibonacciCalculations
doneEvents(i) = New ManualResetEvent(False)
Dim f = New Fibonacci(r.Next(20, 40), doneEvents(i))
fibArray(i) = f
ThreadPool.QueueUserWorkItem(AddressOf f.ThreadPoolCallBackMar, i)
Next
Console.WriteLine("All calculations are complete.")
For i As Integer = 0 To FibonacciCalculations
doneEvents(i).WaitOne()
Dim f As Fibonacci = fibArray(i)
Console.WriteLine("Fibonacci({0}) = {1}", f.N, f.FibOfN)
Next
Console.Read()
End Sub
End Module
The use of WaitOne() instead of WaitAll() resolve the problem but the question is: If I don't need to display the results then I don't need neither the second loop, but... without the second loop where I've to put the waitOne() function?
Your code does essentially this:
// start a bunch of threads to do calculations
Console.WriteLine("All calculations are complete."); // This is a lie!
// Wait for the threads to exit
The primary problem here is that the calculations are not complete when you make that call to Console.WriteLine. Well, they might be complete, but you don't know unless you've waited on the event to see that it's signaled.
The purpose of WaitOne is to tell you if the calculation has completed. Your code should be written like this:
For i As Integer = 0 To FibonacciCalculations
doneEvents(i) = New ManualResetEvent(False)
Dim f = New Fibonacci(r.Next(20, 40), doneEvents(i))
fibArray(i) = f
ThreadPool.QueueUserWorkItem(AddressOf f.ThreadPoolCallBackMar, i)
Next
Console.WriteLine("All calculations are started. Waiting for them to complete.")
For i As Integer = 0 To FibonacciCalculations
doneEvents(i).WaitOne()
Dim f As Fibonacci = fibArray(i)
Console.WriteLine("Fibonacci({0}) = {1}", f.N, f.FibOfN)
Next
Console.WriteLine("All calculations are complete.")
You must check the event to know that the calculation is complete.
Now, if you don't need to know if the calculation is complete, then there's no need for the WaitOne at all. And if you're not going to wait on the event, then there's no real need to have the event, is there? Although one wonders why you're going to do a calculation and then not use the result.

Maintain local variable value between calls in vb.net

Public Function MethodOne(ByVal s As String) As String
Dim sb As New StringBuilder()
sb.Append(s)
sb.Append(MethodTwo())
return sb.ToString()
End Function
Public Function MethodTwo() As String
Dim i As Integer = 0
For index As Integer = 0 To 5
i = index
Next
return i.ToString()
End Function
I want to retain the value of i, but once it goes back into MethodOne, it loses its value. I tried making it static i As integer = 0, but this did not work.
sorry misread that. How about creating a property called Count, and update it whenever MethodTwo is called. You can use the Property Count in MethodTwo instead of i.
Public Function MethodOne(ByVal s As String) As String
Dim sb As New StringBuilder()
sb.Append(s)
sb.Append(MethodTwo())
return sb.ToString()
End Function
Public Property Count As Integer
'Count will be zero when initialized
Public Function MethodTwo() As String
'Dim i As Integer = 0
For index As Integer = 0 To 5
Count = Count + index
Next
return Count.ToString()
End Function
Consider this example which is a bit different than yours (adds 5 to i instead of setting a value of 5)
Public Function MethodOne(ByVal s As String) As String
Dim sb As New StringBuilder()
sb.Append(s)
sb.Append(MethodTwo())
return sb.ToString()
End Function
Public Function MethodTwo() As String
Static i As Integer = 0
i+=5
return i.ToString()
End Function
Now, on the first run i will be set to its static value, which is 0. It will be incremented by 5, so the value will be 5. On the second one, the value of i is still 5, and it will be incremented by 5. The new value will be 10.
In your example, i was always set to 5, so it didn't change anything if you retained the value or not.
Edit after question changed:
What you want to do is have a class member, not a method variable. If the value is still 0 once the method has run, then there are two possible reasons for this. Either:
The variable is never set (AgeQualifyingCode is never 8 or 10)
The variable is set to 0 inside the method.
You can find out what is happening with some debugging with breakpoints.