C#
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C#
-
Section 4: Working with Data
-
Section 5: Error Handling
-
Section 6: Delegates and Events
-
Section 7: Async Programming
-
Section 8: More Language Features
-
Section 9: File I/O and Serialization
-
Section 10: Networking in .NET
-
Section 11: The .NET Ecosystem
-
Section 12: Memory and Performance
-
Section 13: Concurrency Beyond Async
-
Section 14: Reflection and Attributes
-
Section 15: Testing and Best Practices
-
Section 16: Design Patterns in C#
-
Section 17: Standard Library Deep Dive
-
Section 18: Data Structures and Algorithms in C#
-
Section 19: GUI and Desktop Development Overview
-
Section 20: Practical Projects
-
Section 21: More Practice Exercises
-
Section 22: More Standard Library and Text Processing
-
Section 23: More Design Patterns
-
Section 24: More Projects
-
Section 25: Interview Practice
-
Section 26: C# Keywords Reference (Modifiers)
-
Section 27: C# Keywords Reference (Statements)
-
Section 28: C# Keywords Reference (Operators)
-
Section 29: BCL: System.Collections.Generic
-
Section 30: BCL: System.Linq
-
Section 31: BCL: System.Threading
-
Section 32: BCL: System.IO
-
Section 33: BCL: System.Text and System.Text.Json
-
Section 34: BCL: System.Net.Http
-
Section 35: C# Language Specification Topics
-
Section 36: More Practice Exercises
-
Section 37: More Async Patterns
-
Section 38: More BCL: System.Reflection and System.Diagnostics
-
Section 39: Nullable Reference Types In Depth
-
Section 40: C# Records and Pattern Matching In Depth
-
Section 41: Dependency Injection Deep Dive
-
Section 42: More Interview and Whiteboard Practice
226: Stopwatch and Performance Measurement
You've probably been tempted to do this at some point: you've written a piece of logic, it feels slow, and you want to know exactly how many milliseconds it's taking. Your first instinct is likely to grab the current time, run the code, grab the time again, and subtract the two. It seems logical, but in the world of high-performance C#, this is a trap.
// The "Quick and Dirty" way that actually fails
public void ProcessData()
{
var startTime = DateTime.Now;
// Simulate a fast operation
PerformFastCalculation();
var endTime = DateTime.Now;
var duration = endTime - startTime;
Console.WriteLine($"Elapsed time: {duration.TotalMilliseconds} ms");
}
private void PerformFastCalculation()
{
// This runs very quickly—likely under 10ms
Thread.Sleep(2);
}
The DateTime Precision Trap
If you run the code above, you'll notice something frustrating: the output is wildly inconsistent. You might get 0ms, then 15ms, then 31ms, even though the actual work is nearly identical. This happens because DateTime.Now is not designed for measurement; it's designed for telling the time of day.
The system clock has a relatively low resolution (typically around 10 to 15 milliseconds). If your code executes faster than the clock ticks, the difference between startTime and endTime will be zero. If it happens to cross a clock tick boundary, you get a massive jump. You're not measuring your code's performance; you're measuring the heartbeat of the Windows OS clock.
Using the High-Resolution Stopwatch
To get actual precision, we use System.Diagnostics.Stopwatch. Unlike DateTime, the Stopwatch class wraps the underlying high-resolution performance counter provided by the hardware. It doesn't care what time it is in London or New York; it only cares about how many ticks have passed since you hit "Start."
using System.Diagnostics;
public void ProcessData()
{
// StartNew() is a handy shortcut that instantiates and starts the timer immediately
Stopwatch sw = Stopwatch.StartNew();
PerformFastCalculation();
sw.Stop();
// Use Elapsed.TotalMilliseconds for a double, or ElapsedMilliseconds for a long
Console.WriteLine($"Actual elapsed time: {sw.Elapsed.TotalMilliseconds} ms");
}
This gives you sub-millisecond precision. I usually prefer sw.Elapsed.TotalMilliseconds because it returns a double, allowing you to see if a method is taking 0.4ms versus 0.8ms—a difference that matters when you're calling that method ten thousand times in a loop.
Accounting for the JIT Warm-up
Now, here is a professional tip that textbooks often skip: your first measurement is almost always a lie. In C#, the Just-In-Time (JIT) compiler compiles your IL code into machine code the first time a method is called. That initial compilation takes time.
If you measure a method once, you're measuring Compilation Time + Execution Time. To measure the actual performance of the logic, you need to "warm up" the code.
Stopwatch sw = new Stopwatch();
// Warm up: Run it once without measuring to trigger JIT compilation
PerformFastCalculation();
sw.Start();
for(int i = 0; i < 100; i++)
{
PerformFastCalculation();
}
sw.Stop();
Console.WriteLine($"Average execution time: {sw.Elapsed.TotalMilliseconds / 100} ms");
By running the method once before starting the timer, and then averaging the result over multiple iterations, you strip away the JIT overhead and the noise of background OS tasks. That is how you get a number you can actually trust.
📋 Practical Task
Benchmarking LINQ vs. For-Loops in Data Filtering
Create a program that compares the performance of filtering a large list using a foreach loop versus using a LINQ .Where().ToList() query. Follow these requirements:
- Generate a
List<int>containing 1,000,000 random integers. - Write two methods: one that filters out even numbers using a standard
foreachloop and a temporary list, and one that does the same using LINQ. - Implement a "warm-up" phase where both methods are called once before measurement begins.
- Use
System.Diagnostics.Stopwatchto measure how long each method takes to process the million-item list. - Print the total milliseconds for both approaches and determine which one was faster.
There are no comments for now.