Performance Optimization Basics
Level: Intermediate to Advanced
- Measuring performance
- Profiling .NET applications
- Identifying bottlenecks
- Memory optimization
- Common performance mistakes
- BenchmarkDotNet basics
Measure Before Optimizing
Always profile before optimizing.
Blindly optimizing wastes time.
Use data to identify slow parts.
Visual Studio Profiler
- Debug → Performance Profiler
- Select CPU Usage
- Run application under load
- Analyze results
Shows which functions take most time.
Optimize the hot spots (slow functions).
Common Bottlenecks
1. Database Queries
Most common bottleneck.
Bad:
var students = _db.Students.ToList();
foreach (var s in students)
{
var marks = _db.ExamResults.Where(r => r.StudentId == s.Id).ToList();
// N+1 problem: 1 query for students + 100 queries for marks
}
Good:
var students = _db.Students
.Include(s => s.ExamResults)
.ToList();
// 1 query with JOIN
2. Unnecessary Object Allocation
// Bad: Creates string each iteration
for (int i = 0; i < 1000; i++)
{
var s = "Student" + i.ToString();
}
// Good: Use StringBuilder
var sb = new StringBuilder();
for (int i = 0; i < 1000; i++)
{
sb.Append("Student").Append(i);
}
3. Synchronous I/O
// Bad: Blocks thread
var data = _db.Students.ToList();
// Good: Async, frees thread
var data = await _db.Students.ToListAsync();
Async is essential for web applications.
Benchmarking with BenchmarkDotNet
Compare performance of different approaches.
Install:
dotnet add package BenchmarkDotNet
Write benchmark:
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
[MemoryDiagnoser]
public class StringBenchmark
{
[Benchmark]
public string StringConcat()
{
string result = "";
for (int i = 0; i < 1000; i++)
result += i;
return result;
}
[Benchmark]
public string StringBuilder()
{
var sb = new StringBuilder();
for (int i = 0; i < 1000; i++)
sb.Append(i);
return sb.ToString();
}
}
static void Main()
{
var summary = BenchmarkRunner.Run<StringBenchmark>();
}
Run:
dotnet run -c Release
Results show which is faster.
Memory Profiler
Visual Studio → Debug → Memory Usage
While running application:
- Inspect heap size
- Find memory leaks
- Identify large objects
Look for growing memory over time (leak indicator).
Optimization Techniques
Use ValueType for Small Objects
// Bad: Heap allocation
public class StudentId { public int Value; }
// Good: Stack allocation
public struct StudentId { public int Value; }
Struct uses stack, faster (but immutable).
Cache Expensive Operations
// Bad: Recompute every time
public int GetStudentCount()
{
return _db.Students.Count(); // SQL query every call
}
// Good: Cache result
private int _studentCount = 0;
private DateTime _cacheTime = DateTime.MinValue;
public int GetStudentCount()
{
if ((DateTime.Now - _cacheTime).TotalMinutes > 1)
{
_studentCount = _db.Students.Count();
_cacheTime = DateTime.Now;
}
return _studentCount;
}
Use LINQ Efficiently
// Bad: Loads all into memory then filters
var students = _db.Students.ToList().Where(s => s.Name.StartsWith("A"));
// Good: Filter in database
var students = _db.Students.Where(s => s.Name.StartsWith("A")).ToList();
ToList() must be last (after filtering/selecting).
Profiling Tools
| Tool | Purpose |
|---|---|
| Visual Studio Profiler | Built-in, full featured |
| dotTrace | JetBrains, advanced profiling |
| New Relic | Production monitoring |
| DataDog | Application performance monitoring |
Performance Best Practices
- Profile before optimizing — Measure actual bottlenecks
- Optimize database queries first — Usually the slowest
- Use async/await — Don't block threads
- Cache expensive results — In-memory caching
- Batch operations — Fewer database round trips
- Monitor production — Real-world performance data
- Benchmark changes — Verify improvements
Q: How do you identify and optimize performance bottlenecks in a .NET application?
Good Answer: "First, I use the Visual Studio Performance Profiler under real workload to identify where CPU time is spent. Usually the biggest bottleneck is database queries - I look for N+1 problems (querying inside loops) and fix them with Include() or batch loading. I benchmark string concatenation vs StringBuilder for high-volume operations. For web APIs, I ensure operations are async to free threads for other requests. I use caching for expensive operations that don't change frequently. Before optimizing anything, I measure to avoid premature optimization. After changes, I re-profile to verify the improvement actually happened."
Use ChatGPT, Claude, or Copilot to go deeper on Performance optimization in .NET. Try these prompts:
"How do I find performance bottlenecks in my .NET app?""What is the N+1 problem and how do I fix it?""When should I use async/await?""How do I use BenchmarkDotNet to compare approaches?"
💡 Tip: After reading this article, paste your own code into AI and ask "What could go wrong here and why?" — fastest way to find edge cases and deepen understanding.
Section Complete
You have completed the .NET Platform Fundamentals section.
Next recommended step: Start C# Fundamentals