views:

1185

answers:

3

Reviewing code, I came across a number of new Private Shared variables (of type Hashtables(Of String), initialized in the declaration) added to a partial class for a very large (DataContext-derived) class. This seems sensible to me in one sense because they never change, and making these shared variables ensures that they won't get re-initialized every time a function is called. However, these variables are only used within the scope of one function in the class, and I fear the private namespace of this DataContext-derived class is getting rather polluted, and having these sorts of things exposed at such a high level might be confusing to others reading the code in the future. Would there be negative performance impact to making these local variables within the function where they are used, or is there some better way to handle this? Basically we are using these 3 hashtables to determine whether anything within particular subsets of properties changed (using GetModifiedMembers and then using the Overlaps function of the hashset to see if any of the modified members correspond to members we care about).

Edit: I caved and took the time to write my own test program, which confirmed that there is a cost to using local variables (which I assume applies generally to all cases -- I doubt there's any case where a shared variable would be slower unless using the shared variable requires some additional logic to do so properly):

Sub Main()
  Dim st As New Stopwatch()
  Dim specialCount As Integer = 0
  Dim r As New Random()
  Dim params As Integer()
  ReDim params(0 To 9)
  st.Start()
  For i As Integer = 1 To 100000
     For j As Integer = 0 To 9
        params(j) = r.Next(100)
     Next
     If IsSpecialShare(params) Then specialCount += 1
  Next
  st.Stop()

  Console.WriteLine("Shared: " & specialCount)
  Console.WriteLine(st.Elapsed.ToString())

  st.Reset()
  specialCount = 0

  st.Start()
  For i As Integer = 1 To 100000
     For j As Integer = 0 To 9
        params(j) = r.Next(100)
     Next
     If IsSpecialLocal(params) Then specialCount += 1
  Next
  st.Stop()

  Console.WriteLine("Local: " & specialCount)
  Console.WriteLine(st.Elapsed.ToString())

End Sub

Dim specialListShared As New HashSet(Of Integer)(New Integer() {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41})

Private Function IsSpecialLocal(ByVal paramList As IEnumerable(Of Integer)) As Boolean
  Dim specialListLocal As New HashSet(Of Integer)(New Integer() {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41})
  Return specialListLocal.Overlaps(paramList)
End Function

Private Function IsSpecialShare(ByVal paramList As IEnumerable(Of Integer)) As Boolean
  Return specialListShared.Overlaps(paramList)
End Function

Sample Output:

Shared: 75232
00:00:00.0408223
Local: 75018
00:00:00.1344628

So in this particular case, using the local variable costs about 200%. But in most cases (including my own), the time is probably negligible compared to the overall task. So I guess the question now becomes, how do people generally feel about improving code maintainability at the cost of negligible but known performance impacts?

A: 

When you say "shared" - is that VB-speak for static? If so, note that you could be in a bit of threading jeopardy if you aren't careful... and given the description, it sounds like this data may be mutable? Could be... "interesting" if you ever have multiple instances/threads.

Maybe I'm being overly paranoid - hard to tell without illustrative code...

Instance fields (rather than shared/static fields) would be my default assumption - but again, without context I can't be 100% sure.

Marc Gravell
Yes, VB.Net Shared = C# Static
Pondidum
or == should i say?
Pondidum
They should not change. See my comment "This seems sensible to me in one sense because they never change".
BlueMonkMN
Basically we want to know (in one case), have any of the properties named "SetupTime", "RunRate", "RunRateUnits" or "YieldPercentage" changed on a particular record, so we have Private Shared ItemRoutingStepInterestedChangeset As New HashSet(Of String)(New String() {"SetupTime", "RunRate", "RunRateUnits", "YieldPercentage"}), and then use Dim modifieds = (From pi In ItemRoutingSteps.GetModifiedMembers(routing) Select pi.Member.Name), and then If ItemRoutingStepInterestedChangeset.Overlaps(modifieds) Then ....
BlueMonkMN
+2  A: 

I'd say that you need to determine if those static variables really belong to the namespace, class or function. If they really are specific to the function, then I'd put them inside the function.

If you need variables that retain state even after the function goes out of scope, then you can declare them as Static. That rules out the problem of having these variables at the class scope.

Difference between Shared class variable, Static function variable, and Local function variable:

Shared: There will be only one copy of the variable amongst all class instances.

Static: There will be one copy of the variable per instance of your class.

Local: A copy of the variable will be created every time the function is called (but freed once function goes out of scope).

Use the one that makes sense in your situation.

Meta-Knight
I don't think I'm concerned about actual measurable performance here since the difference would probably be undetectably small. And there are performance based guidelines out there regardless of the need to profile individual cases. That's what I'm after here. For example, you should generally use strongly-typed variables instead of objects references to deal with value types where possible because of the cost of boxing and un-boxing. My question, along similar lines, is whether there is an appreciable cost to consider when using local variables rather than shared variables. Benefit/Cost?
BlueMonkMN
I edited my post with more details. Hope it helps.
Meta-Knight
Actually it's a constant, but (although VB.NET allows local constants) it's not possible to declare a constant with this kind of value, so we have to declare a variable. And then we run into a similar question: is the cost of the overhead of having the extra instances worth the benefit of un-polluting the namespace of the class. It really is a constant applicable only to that specific function. It's just a list of property names for properties whose values are being handled within that function.
BlueMonkMN
Then I'd declare it as a static local variable. The overhead is probably very small so it's worth un-polluting the namespace of the class, unless there are millions of objects of that type that are created. (1 million objects = 1 million copies of your static variable)
Meta-Knight
Thinking about it further I realized that this class (a DataContext-derived class) was a very widely used class whose memory footprint (for instances) I was trying to keep at an absolute minimum because the same class gets used for every type of transaction. So I'm back to thinking that a shared class member is the best solution available right now. Thanks for reminding me that static variables in instance methods allocate space on the instance.
BlueMonkMN
A: 

I almost forgot that VB.NET supports static local variables. The following test indicates that I can get performance just as good by using a static local variable:

Sub Main()
  Dim st As New Stopwatch()
  Dim specialCount As Integer = 0
  Dim r As New Random()
  Dim params As Integer()
  ReDim params(0 To 9)
  st.Start()
  For i As Integer = 1 To 100000
     For j As Integer = 0 To 9
        params(j) = r.Next(100)
     Next
     If IsSpecialShare(params) Then specialCount += 1
  Next
  st.Stop()

  Console.WriteLine("Shared: " & specialCount)
  Console.WriteLine(st.Elapsed.ToString())

  st.Reset()
  specialCount = 0

  st.Start()
  For i As Integer = 1 To 100000
     For j As Integer = 0 To 9
        params(j) = r.Next(100)
     Next
     If IsSpecialLocal(params) Then specialCount += 1
  Next
  st.Stop()

  Console.WriteLine("Local: " & specialCount)
  Console.WriteLine(st.Elapsed.ToString())

  st.Reset()
  specialCount = 0

  st.Start()
  For i As Integer = 1 To 100000
     For j As Integer = 0 To 9
        params(j) = r.Next(100)
     Next
     If IsSpecialStatic(params) Then specialCount += 1
  Next
  st.Stop()

  Console.WriteLine("Static: " & specialCount)
  Console.WriteLine(st.Elapsed.ToString())

End Sub

Dim specialListShared As New HashSet(Of Integer)(New Integer() {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41})

Private Function IsSpecialLocal(ByVal paramList As IEnumerable(Of Integer)) As Boolean
  Dim specialListLocal As New HashSet(Of Integer)(New Integer() {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41})
  Return specialListLocal.Overlaps(paramList)
End Function

Private Function IsSpecialShare(ByVal paramList As IEnumerable(Of Integer)) As Boolean
  Return specialListShared.Overlaps(paramList)
End Function

Private Function IsSpecialStatic(ByVal paramList As IEnumerable(Of Integer)) As Boolean
  Static specialListLocal As New HashSet(Of Integer)(New Integer() {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41})
  Return specialListLocal.Overlaps(paramList)
End Function

Sample output:

Shared: 74836
00:00:00.0498081
Local: 75205
00:00:00.1317149
Static: 74862
00:00:00.0469154

BTW, I've always wondered if C# supports static local variables, and if not, why not? There are a few annoying missing features in C# that VB.NET supports.

BlueMonkMN
This is no good. Static variables of non-shared functions allocate storage on every instance, which is (in our situation) the worst cost of all. I suppose we might be able to create another shared function that does the work with the static variables, but that function would still be "namespace pollution". I guess we'll have to stick with the shared class field for now.
BlueMonkMN