Skip to main content

Performance Optimization

Short Introduction

Performance optimization in .NET Core involves analyzing, measuring, and improving application speed, throughput, and resource utilization. It encompasses profiling, JIT/AOT compilation strategies, threading optimizations, and runtime tuning.

Official Definition:

Performance optimization is the systematic process of making .NET applications run faster and use resources more efficiently through code improvements, configuration tuning, and runtime optimizations.

Setup/Usage

Profiling Tools Setup:

<!-- Add to .csproj for profiling -->
<PackageReference Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="3.0.2" />
<PackageReference Include="BenchmarkDotNet" Version="0.13.8" />

AOT Publishing:

<!-- Enable AOT in .csproj -->
<PropertyGroup>
<PublishAot>true</PublishAot>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>

Use Cases

  • High-traffic web applications requiring low latency
  • Microservices with strict SLA requirements
  • Real-time systems needing predictable response times
  • Resource-constrained environments (IoT, edge computing)
  • Cost optimization in cloud deployments
  • Batch processing applications handling large datasets

When to Use vs When Not to Use

When to Use:

  • Performance bottlenecks are identified through profiling
  • Application doesn't meet performance requirements
  • Resource costs are high due to inefficiency
  • User experience is impacted by slow response times
  • Scaling requirements demand optimization

When Not to Use:

  • Premature optimization without measurement
  • Development time outweighs performance benefits
  • Simple applications with minimal load
  • Temporary or prototype applications
  • When optimization adds significant complexity

Alternatives and Trade-offs

Alternatives:

  • Horizontal scaling instead of optimization
  • Caching layers to mask performance issues
  • CDN for content delivery optimization
  • Database optimization and indexing
  • Infrastructure upgrades (faster CPUs, more RAM)

Trade-offs:

  • Development time vs performance gains
  • Code complexity vs execution speed
  • Memory usage vs CPU usage
  • Startup time vs runtime performance (AOT vs JIT)

Sample Code and Commands

BenchmarkDotNet Example:

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

[MemoryDiagnoser]
[SimpleJob(BenchmarkDotNet.Jobs.RuntimeMoniker.Net80)]
public class StringConcatenationBenchmark
{
private readonly string[] _strings = Enumerable.Range(0, 1000)
.Select(i => $"String_{i}")
.ToArray();

[Benchmark]
public string UsingStringConcatenation()
{
string result = "";
foreach (var str in _strings)
{
result += str;
}
return result;
}

[Benchmark]
public string UsingStringBuilder()
{
var sb = new StringBuilder();
foreach (var str in _strings)
{
sb.Append(str);
}
return sb.ToString();
}

[Benchmark]
public string UsingStringJoin()
{
return string.Join("", _strings);
}
}

// Program.cs
class Program
{
static void Main(string[] args)
{
BenchmarkRunner.Run<StringConcatenationBenchmark>();
}
}

ThreadPool Tuning:

// Program.cs - Configure ThreadPool
var builder = WebApplication.CreateBuilder(args);

// Configure ThreadPool for high-throughput scenarios
ThreadPool.SetMinThreads(
workerThreads: Environment.ProcessorCount * 4,
completionPortThreads: Environment.ProcessorCount * 4
);

// Configure Kestrel for performance
builder.Services.Configure<KestrelServerOptions>(options =>
{
options.Limits.MaxConcurrentConnections = 1000;
options.Limits.MaxConcurrentUpgradedConnections = 1000;
options.Limits.MaxRequestBodySize = 10 * 1024 * 1024; // 10MB
options.Limits.MinRequestBodyDataRate = new MinDataRate(100, TimeSpan.FromSeconds(10));
});

High-Performance HTTP Client:

// Efficient HttpClient usage
public class OptimizedHttpService
{
private static readonly HttpClient _httpClient = new HttpClient(new SocketsHttpHandler
{
PooledConnectionLifetime = TimeSpan.FromMinutes(15),
MaxConnectionsPerServer = 10
});

static OptimizedHttpService()
{
_httpClient.DefaultRequestHeaders.Connection.Add("keep-alive");
}

public async Task<T> GetAsync<T>(string url)
{
using var response = await _httpClient.GetAsync(url);

// Use System.Text.Json for better performance
var jsonStream = await response.Content.ReadAsStreamAsync();
return await JsonSerializer.DeserializeAsync<T>(jsonStream);
}
}

Memory-Efficient Collections:

// Use Span<T> and Memory<T> for high-performance scenarios
public class HighPerformanceProcessor
{
public void ProcessData(ReadOnlySpan<byte> data)
{
// Process data without allocations
for (int i = 0; i < data.Length; i++)
{
var value = data[i];
// Process value
}
}

public async Task<int> ProcessFileAsync(string filePath)
{
// Use Memory<T> for async operations
using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
var buffer = new byte[4096];
var memory = buffer.AsMemory();

int totalBytes = 0;
int bytesRead;

while ((bytesRead = await fileStream.ReadAsync(memory)) > 0)
{
totalBytes += bytesRead;
ProcessData(memory.Span[..bytesRead]);
}

return totalBytes;
}
}

Performance Commands:

# Build with AOT
dotnet publish -c Release -r win-x64 --self-contained

# Profile with dotnet-trace
dotnet tool install --global dotnet-trace
dotnet trace collect --process-id <PID> --providers Microsoft-DotNETCore-SampleProfiler

# Analyze memory usage
dotnet tool install --global dotnet-dump
dotnet dump collect --process-id <PID>

# Monitor performance counters
dotnet tool install --global dotnet-counters
dotnet counters monitor --process-id <PID> System.Runtime