-
Notifications
You must be signed in to change notification settings - Fork 94
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: How to use profiler to optimize gas usage #267
Open
raizo07
wants to merge
2
commits into
NethermindEth:dev
Choose a base branch
from
raizo07:profiler
base: dev
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
282 changes: 282 additions & 0 deletions
282
pages/advanced-concepts/optimisations/cairo-profiler.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,282 @@ | ||
# Cairo Profiler Technical Guide: Gas Optimization | ||
|
||
**Understanding Available Metrics** | ||
|
||
The profiler collects several key metrics per function call: | ||
|
||
```cairo | ||
|
||
measurements = { | ||
"steps": n_steps, | ||
"memory_holes": n_memory_holes, | ||
"calls": 1, // count of function invocations | ||
"l2_l1_message_sizes": message_sizes_sum, | ||
// builtin counters (pedersen, range_check, etc.) | ||
} | ||
``` | ||
|
||
## Running the Profiler | ||
|
||
```cairo | ||
cairo-profiler <path_to_trace_data> [OPTIONS] | ||
|
||
Options: | ||
-o, --output-path <PATH> Output path [default: profile.pb.gz] | ||
--show-details Show contract addresses and selectors | ||
--max-function-stack-trace-depth <DEPTH> Maximum depth of function tree [default: 100] | ||
--split-generics Split generic functions by type | ||
--show-inlined-functions Show inlined function calls | ||
--versioned-constants-path <PATH> Custom resource costs file | ||
|
||
``` | ||
|
||
### Example: Optimizing Storage Access | ||
|
||
### Unoptimized Code | ||
|
||
```cairo | ||
#[starknet::contract] | ||
mod UnoptimizedContract { | ||
#[storage] | ||
struct Storage { | ||
balances: LegacyMap<ContractAddress, u256>, | ||
allowances: LegacyMap<(ContractAddress, ContractAddress), u256>, | ||
} | ||
|
||
#[external] | ||
fn transfer_all_from_list( | ||
ref self: ContractState, | ||
from: ContractAddress, | ||
to_list: Array<ContractAddress> | ||
) { | ||
// Unoptimized: Multiple storage reads/writes | ||
let len = to_list.len(); | ||
let mut i: usize = 0; | ||
loop { | ||
if i >= len { | ||
break; | ||
} | ||
let recipient = *to_list.at(i); | ||
let balance = self.balances.read(from); | ||
self.balances.write(from, 0); | ||
self.balances.write(recipient, balance); | ||
i += 1; | ||
}; | ||
} | ||
} | ||
``` | ||
|
||
## Profiler Output Analysis | ||
|
||
```cairo | ||
Function Call Trace: | ||
├── transfer_all_from_list (100%) | ||
├── storage_read (45%) | ||
│ └── steps: 2500 | ||
├── storage_write (40%) | ||
│ └── steps: 2200 | ||
└── array_operations (15%) | ||
└── steps: 800 | ||
|
||
``` | ||
|
||
### Key Issues Identified: | ||
|
||
1. Repeated storage reads for the same value | ||
2. Multiple write operations that could be batched | ||
3. High step count from storage operations | ||
|
||
### Optimized Version | ||
|
||
```cairo | ||
|
||
#[starknet::contract] | ||
mod OptimizedContract { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same |
||
#[storage] | ||
struct Storage { | ||
balances: LegacyMap<ContractAddress, u256>, | ||
allowances: LegacyMap<(ContractAddress, ContractAddress), u256>, | ||
} | ||
|
||
#[external] | ||
fn transfer_all_from_list( | ||
ref self: ContractState, | ||
from: ContractAddress, | ||
to_list: Array<ContractAddress> | ||
) { | ||
// Read once | ||
let total_balance = self.balances.read(from); | ||
let len = to_list.len(); | ||
|
||
// Clear sender balance first | ||
self.balances.write(from, 0); | ||
|
||
// Calculate individual amount | ||
let amount_per_recipient = total_balance / len.into(); | ||
|
||
// Batch write operations | ||
let mut i: usize = 0; | ||
loop { | ||
if i >= len { | ||
break; | ||
} | ||
let recipient = *to_list.at(i); | ||
self.balances.write(recipient, amount_per_recipient); | ||
i += 1; | ||
}; | ||
} | ||
} | ||
``` | ||
|
||
### Performance Comparison | ||
|
||
- Before: | ||
|
||
```cairo | ||
Total Steps: 5,500 | ||
Storage Reads: 100 (for list size 100) | ||
Storage Writes: 200 | ||
``` | ||
|
||
- After | ||
|
||
```cairo | ||
Total Steps: 2,300 (58% reduction) | ||
Storage Reads: 1 | ||
Storage Writes: 101 | ||
``` | ||
|
||
## Resources and Tools | ||
|
||
1. **pprof visualization**: | ||
|
||
```cairo | ||
go tool pprof -http=:8080 profile.pb.gz | ||
``` | ||
|
||
2. **Flame graph generation**: | ||
|
||
```cairo | ||
cairo-profiler trace.bin --output-path flame.svg --format=flamegraph | ||
``` | ||
|
||
## Advanced Profiling Features | ||
|
||
1. **Inlined Function Analysis** | ||
|
||
Enable with `--show-inlined-functions` to see detailed call stacks including inlined functions: | ||
|
||
```cairo | ||
cairo-profiler trace.bin --show-inlined-functions | ||
``` | ||
|
||
2. **Generic Function Analysis** | ||
|
||
Use `--split-generics` to analyze generic functions by their concrete types: | ||
|
||
```cairo | ||
cairo-profiler trace.bin --split-generics | ||
``` | ||
|
||
3. **Custom Resource Costs** | ||
|
||
Specify custom resource costs for different Starknet versions: | ||
|
||
```cairo | ||
cairo-profiler trace.bin --versioned-constants-path custom_costs.json | ||
``` | ||
|
||
## Best Practices | ||
|
||
1. **Storage Pattern Optimization** | ||
|
||
- Cache storage reads in memory when accessed multiple times | ||
- Batch storage writes where possible | ||
- Use storage spans for array operations | ||
|
||
2. **Function Call Optimization** | ||
|
||
- Monitor inlined vs non-inlined function costs | ||
- Use `--show-inlined-functions` to identify expensive inlined operations | ||
- Consider function composition based on profiler output | ||
|
||
3. **Memory Management** | ||
|
||
- Track memory_holes metric to identify inefficient memory usage | ||
- Use appropriate data structures to minimize memory fragmentation | ||
- Consider span operations for array access | ||
|
||
4. **L1 Communication Optimization** | ||
|
||
- Monitor l2_l1_message_sizes for cross-layer communication | ||
- Batch L1 messages when possible | ||
- Compress data when sending to L1 | ||
|
||
## Common Optimization Patterns | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Have you benchmarked all of these? |
||
|
||
1. **Storage Read Caching** | ||
|
||
```cairo | ||
// Before | ||
let value1 = storage.read(key); | ||
let value2 = storage.read(key); | ||
|
||
// After | ||
let cached = storage.read(key); | ||
let value1 = cached; | ||
let value2 = cached; | ||
``` | ||
|
||
2. **Batch Operations** | ||
|
||
```cairo | ||
// Before | ||
for item in items { | ||
storage.write(item.key, item.value); | ||
} | ||
|
||
// After | ||
let mut writes = Array::new(); | ||
// ... collect writes ... | ||
storage.write_multiple(writes); | ||
``` | ||
|
||
3. **Memory Efficiency** | ||
|
||
```cairo | ||
// Before | ||
let mut array = ArrayTrait::new(); | ||
// ... multiple push operations ... | ||
|
||
// After | ||
let mut array = ArrayTrait::with_capacity(size); | ||
// ... reserve space first ... | ||
``` | ||
|
||
## Profile-Guided Optimization Process | ||
|
||
1. **Baseline Profiling** | ||
|
||
```cairo | ||
cairo-profiler initial_trace.bin --output-path baseline.pb.gz | ||
``` | ||
|
||
2. **Identify Hotspots** | ||
|
||
```cairo | ||
Use flame graphs to identify expensive operations | ||
Focus on functions with high step counts | ||
Look for repeated storage operations | ||
``` | ||
|
||
3. **Optimize and Compare** | ||
|
||
```cairo | ||
cairo-profiler optimized_trace.bin --output-path optimized.pb.gz | ||
``` | ||
|
||
4. **Validate Improvements** | ||
|
||
- Compare step counts and resource usage | ||
- Verify optimization doesn't introduce new inefficiencies | ||
- Document performance gains |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you move this in a
listing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @julio4 do you mean the
listing
folder?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, listing folder. See this section of the contribution guide.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay