-
Notifications
You must be signed in to change notification settings - Fork 76
Open
Labels
enhancementNew feature or requestNew feature or request
Description
GitHub Issue: Performance: Eliminate string allocations in request metrics hot path
Problem
Every HTTP request creates 4-6 unnecessary string allocations in the GooseRequestMetric::new() constructor, causing significant memory pressure and CPU overhead in the hot path. This happens in src/goose.rs:
// In GooseRequestMetric::new() - these allocate on EVERY request
pub struct GooseRequestMetric {
pub scenario_name: String, // Heap allocation #1
pub transaction_index: String, // Heap allocation #2
pub transaction_name: String, // Heap allocation #3
pub name: String, // Heap allocation #4
// ... other fields
}
// In the constructor:
GooseRequestMetric {
scenario_name: transaction_detail.scenario_name.to_string(), // Clone
transaction_index: transaction_detail.transaction_index.to_string(), // Clone
transaction_name: transaction_detail.transaction_name.to_string(), // Clone
name: name.to_string(), // Clone
// ...
}Performance Impact
- 4-6 heap allocations per HTTP request (most critical bottleneck)
- String cloning overhead for data that's already available elsewhere
- Memory fragmentation from millions of small string allocations
- Scale: In a 1M request test, this creates 4-6M unnecessary allocations
- GC pressure: Significantly increases allocation rate
Proposed Solution: Reference Existing Data
Instead of copying strings, reference the data that already exists in GooseAttack using simple indices:
pub struct GooseRequestMetric {
// Replace String with lightweight references to existing data
pub scenario_index: usize, // Index into scenarios Vec
pub transaction_index: usize, // Index into transactions Vec
pub name: String, // Keep this one since it varies per request
// ... other fields remain unchanged
}
// Access existing strings without allocation:
impl GooseRequestMetric {
pub fn get_scenario_name(&self, goose_attack: &GooseAttack) -> &str {
&goose_attack.scenarios[self.scenario_index].name
}
pub fn get_transaction_name(&self, goose_attack: &GooseAttack) -> &str {
&goose_attack.scenarios[self.scenario_index]
.transactions[self.transaction_index].name
}
}
// Constructor becomes allocation-free for most fields:
impl GooseRequestMetric {
pub fn new(
scenario_index: usize,
transaction_index: usize,
name: String, // Only allocate for the request-specific name
// ... other params
) -> Self {
GooseRequestMetric {
scenario_index,
transaction_index,
name, // Only 1 allocation instead of 4-6
// ... other fields
}
}
}Why This Approach
- Zero external dependencies - uses existing GooseAttack data structures
- Minimal code changes - just change field types and constructor
- Same performance benefits - eliminates 4-6 unnecessary allocations per request
- No complexity overhead - straightforward index approach
- Leverages existing architecture - GooseAttack already stores all scenario/transaction names
Expected Performance Gains
- Memory reduction: 60-80% fewer allocations in request hot path (4-5 allocations eliminated per request)
- CPU improvement: Faster metrics creation without string cloning
- Simplicity: Minimal code changes with maximum impact
- GC pressure reduction: Significantly less memory allocation/deallocation churn
- Maintainability: Uses existing GooseAttack architecture without adding complexity
Implementation Plan
- Phase 1: Change String fields to indices that reference existing collections
- Phase 2: Update
GooseRequestMetric::new()to use indices instead of cloning - Phase 3: Update display/serialization code to resolve indices to strings
Testing Strategy
- Unit tests: Verify index-based lookups work correctly
- Integration tests: Ensure metrics display and serialization work
- Performance tests: Benchmark allocation reduction
- Memory profiling: Confirm heap allocation improvement
- Load tests: Validate under realistic high-load scenarios
Acceptance Criteria
-
GooseRequestMetricuses indices instead of owned strings for scenario/transaction names - Eliminates 4-5 unnecessary string allocations in
GooseRequestMetric::new() - All display functionality works correctly
- Serialization/deserialization preserved
- Performance benchmarks show 60-80% allocation reduction
- Memory usage reduced significantly
- All tests pass
Related Issues
Part of comprehensive performance optimization effort. Related to:
- Performance: Eliminate string formatting in metrics aggregation hot path #637 (Metrics key optimization)
- Performance: Eliminate unnecessary header processing in request hot path #639 (Header processing optimization)
Implementation Notes
- Scenario and transaction names already exist in GooseAttack structure
- Index bounds should be validated to prevent panics
- Display code needs access to GooseAttack for index->string resolution
- Only the request name field remains as String since it varies per request
Labels
- enhancement
- breaking-change
Priority
Priority 1 (Critical Impact)
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or request