To write high quality applications, we need to be able to monitor the speed and efficiency of our code.
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:
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.
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(); } } }
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 |
This resets the elapsed time to zero and then starts the stopwatch. |
The |
This stops the stopwatch. |
The |
This is the elapsed time stored as a |
The |
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 |
|
This displays the amount of the virtual memory, in bytes, allocated for the process. |
|
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.
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:
string
class used about 7.5 MB of memory and took 133 millisecondsStringBuilder
class used 8 KB of memory and took 1.5 millisecondsIn this scenario, StringBuilder
is about one hundred times faster and about one thousand times more memory efficient when concatenating text!
3.133.130.199