Chapter 8 (Performance and Memory) Samples
Listing 8.1. Ways of Deferred Loading, Caching, and Releasing Graphics Resources
Option Strict On Public Class GraphicsGlobals Private Shared s_Player_Bitmap1 As System.Drawing.Bitmap Private Shared s_Player_Bitmap2 As System.Drawing.Bitmap Private Shared s_Player_Bitmap3 As System.Drawing.Bitmap Private Shared s_Player_Bitmap4 As System.Drawing.Bitmap Private Shared s_colPlayerBitmaps As _ System.Collections.ArrayList '--------------------------------------------------- 'Releases all the resoruces '--------------------------------------------------- Public Shared Sub g_PlayerBitmapsCollection_CleanUp() 'If we don't have any bitmaps loaded, there is nothing 'to clean up If (s_colPlayerBitmaps Is Nothing) Then Return 'Tell each of these objects to free up 'whatever nonmanaged resources they are 'holding. s_Player_Bitmap1.Dispose() s_Player_Bitmap2.Dispose() s_Player_Bitmap3.Dispose() s_Player_Bitmap4.Dispose() 'Clear each of these variables, so they do not keep 'the objects in memory s_Player_Bitmap1 = Nothing s_Player_Bitmap2 = Nothing s_Player_Bitmap3 = Nothing s_Player_Bitmap4 = Nothing 'Get rid of the array list s_colPlayerBitmaps = Nothing End Sub '--------------------------------------------------- 'Function: Returns Collection of Bitmaps '--------------------------------------------------- Public Shared Function g_PlayerBitmapsCollection() _ As System.Collections.ArrayList '--------------------------------------------------- 'If we have already loaded these, just return them '--------------------------------------------------- If Not (s_colPlayerBitmaps Is Nothing) Then Return s_colPlayerBitmaps End If 'Load the bitmaps as resources from our executuable binary Dim thisAssembly As System.Reflection.Assembly = _ System.Reflection.Assembly.GetExecutingAssembly() Dim thisAssemblyName As System.Reflection.AssemblyName = _ thisAssembly.GetName() Dim assemblyName As String = thisAssemblyName.Name 'Load the bitmaps s_Player_Bitmap1 = New System.Drawing.Bitmap( _ thisAssembly.GetManifestResourceStream(assemblyName _ + ".Hank_RightRun1.bmp")) s_Player_Bitmap2 = New System.Drawing.Bitmap( _ thisAssembly.GetManifestResourceStream(assemblyName + _ ".Hank_RightRun2.bmp")) s_Player_Bitmap3 = New System.Drawing.Bitmap( _ thisAssembly.GetManifestResourceStream(assemblyName + _ ".Hank_LeftRun1.bmp")) s_Player_Bitmap4 = New System.Drawing.Bitmap( _ thisAssembly.GetManifestResourceStream(assemblyName + _ ".Hank_LeftRun2.bmp")) 'Add them to the collection s_colPlayerBitmaps = New System.Collections.ArrayList s_colPlayerBitmaps.Add(s_Player_Bitmap1) s_colPlayerBitmaps.Add(s_Player_Bitmap2) s_colPlayerBitmaps.Add(s_Player_Bitmap3) s_colPlayerBitmaps.Add(s_Player_Bitmap4) 'Return the collection Return s_colPlayerBitmaps End Function Private Shared s_blackPen As System.Drawing.Pen Private Shared s_whitePen As System.Drawing.Pen Private Shared s_ImageAttribute As _ System.Drawing.Imaging.ImageAttributes Private Shared s_boldFont As System.Drawing.Font '--------------------------------------- 'Called to release any drawing resources we 'may have cached '--------------------------------------- Private Shared Sub g_CleanUpDrawingResources() 'Clean up the black pen, if we've got one If Not (s_blackPen Is Nothing) Then s_blackPen.Dispose() s_blackPen = Nothing End If 'Clean up the white pen, if we've got one If Not (s_whitePen Is Nothing) Then s_whitePen.Dispose() s_whitePen = Nothing End If 'Clean up the ImageAttribute, if we've got one 'Note: This type does not have a Dispose() method 'because all of its data is managed data If Not (s_ImageAttribute Is Nothing) Then s_ImageAttribute = Nothing End If 'Clean up the bold font, if we've got one If Not (s_boldFont Is Nothing) Then s_boldFont.Dispose() s_boldFont = Nothing End If End Sub '------------------------------------- 'This function enables us to access the 'cached black pen '------------------------------------- Private Shared Function g_GetBlackPen() As System.Drawing.Pen 'If the pen does not exist yet, create it If (s_blackPen Is Nothing) Then s_blackPen = New System.Drawing.Pen( _ System.Drawing.Color.Black) End If 'Return the black pen Return s_blackPen End Function '------------------------------------- 'This function enables us to access the 'cached white pen '------------------------------------- Private Shared Function g_GetWhitePen() As System.Drawing.Pen 'If the pen does not exist yet, create it If (s_whitePen Is Nothing) Then s_whitePen = New System.Drawing.Pen( _ System.Drawing.Color.White) End If 'Return the white pen Return s_whitePen End Function '------------------------------------- 'This function enables us to access the 'cached bold font '------------------------------------- Private Shared Function g_GetBoldFont() As System.Drawing.Font 'If the pen does not exist yet, create it If (s_boldFont Is Nothing) Then s_boldFont = New System.Drawing.Font( _ System.Drawing.FontFamily.GenericSerif, _ 10, System.Drawing.FontStyle.Bold) End If 'Return the bold font Return s_boldFont End Function '----------------------------------------------- 'This function enables us to access the 'cached imageAttributes we use for bitmaps 'with transparency '----------------------------------------------- Private Shared Function g_GetTransparencyImageAttribute() As _ System.Drawing.Imaging.ImageAttributes 'If it does not exist, create it If (s_ImageAttribute Is Nothing) Then 'Create an image attribute s_ImageAttribute = _ New System.Drawing.Imaging.ImageAttributes s_ImageAttribute.SetColorKey(System.Drawing.Color.White, _ System.Drawing.Color.White) End If 'Return it Return s_ImageAttribute End Function End Class
Listing 8.2. Common Code Used in All Test Cases Below
'Number of times we want to repeat the test Const LOOP_SIZE As Integer = 8000 '---------------------------------------------------- 'This function resets the contents of our test 'array, so we can run the test algorithm 'over and over '---------------------------------------------------- Private Sub ResetTestArray(ByRef testArray() As String) If (testArray Is Nothing) Then ReDim testArray(5) End If testArray(0) = "big_blue_duck" testArray(1) = "small_yellow_horse" testArray(2) = "wide_blue_cow" testArray(3) = "tall_green_zepplin" testArray(4) = "short_blue_train" testArray(5) = "short_purple_dinosaur" End Sub
Listing 8.3. A Test Case Showing Wasteful Allocations (a Typical First Implementation of a Function)
Private Sub Button2_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button2.Click 'Run the garbage collector so we know we're starting 'from a clean slate for our test. 'ONLY CALL DURING TESTING! Calling the GC manually will 'slow(down) 'overall application throughput! System.GC.Collect() Dim testArray() As String = Nothing '--------------------------------------------- 'Go through the items in the array and 'look for ones where the middle word is '"blue". Replace the "blue" with "orange" '--------------------------------------------- 'Start the stopwatch for our test! PerformanceSampling.StartSample(0, "WastefulWorkerClass") Dim workerClass1 As WastefulWorkerClass Dim outerLoop As Integer For outerLoop = 1 To LOOP_SIZE 'Set up the data in the array we want to do our test on ResetTestArray(testArray) Dim topIndex = testArray.Length - 1 Dim idx As Integer For idx = 0 To topIndex '------------------------------------ 'Create an instance of a helper class 'that dissects our string into 3 pieces ' 'This is wasteful! '------------------------------------ workerClass1 = New WastefulWorkerClass(testArray(idx)) 'If the middle word is "blue", make it "orange" If (workerClass1.MiddleSegment = "blue") Then 'Replace the middle item workerClass1.MiddleSegment = "orange" 'Replace the word testArray(idx) = workerClass1.getWholeString() End If Next 'inner loop Next 'outer loop 'Get the time we completed our test PerformanceSampling.StopSample(0) MsgBox(PerformanceSampling.GetSampleDurationText(0)) End Sub
Listing 8.4. The Worker Class for our First Test Case
Option Strict On Imports System Public Class WastefulWorkerClass Private m_beginning_segment As String Public Property BeginSegment() As String Get Return m_beginning_segment End Get Set(ByVal Value As String) m_beginning_segment = Value End Set End Property Private m_middle_segment As String Public Property MiddleSegment() As String Get Return m_middle_segment End Get Set(ByVal Value As String) m_middle_segment = Value End Set End Property Private m_end_segment As String Public Property EndSegment() As String Get Return m_end_segment End Get Set(ByVal Value As String) m_end_segment = Value End Set End Property Public Sub New(ByVal in_word As String) Dim index_segment1 As Integer 'Look for a "_" in the string index_segment1 = in_word.IndexOf("_", 0) 'If there is no "_", the first segment is the whole thing If (index_segment1 = -1) Then m_beginning_segment = in_word m_middle_segment = " m_end_segment = " Return Else 'If there is a "_", split it 'If the "_" is the first char, the 1st segment is " If (index_segment1 = 0) Then m_beginning_segment = " Else 'The first segment m_beginning_segment = in_word.Substring(0, index_segment1) End If 'Find the second "_" Dim index_segment2 As Integer index_segment2 = in_word.IndexOf("_", index_segment1 + 1) 'Second "_" does not exist If (index_segment2 = -1) Then m_middle_segment = " m_end_segment = in_word.Substring(index_segment1 + 1) Return End If 'Set the end segment m_middle_segment = in_word.Substring(index_segment1 + 1, _ index_segment2 - index_segment1 - 1) m_end_segment = in_word.Substring(index_segment2 + 1) End If End Sub 'Returns the whole 3 segments joined by "-"s Public Function getWholeString() As String Return m_beginning_segment + "_" + m_middle_segment + "_" + _ m_end_segment End Function End Class
Listing 8.5. A Test Case Showing Slightly Reduced Object Allocations (a Typical Refinement of a First Function Implementation)
Private Sub Button3_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button3.Click 'Run the garbage collector so we start from a "clean" 'state for our test 'ONLY CALL DURING TESTING! Calling the GC manually will slow 'down overall application throughput! System.GC.Collect() Dim testArray() As String = Nothing '--------------------------------------------- 'Go through the items in the array and 'look for ones where the middle word is '"blue". Replace the "blue" with "orange" '--------------------------------------------- 'Start the stopwatch! PerformanceSampling.StartSample(1, "LessWasteful") '------------------------------------------------ 'LESS WASTEFUL: Allocate the object before we get in the 'loop '------------------------------------------------ Dim workerClass1 As LessWastefulWorkerClass workerClass1 = New LessWastefulWorkerClass Dim outerLoop As Integer For outerLoop = 1 To LOOP_SIZE 'Set up the data in the array we want to do our test on ResetTestArray(testArray) Dim topIndex As Integer = testArray.Length - 1 Dim idx As Integer For idx = 0 To topIndex '---------------------------------------------------- 'Instead of reallocating the object, let's just reuse 'it '---------------------------------------------------- 'workerClass1 = new WastefulWorkerClass( ' testArray(topIndex)) workerClass1.ReuseClass(testArray(idx)) 'If the middle word is "blue", make it "orange" If (workerClass1.MiddleSegment = "blue") Then 'Replace the middle item workerClass1.MiddleSegment = "orange" 'Replace the word testArray(idx) = workerClass1.getWholeString() End If Next 'inner loop Next 'outer loop 'Stop the stopwatch! PerformanceSampling.StopSample(1) MsgBox(PerformanceSampling.GetSampleDurationText(1)) End Sub
Listing 8.6. The Worker Class for Our Second Test Case
Option Strict On Imports System Public Class LessWastefulWorkerClass Private m_beginning_segment As String Public Property BeginSegment() As String Get Return m_beginning_segment End Get Set(ByVal Value As String) m_beginning_segment = Value End Set End Property Private m_middle_segment As String Public Property MiddleSegment() As String Get Return m_middle_segment End Get Set(ByVal Value As String) m_middle_segment = Value End Set End Property Private m_end_segment As String Public Property EndSegment() As String Get Return m_end_segment End Get Set(ByVal Value As String) m_end_segment = Value End Set End Property Public Sub ReuseClass(ByVal in_word As String) '----------------------------------------- 'To reuse the class, clear all the internal state '----------------------------------------- m_beginning_segment = " m_middle_segment = " m_end_segment = " Dim index_segment1 As Integer 'Look for a "_" in the string index_segment1 = in_word.IndexOf("_", 0) 'If there is no "_", the first segment is the whole thing If (index_segment1 = -1) Then m_beginning_segment = in_word Return Else 'If there is a "_", split it If (index_segment1 = 0) Then Else m_beginning_segment = in_word.Substring(0, _ index_segment1) End If Dim index_segment2 As Integer index_segment2 = in_word.IndexOf("_", index_segment1 + 1) If (index_segment2 = -1) Then m_end_segment = in_word.Substring(index_segment1 + 1) Return End If 'Set the end segment m_middle_segment = in_word.Substring(index_segment1 + 1, _ index_segment2 - index_segment1 - 1) m_end_segment = in_word.Substring(index_segment2 + 1) End If End Sub Public Function getWholeString() As String Return m_beginning_segment + "_" + m_middle_segment + "_" + _ m_end_segment End Function End Class
Listing 8.7. A Test Case Showing Significantly Reduced Object Allocations (Typical of Doing Significant Algorithm Optimizations on the First Implementation)
Private Sub Button5_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button5.Click 'Run the garbage collector, so we start from a 'clean slate for our test 'ONLY CALL DURING TESTING! Calling the GC manually will slow 'down overall application throughput! System.GC.Collect() Dim testArray() As String = Nothing '--------------------------------------------- 'Go through the items in the array and 'look for ones where the middle word is '"blue". Replace the "blue" with "orange" '--------------------------------------------- 'Start the stopwatch for the test PerformanceSampling.StartSample(2, "DeferredObjects") '------------------------------------------------ 'Less wasteful: Allocate the object before we get in the 'loop '------------------------------------------------ Dim workerClass1 As LessAllocationsWorkerClass workerClass1 = New LessAllocationsWorkerClass Dim outerLoop As Integer For outerLoop = 1 To LOOP_SIZE 'Set up the data in the array we want to do our test on ResetTestArray(testArray) Dim topIndex As Integer = testArray.Length - 1 Dim idx As Integer For idx = 0 To topIndex '------------------------------------------------------ 'Less wasteful: 'Instead of reallocating the object, let's just reuse it 'Also: The implementation does NOT create additional 'strings '------------------------------------------------------ 'workerClass1 = new WastefulWorkerClass( ' testArray[topIndex]) workerClass1.ReuseClass(testArray(idx)) 'If the middle word is "blue", make it "orange" '-------------------------------------------------- 'Less wasteful: 'This compare does not need to create any additional 'strings '-------------------------------------------------- If (workerClass1.CompareMiddleSegment("blue") = 0) Then 'Replace the middle item workerClass1.MiddleSegment = "orange" 'Replace the word testArray(idx) = workerClass1.getWholeString() End If Next 'inner loop Next 'outer loop 'Stop the stopwatch! PerformanceSampling.StopSample(2) MsgBox(PerformanceSampling.GetSampleDurationText(2)) End Sub
Listing 8.8. The Worker Class for Our Third Test Case
Option Strict On Imports System Public Class LessAllocationsWorkerClass Public WriteOnly Property MiddleSegment() As String Set(ByVal Value As String) m_middleSegmentNew = Value End Set End Property Private m_middleSegmentNew As String Private m_index_1st_undscore As Integer Private m_index_2nd_undscore As Integer Private m_stringIn As String Public Sub ReuseClass(ByVal in_word As String) '----------------------------------------- 'To reuse the class, clear all the internal state '----------------------------------------- m_index_1st_undscore = -1 m_index_2nd_undscore = -1 m_middleSegmentNew = Nothing m_stringIn = in_word 'This does not make a string copy 'Look for a "_" in the string m_index_1st_undscore = in_word.IndexOf("_", 0) 'If there is no "_", the first segment is the whole thing If (m_index_1st_undscore = -1) Then Return End If 'Look for the second "_" m_index_2nd_undscore = in_word.IndexOf("_", _ m_index_1st_undscore + 1) End Sub Public Function CompareMiddleSegment(ByVal compareTo As _ String) As Integer 'If there is no second underscore, there is no middle If (m_index_2nd_undscore < 0) Then 'If we are comparing to an empty string, then this is a 'match If ((compareTo = Nothing) OrElse (compareTo = ")) Then Return 0 End If Return -1 End If 'Compare the middle segment to the first and second segments Return System.String.Compare(m_stringIn, _ m_index_1st_undscore + 1, _ compareTo, _ 0, _ m_index_2nd_undscore - m_index_1st_undscore - 1) End Function Public Function getWholeString() As String 'If we've been given no new middle segment, return the 'original If (m_middleSegmentNew = Nothing) Then Return m_stringIn End If 'Build the return string Return m_stringIn.Substring(0, m_index_1st_undscore + 1) + _ m_middleSegmentNew + m_stringIn.Substring( _ m_index_2nd_undscore, _ m_stringIn.Length - m_index_2nd_undscore) End Function End Class
Listing 8.9. Comparing String Usage to StringBuilder in Algorithms
Const COUNT_UNTIL As Integer = 300 Const LOOP_ITERATIONS As Integer = 40 '------------------------------------------------------- 'NOT VERY EFFICIENT! ' 'Use regular strings to simulate building a typical set of 'strings '------------------------------------------------------- Private Sub Button1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button1.Click 'Do a garbage collection before we start running, to start 'us from a clean state. 'ONLY CALL DURING TESTING! Calling the GC manually will slow 'down overall application throughput! System.GC.Collect() Dim numberToStore As Integer PerformanceSampling.StartSample(0, "StringAllocaitons") Dim total_result As String Dim outer_loop As Integer For outer_loop = 1 To LOOP_ITERATIONS 'Clear out the old result total_result = " 'Count up to 'x_counter' and append the test of each count 'to our working string Dim x_counter As Integer For x_counter = 1 To COUNT_UNTIL total_result = total_result + numberToStore.ToString() _ + ", " 'Advance the counter numberToStore = numberToStore + 1 Next Next PerformanceSampling.StopSample(0) 'Display the length of the string MsgBox("String Length: " + total_result.Length.ToString()) 'Display string MsgBox("String : " + total_result) 'Display the time it tool to run MsgBox(PerformanceSampling.GetSampleDurationText(0)) End Sub '------------------------------------------------------- 'MUCH MORE EFFICIENT! ' 'Use the string builder to simulate building a fairly typical 'set of strings '------------------------------------------------------- Private Sub Button2_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button2.Click 'Do a garbage collection before we start running, to start 'us from a clean state. 'ONLY CALL DURING TESTING! Calling the GC manually will slow 'down overall application throughput! System.GC.Collect() Dim sb As System.Text.StringBuilder = _ New System.Text.StringBuilder Dim total_result As String Dim numberToStore As Integer PerformanceSampling.StartSample(1, "StringBuilder") Dim outer_loop As Integer For outer_loop = 1 To LOOP_ITERATIONS 'Clear the string builder sb.Length = 0 'Clear out our old result string total_result = " 'Count up to 'x_counter' and append the test of each count 'to our working string Dim x_counter As Integer For x_counter = 1 To COUNT_UNTIL sb.Append(numberToStore) sb.Append(", ") 'Advance the counter numberToStore = numberToStore + 1 Next 'Pretend we're doing something with the string... total_result = sb.ToString() Next PerformanceSampling.StopSample(1) 'Display the length of the string MsgBox("String Length: " + total_result.Length.ToString()) 'Display string MsgBox("String : " + total_result) 'Display the time it tool to run MsgBox(PerformanceSampling.GetSampleDurationText(1)) End Sub
|