C sharp - Performance Engineering in C# (.NET Performance Tuning)
Performance engineering is the process of designing, measuring, and optimizing applications to achieve maximum efficiency.
In C#, performance depends on:
-
Memory management
-
Garbage Collection
-
JIT compilation
-
CPU usage
-
Thread management
-
I/O efficiency
This topic focuses on understanding how .NET works internally and how to optimize correctly.
1. Understanding the CLR (Common Language Runtime)
C# code is:
-
Compiled into IL (Intermediate Language)
-
JIT-compiled into native machine code at runtime
Performance depends on:
-
JIT optimizations
-
CPU architecture
-
Runtime environment
The JIT compiler performs:
-
Inlining
-
Dead code elimination
-
Loop optimizations
2. Garbage Collection (GC) Optimization
.NET uses automatic memory management.
Objects are allocated on:
-
Stack (value types, local refs)
-
Heap (reference types)
Heap is divided into generations:
| Generation | Purpose |
|---|---|
| Gen 0 | Short-lived objects |
| Gen 1 | Intermediate |
| Gen 2 | Long-lived objects |
| LOH | Large Object Heap (85KB+) |
Why This Matters
Frequent allocations cause:
-
GC pressure
-
CPU spikes
-
Latency
Optimization Techniques
-
Reuse objects
-
Use
ArrayPool<T> -
Avoid unnecessary allocations
-
Prefer structs for small data types
-
Avoid boxing/unboxing
3. Avoid Boxing and Unboxing
Boxing:
Converting value type → object
int x = 5;
object obj = x; // boxing
Unboxing:
int y = (int)obj;
This causes:
-
Heap allocation
-
Performance overhead
Avoid by:
-
Using generics
-
Avoid storing value types in object collections
4. Use Span and Memory
Span<T> allows working with memory without allocation.
Example:
Span<int> numbers = stackalloc int[5];
Benefits:
-
No heap allocation
-
High performance
-
Safe memory access
Used in:
-
High-performance systems
-
Parsing libraries
5. String Performance
Strings are immutable.
Every modification creates new object:
string s = "";
s += "Hello";
Better approach:
StringBuilder sb = new StringBuilder();
sb.Append("Hello");
Use StringBuilder for:
-
Loops
-
Large concatenations
6. Async for Scalability
Use async for:
-
I/O-bound operations
-
Web servers
-
Database calls
It:
-
Frees threads
-
Improves throughput
-
Reduces memory usage
Avoid blocking calls:
-
.Wait() -
.Result
7. LINQ Performance Consideration
LINQ is readable but can cause:
-
Extra allocations
-
Deferred execution confusion
Example inefficient:
var result = list.Where(x => x > 5).ToList();
If only counting:
list.Count(x => x > 5);
Avoid multiple enumerations of same collection.
8. Profiling and Benchmarking
Never optimize blindly.
Use tools like:
-
BenchmarkDotNet
-
dotnet trace
-
Visual Studio Profiler
-
PerfView
Measure:
-
CPU usage
-
Memory allocation
-
GC frequency
-
Execution time
9. Threading and Parallelism
Improper threading causes:
-
Context switching overhead
-
Lock contention
-
Deadlocks
Use:
-
Parallel.For
-
Task Parallel Library (TPL)
-
Concurrent collections
Avoid excessive locking.
10. Data Structure Selection
Choosing wrong structure impacts performance.
| Use Case | Best Structure |
|---|---|
| Fast lookup | Dictionary |
| Ordered data | SortedSet |
| FIFO | Queue |
| LIFO | Stack |
| Large numeric arrays | Array |
Algorithm complexity matters:
O(1) is better than O(n)
11. Reduce Exception Usage
Exceptions are expensive.
Do not use for control flow:
Bad:
try
{
int x = int.Parse(value);
}
catch
{
}
Better:
int.TryParse(value, out int x);
12. Real-World Example
In ASP.NET:
Poor performance:
-
Blocking DB calls
-
Large object allocations
-
Frequent GC
Optimized:
-
Async DB calls
-
Object pooling
-
Efficient caching
-
Minimal allocations
This can increase request handling capacity by multiple times.
Key Principles
-
Measure before optimizing
-
Avoid unnecessary allocations
-
Understand GC behavior
-
Use correct data structures
-
Prefer async for I/O
-
Minimize locking
-
Avoid boxing