diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 40f4b0ef..7a570db3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,6 +10,12 @@ You should never open a pull request to merge your changes directly into `main`. The `dev` branch is deployed at +The release branch is `main`. The development branch is `dev` and is considered stable (but not released yet). +When you want to contribute, please create a new branch from `dev` and open a pull request to merge your changes back into `dev`. +You should never open a pull request to merge your changes directly into `main`. + +The `dev` branch is deployed at https://starknet-by-example-dev.voyager.online/ + Please note we have a code of conduct, please follow it in all your interactions with the project. ## Table of Contents diff --git a/package.json b/package.json index 1d9f92f5..93089196 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,12 @@ "vocs": "patches/vocs.patch" }, "overrides": { + "vocs>shiki": "^1.23.0", + "vocs>@shikijs/core": "^1.23.0", + "vocs>@shikijs/types": "^1.23.0", + "vocs>@shikijs/rehype": "^1.23.0", + "vocs>@shikijs/twoslash": "^1.23.0", + "vocs>@shikijs/transformers": "^1.23.0" "vocs>shiki": "^1.23.0 <1.24.0", "vocs>@shikijs/core": "^1.23.0 <1.24.0", "vocs>@shikijs/types": "^1.23.0 <1.24.0", diff --git a/pages/advanced-concepts/optimisations/cairo-profiler.md b/pages/advanced-concepts/optimisations/cairo-profiler.md new file mode 100644 index 00000000..64769556 --- /dev/null +++ b/pages/advanced-concepts/optimisations/cairo-profiler.md @@ -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 [OPTIONS] + +Options: + -o, --output-path Output path [default: profile.pb.gz] + --show-details Show contract addresses and selectors + --max-function-stack-trace-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 Custom resource costs file + +``` + +### Example: Optimizing Storage Access + +### Unoptimized Code + +```cairo +#[starknet::contract] +mod UnoptimizedContract { + #[storage] + struct Storage { + balances: LegacyMap, + allowances: LegacyMap<(ContractAddress, ContractAddress), u256>, + } + + #[external] + fn transfer_all_from_list( + ref self: ContractState, + from: ContractAddress, + to_list: Array + ) { + // 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 { + #[storage] + struct Storage { + balances: LegacyMap, + allowances: LegacyMap<(ContractAddress, ContractAddress), u256>, + } + + #[external] + fn transfer_all_from_list( + ref self: ContractState, + from: ContractAddress, + to_list: Array + ) { + // 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 + +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 diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 06575621..73883771 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,6 +5,16 @@ settings: excludeLinksFromLockfile: false overrides: + vocs>shiki: ^1.23.0 + vocs>@shikijs/core: ^1.23.0 + vocs>@shikijs/types: ^1.23.0 + vocs>@shikijs/rehype: ^1.23.0 + vocs>@shikijs/twoslash: ^1.23.0 + vocs>@shikijs/transformers: ^1.23.0 + +patchedDependencies: + vocs: + hash: 7wumpnts656yvepr4seo2mjn34 vocs>shiki: ^1.23.0 <1.24.0 vocs>@shikijs/core: ^1.23.0 <1.24.0 vocs>@shikijs/types: ^1.23.0 <1.24.0 @@ -41,6 +51,8 @@ importers: version: 5.7.3 vocs: specifier: 1.0.0-alpha.62 + version: 1.0.0-alpha.62(patch_hash=7wumpnts656yvepr4seo2mjn34)(@types/node@22.8.6)(@types/react-dom@18.3.1)(@types/react@18.3.12)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.24.3)(typescript@5.6.3) + version: 1.0.0-alpha.62(patch_hash=472ea4771ea28176fd7a35b8f83fe164ce160c07b9b50111ae749eb8da92176d)(@types/node@22.13.1)(@types/react-dom@18.3.5(@types/react@19.0.8))(@types/react@19.0.8)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(rollup@4.34.6)(typescript@5.7.3) version: 1.0.0-alpha.62(patch_hash=472ea4771ea28176fd7a35b8f83fe164ce160c07b9b50111ae749eb8da92176d)(@types/node@22.13.1)(@types/react-dom@18.3.5(@types/react@19.0.9))(@types/react@19.0.9)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(rollup@4.34.6)(typescript@5.7.3) devDependencies: '@types/react-dom': @@ -1384,6 +1396,9 @@ packages: emoji-regex-xs@1.0.0: resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==} + emoji-regex-xs@1.0.0: + resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==} + emoji-regex@10.4.0: resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==} @@ -2287,12 +2302,16 @@ packages: regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + regex-recursion@4.2.1: + resolution: {integrity: sha512-QHNZyZAeKdndD1G3bKAbBEKOSSK4KOHQrAJ01N1LJeb0SoH4DJIeFhp0uUpETgONifS4+P3sOgoA1dhzgrQvhA==} regex-recursion@4.3.0: resolution: {integrity: sha512-5LcLnizwjcQ2ALfOj95MjcatxyqF5RPySx9yT+PaXu3Gox2vyAtLDjHB8NTJLtMGkvyau6nI3CfpwFCjPUIs/A==} regex-utilities@2.3.0: resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} + regex@5.0.2: + resolution: {integrity: sha512-/pczGbKIQgfTMRV0XjABvc5RzLqQmwqxLHdQao2RTXPk+pmTXB2P0IaUHYdYyk412YLwUIkaeMd5T+RzVgTqnQ==} regex@5.1.1: resolution: {integrity: sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw==} @@ -3501,6 +3520,7 @@ snapshots: '@shikijs/engine-javascript': 1.23.1 '@shikijs/engine-oniguruma': 1.23.1 '@shikijs/types': 1.23.1 + '@shikijs/vscode-textmate': 9.3.0 '@shikijs/vscode-textmate': 9.3.1 '@types/hast': 3.0.4 hast-util-to-html: 9.0.4 @@ -3508,12 +3528,14 @@ snapshots: '@shikijs/engine-javascript@1.23.1': dependencies: '@shikijs/types': 1.23.1 + '@shikijs/vscode-textmate': 9.3.0 '@shikijs/vscode-textmate': 9.3.1 oniguruma-to-es: 0.4.1 '@shikijs/engine-oniguruma@1.23.1': dependencies: '@shikijs/types': 1.23.1 + '@shikijs/vscode-textmate': 9.3.0 '@shikijs/vscode-textmate': 9.3.1 '@shikijs/rehype@1.23.1': @@ -3528,7 +3550,11 @@ snapshots: '@shikijs/transformers@1.23.1': dependencies: shiki: 1.23.1 - + '@shikijs/twoslash@1.23.1(typescript@5.6.3)': + dependencies: + '@shikijs/core': 1.23.1 + '@shikijs/types': 1.23.1 + twoslash: 0.2.12(typescript@5.6.3) '@shikijs/twoslash@1.23.1(typescript@5.7.3)': dependencies: '@shikijs/core': 1.23.1 @@ -3937,6 +3963,8 @@ snapshots: emoji-regex-xs@1.0.0: {} + emoji-regex-xs@1.0.0: {} + emoji-regex@10.4.0: {} emoji-regex@8.0.0: {} @@ -5023,6 +5051,8 @@ snapshots: oniguruma-to-es@0.4.1: dependencies: emoji-regex-xs: 1.0.0 + regex: 5.0.2 + regex-recursion: 4.2.1 regex: 5.1.1 regex-recursion: 4.3.0 @@ -5268,12 +5298,14 @@ snapshots: regenerator-runtime@0.14.1: {} + regex-recursion@4.2.1: regex-recursion@4.3.0: dependencies: regex-utilities: 2.3.0 regex-utilities@2.3.0: {} + regex@5.0.2: regex@5.1.1: dependencies: regex-utilities: 2.3.0 @@ -5488,6 +5520,7 @@ snapshots: '@shikijs/engine-javascript': 1.23.1 '@shikijs/engine-oniguruma': 1.23.1 '@shikijs/types': 1.23.1 + '@shikijs/vscode-textmate': 9.3.0 '@shikijs/vscode-textmate': 9.3.1 '@types/hast': 3.0.4 @@ -5777,6 +5810,8 @@ snapshots: '@types/node': 22.13.1 fsevents: 2.3.3 + vocs@1.0.0-alpha.62(patch_hash=7wumpnts656yvepr4seo2mjn34)(@types/node@22.8.6)(@types/react-dom@18.3.1)(@types/react@18.3.12)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.24.3)(typescript@5.6.3): + vocs@1.0.0-alpha.62(patch_hash=472ea4771ea28176fd7a35b8f83fe164ce160c07b9b50111ae749eb8da92176d)(@types/node@22.13.1)(@types/react-dom@18.3.5(@types/react@19.0.8))(@types/react@19.0.8)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(rollup@4.34.6)(typescript@5.7.3): vocs@1.0.0-alpha.62(patch_hash=472ea4771ea28176fd7a35b8f83fe164ce160c07b9b50111ae749eb8da92176d)(@types/node@22.13.1)(@types/react-dom@18.3.5(@types/react@19.0.9))(@types/react@19.0.9)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(rollup@4.34.6)(typescript@5.7.3): dependencies: '@floating-ui/react': 0.26.28(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -5785,6 +5820,19 @@ snapshots: '@mdx-js/rollup': 3.1.0(acorn@8.14.0)(rollup@4.34.6) '@noble/hashes': 1.7.1 '@radix-ui/colors': 3.0.0 + '@radix-ui/react-accordion': 1.2.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-dialog': 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-icons': 1.3.1(react@18.3.1) + '@radix-ui/react-label': 2.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-navigation-menu': 1.2.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-popover': 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-tabs': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@shikijs/rehype': 1.23.1 + '@shikijs/transformers': 1.23.1 + '@shikijs/twoslash': 1.23.1(typescript@5.6.3) + '@vanilla-extract/css': 1.16.0 + '@radix-ui/react-accordion': 1.2.3(@types/react-dom@18.3.5(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-dialog': 1.1.6(@types/react-dom@18.3.5(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@radix-ui/react-accordion': 1.2.3(@types/react-dom@18.3.5(@types/react@19.0.9))(@types/react@19.0.9)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@radix-ui/react-dialog': 1.1.6(@types/react-dom@18.3.5(@types/react@19.0.9))(@types/react@19.0.9)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@radix-ui/react-icons': 1.3.2(react@19.0.0) @@ -5835,6 +5883,7 @@ snapshots: remark-parse: 11.0.0 serve-static: 1.16.2 shiki: 1.23.1 + tailwindcss: 3.4.14 tailwindcss: 3.4.17 toml: 3.0.0 twoslash: 0.2.12(typescript@5.7.3)