← Back to list

performance-profiler
by ademceper
⭐ 1🍴 0📅 Jan 23, 2026
SKILL.md
name: performance-profiler description: Analyze and optimize code performance trigger: "slow query OR high memory OR performance issue OR optimize" allowed-tools:
- Read
- Grep
- Glob
- Bash(dotnet build)
- Bash(dotnet test)
- Edit
Performance Profiler
Analyzes code for performance issues and suggests optimizations.
Analysis Categories
1. Database Query Performance
N+1 Query Detection
// ❌ N+1 Problem
var orders = await _context.Orders.ToListAsync();
foreach (var order in orders)
{
// Each iteration = 1 query = N additional queries
var items = order.Items.ToList(); // Lazy loading
}
// Total: 1 + N queries
// ✅ Eager Loading Solution
var orders = await _context.Orders
.Include(o => o.Items)
.AsSplitQuery() // Prevents cartesian explosion
.ToListAsync();
// Total: 2 queries (split query)
Detection:
# Find foreach with navigation property access
grep -rn "foreach.*await" --include="*.cs" -A5 | \
grep -E "\.(Items|Products|Orders|Reviews)\."
# Find ToListAsync without Include
grep -rn "ToListAsync\(\)" --include="*.cs" -B10 | \
grep -v "Include\|AsNoTracking\|Select"
Missing AsNoTracking
// ❌ Tracked query (unnecessary overhead for read-only)
var products = await _context.Products.ToListAsync();
// ✅ Untracked query (faster, less memory)
var products = await _context.Products
.AsNoTracking()
.ToListAsync();
Detection:
# Find query handlers without AsNoTracking
grep -rln "QueryHandler" Merge.Application/ --include="*.cs" | \
xargs grep -L "AsNoTracking"
Missing Projections
// ❌ Loading entire entity when only need Name
var productNames = await _context.Products
.ToListAsync() // Loads ALL columns
.Select(p => p.Name);
// ✅ Projection - only loads Name column
var productNames = await _context.Products
.Select(p => p.Name)
.ToListAsync();
// ✅ With AutoMapper projection
var productDtos = await _context.Products
.ProjectTo<ProductListDto>(_mapper.ConfigurationProvider)
.ToListAsync();
Missing Indexes
// Check for queries without index support
// Common candidates:
// - Foreign keys (UserId, CategoryId)
// - Frequently filtered columns (Status, IsActive)
// - Sorted columns (CreatedAt, Name)
// Entity Configuration
entity.HasIndex(e => e.UserId);
entity.HasIndex(e => e.Status);
entity.HasIndex(e => new { e.CategoryId, e.IsActive }); // Composite
Detection:
# Find WHERE clauses in raw SQL
grep -rn "Where\|where" --include="*.cs" | \
grep -E "\.(Status|UserId|CategoryId|IsActive|CreatedAt)"
# Cross-reference with configurations
grep -rn "HasIndex" Merge.Infrastructure/ --include="*.cs"
2. Memory Performance
Large Object Allocations
// ❌ Loading entire table into memory
var allProducts = await _context.Products.ToListAsync(); // Could be millions
// ✅ Paginated loading
var products = await _context.Products
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
// ✅ Streaming for large datasets
await foreach (var product in _context.Products.AsAsyncEnumerable())
{
yield return product;
}
String Concatenation
// ❌ String concatenation in loop
string result = "";
foreach (var item in items)
{
result += item.Name + ", "; // Creates new string each iteration
}
// ✅ StringBuilder
var sb = new StringBuilder();
foreach (var item in items)
{
sb.Append(item.Name).Append(", ");
}
var result = sb.ToString();
// ✅ String.Join for simple cases
var result = string.Join(", ", items.Select(i => i.Name));
LINQ Materialization Issues
// ❌ Multiple enumerations
var products = GetProducts(); // IEnumerable
var count = products.Count(); // Enumeration 1
var first = products.First(); // Enumeration 2
// ✅ Materialize once
var products = GetProducts().ToList(); // Single enumeration
var count = products.Count;
var first = products[0];
// ❌ Deferred execution surprise
IEnumerable<Product> GetProducts()
{
using var context = new DbContext();
return context.Products; // Context disposed before enumeration!
}
// ✅ Eager execution
async Task<List<Product>> GetProductsAsync()
{
await using var context = new DbContext();
return await context.Products.ToListAsync();
}
3. Async/Await Performance
Unnecessary Async
// ❌ Async overhead without benefit
public async Task<int> GetCountAsync()
{
return await Task.FromResult(42); // No actual async work
}
// ✅ Synchronous when no I/O
public int GetCount() => 42;
// ❌ Async void (fire and forget, unobserved exceptions)
public async void ProcessAsync() { }
// ✅ Async Task
public async Task ProcessAsync() { }
Missing ConfigureAwait
// In library code (not ASP.NET Core controllers)
// ❌ Captures synchronization context unnecessarily
var result = await GetDataAsync();
// ✅ Better performance in libraries
var result = await GetDataAsync().ConfigureAwait(false);
// Note: In ASP.NET Core, there's no sync context, so this is less critical
Parallel vs Sequential
// ❌ Sequential when parallel possible
var result1 = await GetData1Async();
var result2 = await GetData2Async();
var result3 = await GetData3Async();
// ✅ Parallel when independent
var (result1, result2, result3) = await (
GetData1Async(),
GetData2Async(),
GetData3Async()
);
// Or with Task.WhenAll
var tasks = new[] { GetData1Async(), GetData2Async(), GetData3Async() };
var results = await Task.WhenAll(tasks);
4. Caching Analysis
Missing Cache
// ❌ Repeated expensive query
public async Task<ProductDto> GetProductAsync(Guid id)
{
return await _context.Products
.Where(p => p.Id == id)
.ProjectTo<ProductDto>(_mapper)
.FirstOrDefaultAsync(); // Hits DB every time
}
// ✅ With caching
public async Task<ProductDto?> GetProductAsync(Guid id, CancellationToken ct)
{
var cacheKey = $"product:{id}";
return await _cache.GetOrCreateAsync(cacheKey, async entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5);
return await _context.Products
.AsNoTracking()
.Where(p => p.Id == id)
.ProjectTo<ProductDto>(_mapper)
.FirstOrDefaultAsync(ct);
});
}
Cache Stampede Prevention
// ❌ Multiple simultaneous cache misses = multiple DB queries
var product = await _cache.GetOrCreateAsync(key, async _ =>
await _repository.GetAsync(id));
// ✅ With lock/semaphore
private static readonly SemaphoreSlim _semaphore = new(1, 1);
public async Task<ProductDto?> GetProductAsync(Guid id)
{
var cacheKey = $"product:{id}";
var cached = await _cache.GetStringAsync(cacheKey);
if (cached is not null)
return JsonSerializer.Deserialize<ProductDto>(cached);
await _semaphore.WaitAsync();
try
{
// Double-check after acquiring lock
cached = await _cache.GetStringAsync(cacheKey);
if (cached is not null)
return JsonSerializer.Deserialize<ProductDto>(cached);
var product = await _repository.GetAsync(id);
await _cache.SetStringAsync(cacheKey,
JsonSerializer.Serialize(product),
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5)
});
return product;
}
finally
{
_semaphore.Release();
}
}
5. API Performance
Response Size
// ❌ Returning too much data
[HttpGet]
public async Task<ActionResult<List<ProductDetailDto>>> GetAll()
{
return await _context.Products
.Include(p => p.Reviews)
.Include(p => p.Images)
.ProjectTo<ProductDetailDto>(_mapper)
.ToListAsync(); // Returns everything, including all reviews
}
// ✅ List endpoint returns minimal data
[HttpGet]
public async Task<ActionResult<PagedResult<ProductListDto>>> GetAll(
[FromQuery] int page = 1,
[FromQuery] int pageSize = 20)
{
return await _context.Products
.AsNoTracking()
.ProjectTo<ProductListDto>(_mapper) // Minimal fields
.ToPagedResultAsync(page, pageSize);
}
Missing Compression
// In Program.cs
builder.Services.AddResponseCompression(options =>
{
options.EnableForHttps = true;
options.Providers.Add<BrotliCompressionProvider>();
options.Providers.Add<GzipCompressionProvider>();
});
app.UseResponseCompression();
Performance Metrics
Query Performance Targets
| Operation | Target | Warning | Critical |
|---|---|---|---|
| Simple GET | < 50ms | > 100ms | > 500ms |
| List GET (paginated) | < 100ms | > 200ms | > 1s |
| Complex query | < 200ms | > 500ms | > 2s |
| Create/Update | < 100ms | > 200ms | > 1s |
Memory Targets
| Metric | Target | Warning |
|---|---|---|
| Gen 0 GC/min | < 10 | > 50 |
| Gen 2 GC/min | < 1 | > 5 |
| LOH allocations | Minimal | Frequent |
Detection Commands
# Find potential performance issues
# Missing AsNoTracking in queries
grep -rln "QueryHandler" --include="*.cs" | xargs grep -L "AsNoTracking"
# ToListAsync without pagination
grep -rn "ToListAsync\(\)" --include="*.cs" | grep -v "Take\|Skip"
# String concatenation in loops
grep -rn "+=" --include="*.cs" | grep "string\|String"
# Multiple awaits that could be parallel
grep -rn "await.*await.*await" --include="*.cs"
# Missing caching in handlers
grep -rln "QueryHandler" --include="*.cs" | xargs grep -L "IDistributedCache\|IMemoryCache"
Output Format
# Performance Analysis Report
**Analyzed Files:** 45
**Issues Found:** 12
## Critical Issues
### 1. N+1 Query Detected
- **File:** Merge.Application/Orders/Queries/GetOrdersQueryHandler.cs:34
- **Impact:** 1000 orders = 1001 queries
- **Fix:** Add `.Include(o => o.Items).AsSplitQuery()`
## High Issues
### 2. Missing AsNoTracking
- **File:** Merge.Application/Products/Queries/GetProductQueryHandler.cs:28
- **Impact:** Unnecessary change tracking overhead
- **Fix:** Add `.AsNoTracking()` to query
## Medium Issues
### 3. Large Object Loading
- **File:** Merge.Application/Reports/Queries/GetSalesReportHandler.cs:45
- **Impact:** Loads all orders into memory
- **Fix:** Implement streaming or pagination
## Recommendations
1. Add database indexes for frequently queried columns
2. Implement Redis caching for product catalog
3. Add response compression
4. Consider read replicas for heavy read operations
## Estimated Impact
| Fix | Performance Gain |
|-----|------------------|
| N+1 fix | ~95% faster |
| AsNoTracking | ~20% faster |
| Caching | ~80% faster |
Execution Flow
1. Scan Source Files
↓
2. Pattern Detection
- N+1 queries
- Missing optimizations
- Memory issues
↓
3. Analyze Query Plans (if possible)
↓
4. Generate Recommendations
↓
5. Prioritize by Impact
↓
6. Output Report
Score
Total Score
40/100
Based on repository quality metrics
✓SKILL.md
SKILL.mdファイルが含まれている
+20
○LICENSE
ライセンスが設定されている
0/10
○説明文
100文字以上の説明がある
0/10
○人気
GitHub Stars 100以上
0/15
○最近の活動
3ヶ月以内に更新がある
0/10
○フォーク
10回以上フォークされている
0/5
✓Issue管理
オープンIssueが50未満
+5
✓言語
プログラミング言語が設定されている
+5
○タグ
1つ以上のタグが設定されている
0/5
Reviews
💬
Reviews coming soon