Monitoring performance and resource usage

To write high quality applications, we need to be able to monitor the speed and efficiency of our code.

Evaluating the efficiency of types

What is the best type to use for a scenario? To answer this question, we need to carefully consider what we mean by best. We should consider the following factors:

  • Functionality: This can be decided by checking whether the type provides the features you need
  • Memory size: This can be decided by the number of bytes of memory the type takes up
  • Performance: This can be decided by how fast the type is
  • Future needs: This depends on the changes in requirements and maintainability

There will be scenarios, such as storing numbers, where multiple types have the same functionality, so we will need to consider the memory and performance to make a choice.

If we need to store millions of numbers, then the best type to use would be the one that requires the least number of bytes of memory. If we only need to store a few numbers, but we need to perform lots of calculations on them, then the best type to use would be the one that runs fastest on a CPU.

You have seen the use of the sizeof() function to show the number of bytes a single instance of a type uses in memory. When we are storing lots of values in more complex data structures, such as arrays and lists, then we need a better way of measuring memory usage.

You can read lots of advice online and in books, but the only way to know for sure what the best type would be for your code is to compare the types yourself. In the next section, you will learn how to write the code to monitor the actual memory requirements and the actual performance when using different types.

Although, today a short variable might be the best choice, it might be a better choice to use an int variable even though it takes twice as much space in memory because we might need a wider range of values to be stored in the future.

There is another metric we should consider: maintenance. This is a measure of how much effort another programmer would have to put to understand and modify your code. If you use a nonobvious type choice, it might confuse the programmer who comes along later and needs to fix a bug or add a feature. There are analyzing tools that will generate a report that shows how easily maintainable your code is.

Monitoring performance and memory use

The System.Diagnostics namespace has lots of useful types for monitoring your code. The first one we will look at is the Stopwatch type.

Add a new console application project named Ch05_Monitoring. Set the solution's start up project to be the current selection.

Modify the code to look like this:

    using System; 
    using System.Diagnostics; 
    using System.Linq; 
    using static System.Console; 
    using static System.Diagnostics.Process; 
 
    namespace Ch05_Monitoring 
    { 
      class Recorder 
      { 
        static Stopwatch timer = new Stopwatch(); 
        static long bytesPhysicalBefore = 0; 
        static long bytesVirtualBefore = 0; 
 
        public static void Start() 
        { 
          GC.Collect(); 
          GC.WaitForPendingFinalizers(); 
          GC.Collect(); 
          bytesPhysicalBefore = GetCurrentProcess().WorkingSet64; 
          bytesVirtualBefore =
            GetCurrentProcess().VirtualMemorySize64; 
          timer.Restart(); 
        } 
 
        public static void Stop() 
        { 
          timer.Stop(); 
          long bytesPhysicalAfter = GetCurrentProcess().WorkingSet64; 
          long bytesVirtualAfter =
            GetCurrentProcess().VirtualMemorySize64; 
          WriteLine("Stopped recording."); 
          WriteLine($"{bytesPhysicalAfter - bytesPhysicalBefore:N0}
          physical bytes used."); 
          WriteLine($"{bytesVirtualAfter - bytesVirtualBefore:N0}
          virtual bytes used."); 
          WriteLine($"{timer.Elapsed} time span ellapsed."); 
          WriteLine($"{timer.ElapsedMilliseconds:N0} total
          milliseconds ellapsed."); 
        } 
      } 
 
      class Program 
      { 
        static void Main(string[] args) 
        { 
          Write("Press ENTER to start the timer: "); 
          ReadLine(); 
          Recorder.Start(); 
 
          int[] largeArrayOfInts =  
            Enumerable.Range(1, 10000).ToArray(); 
 
          Write("Press ENTER to stop the timer: "); 
          ReadLine(); 
          Recorder.Stop(); 
          ReadLine(); 
        } 
      } 
    } 

Note

The Start method of the Recorder class uses the garbage collector (GC) type to ensure that all the currently allocated memory is collected before recording the amount of used memory. This is an advanced technique that you should almost never use in production code.

You have created a class named Recorder with two methods to start and stop recording the time and memory used by any code you run. The Main method starts recording when the user presses Enter, creates an array of ten thousand int variables, and then stops recording when the user presses Enter again.

The Stopwatch type has some useful members, as shown in the following table:

Member

Description

The Restart method

This resets the elapsed time to zero and then starts the stopwatch.

The Stop method

This stops the stopwatch.

The Elapsed property

This is the elapsed time stored as a TimeSpan (hours:minutes:seconds).

The ElapsedMilliseconds property

This is the elapsed time in milliseconds stored as a long integer.

The Process type has some useful members, as shown in the following table:

Member

Description

VirtualMemorySize64

This displays the amount of the virtual memory, in bytes, allocated for the process.

WorkingSet64

This displays the amount of physical memory, in bytes, allocated for the process.

Run the console application without the debugger attached. The application will start recording the time and memory used when you press Enter and then stop recording when you press Enter again. Wait for a few seconds between pressing Enter twice, as you can see that I did with the following output:

Press ENTER to start the timer:
Press ENTER to stop the timer:
Stopped recording.
942,080 physical bytes used.
0 virtual bytes used.
00:00:03.1166037 time span ellapsed.
3,116 total milliseconds ellapsed.

Measuring the efficiency of processing strings

Now that you've seen how the Stopwatch and Process types can be used to monitor your code, we will use them to evaluate the best way to process string variables.

Comment out the previous code in the Main method by wrapping it in /* */.

Add the following code to the Main method. It creates an array of ten thousand int variables and then concatenates them with commas for separators using a string and a StringBuilder:

    int[] numbers = Enumerable.Range(1, 10000).ToArray(); 
    Recorder.Start(); 
    WriteLine("Using string"); 
    string s = ""; 
    for (int i = 0; i < numbers.Length; i++) 
    { 
      s += numbers[i] + ", "; 
    } 
    Recorder.Stop(); 
    Recorder.Start(); 
    WriteLine("Using StringBuilder"); 
    var builder = new System.Text.StringBuilder(); 
    for (int i = 0; i < numbers.Length; i++) 
    { 
      builder.Append(numbers[i]); 
      builder.Append(", "); 
    } 
    Recorder.Stop(); 
    ReadLine(); 

Run the console application and view the output:

Using string
Stopped recording.
7,540,736 physical bytes used.
69,632 virtual bytes used.
00:00:00.0871730 time span ellapsed.
87 total milliseconds ellapsed.
Using StringBuilder
Stopped recording.
8,192 physical bytes used.
0 virtual bytes used.
00:00:00.0015680 time span ellapsed.
1 total milliseconds ellapsed.

We can summarize the results as follows:

  • The string class used about 7.5 MB of memory and took 133 milliseconds
  • The StringBuilder class used 8 KB of memory and took 1.5 milliseconds

In this scenario, StringBuilder is about one hundred times faster and about one thousand times more memory efficient when concatenating text!

Tip

Good Practice

Avoid using the String.Concat method or the + operator with string variables. Instead, use StringBuilder or C# $ string interpolation to concatenate variables together, especially inside loops.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.130.199