Skip to main content
Glama
sip012_performance_optimization.md14.9 kB
# SIP-012 Performance Optimization Guide ## Overview SIP-012 introduced significant performance improvements to the Stacks blockchain, including updated cost limits, improved runtime cost functions, and better storage optimization. This guide shows how to leverage these improvements for efficient Clarity contract development. **Key Improvements in SIP-012:** - **2x increase** in MARF read/write operations per block - **Dynamic list storage** assessment based on actual length vs. maximum length - **100+ optimized cost functions** for better performance estimation - **Enhanced block throughput** capabilities ## Understanding Stacks Compute Resources ### Five Compute Dimensions Every Clarity operation consumes resources across five dimensions: ```clarity ;; 1. RUNTIME - CPU time for execution ;; 2. READ_COUNT - Number of database reads ;; 3. READ_LENGTH - Total bytes read from database ;; 4. WRITE_COUNT - Number of database writes ;; 5. WRITE_LENGTH - Total bytes written to database ``` ### Block Limits (Post SIP-012) ```clarity ;; Current mainnet limits after SIP-012: ;; RUNTIME: 5,000,000,000 (5 billion units) ;; READ_COUNT: 15,000 (doubled from 7,500) ;; READ_LENGTH: 100,000,000 (100MB) ;; WRITE_COUNT: 15,000 (doubled from 7,500) ;; WRITE_LENGTH: 15,000,000 (15MB) ``` ## Runtime Cost Analysis ### 1. Function Cost Profiling Use this approach to analyze your contract's performance: ```clarity ;; High-cost operations to monitor: ;; - Map operations (map-get?, map-set, map-delete) ;; - List operations (append, filter, fold) ;; - String operations (concat, slice) ;; - Mathematical operations on large numbers ;; Example: Analyzing a token transfer function (define-public (transfer (amount uint) (sender principal) (recipient principal)) (begin ;; READ_COUNT: +1 (balance lookup) ;; READ_LENGTH: +~40 bytes (uint + principal data) (asserts! (>= (ft-get-balance my-token sender) amount) (err u1)) ;; WRITE_COUNT: +2 (sender balance update, recipient balance update) ;; WRITE_LENGTH: +~80 bytes (two uint updates) ;; RUNTIME: ~50,000 units (transfer operation) (try! (ft-transfer? my-token amount sender recipient)) (ok true) ) ) ``` ### 2. Cost Optimization Strategies #### A. Minimize Database Operations ```clarity ;; ❌ BAD: Multiple map operations (define-public (bad-update-user (user principal) (name (string-ascii 50)) (age uint)) (begin (map-set user-names user name) ;; WRITE_COUNT: +1 (map-set user-ages user age) ;; WRITE_COUNT: +1 (map-set user-updated user block-height) ;; WRITE_COUNT: +1 (ok true) ) ) ;; ✅ GOOD: Single map operation with tuple (define-map user-data principal {name: (string-ascii 50), age: uint, updated: uint}) (define-public (good-update-user (user principal) (name (string-ascii 50)) (age uint)) (begin ;; WRITE_COUNT: +1 (single operation) ;; WRITE_LENGTH: ~60 bytes (optimized storage) (map-set user-data user {name: name, age: age, updated: block-height}) (ok true) ) ) ``` #### B. Optimize List Operations ```clarity ;; ❌ BAD: Inefficient list processing (define-public (bad-process-list (items (list 100 uint))) (let ( ;; RUNTIME: O(n²) - very expensive! (filtered (filter process-item items)) (mapped (map transform-item filtered)) ) (fold add-item mapped u0) ) ) ;; ✅ GOOD: Single-pass processing (define-public (good-process-list (items (list 100 uint))) ;; RUNTIME: O(n) - much more efficient (fold process-and-accumulate items u0) ) (define-private (process-and-accumulate (item uint) (acc uint)) (if (process-item item) (+ acc (transform-item item)) acc ) ) ``` #### C. Leverage SIP-012 Dynamic List Storage ```clarity ;; SIP-012 improvement: Storage cost based on actual length, not max length ;; Before SIP-012: Assessed for full 1000 items regardless of actual size ;; After SIP-012: Assessed only for items actually stored (define-data-var dynamic-list (list 1000 uint) (list)) (define-public (add-item (item uint)) (begin ;; WRITE_LENGTH: Only charged for actual list size + new item ;; Much more efficient than pre-SIP-012 (var-set dynamic-list (unwrap! (as-max-len? (append (var-get dynamic-list) item) u1000) (err u1))) (ok true) ) ) ``` ### 3. Advanced Performance Patterns #### A. Batch Operations ```clarity ;; Optimize for batch processing to reduce per-operation overhead (define-public (batch-transfer (transfers (list 25 {recipient: principal, amount: uint}))) (begin ;; Single authorization check (asserts! (is-eq tx-sender contract-owner) (err u1)) ;; Batch processing reduces overhead per transfer (fold execute-transfer transfers (ok u0)) ) ) (define-private (execute-transfer (transfer {recipient: principal, amount: uint}) (result (response uint uint)) ) (match result success (ft-transfer? my-token (get amount transfer) tx-sender (get recipient transfer)) error (err error) ) ) ``` #### B. Lazy Computation ```clarity ;; Defer expensive computations until needed (define-map computed-values uint uint) (define-map computation-needed uint bool) (define-public (expensive-computation (input uint)) (match (map-get? computed-values input) cached-result (ok cached-result) (let ( ;; Only compute if not cached (result (complex-calculation input)) ) (map-set computed-values input result) (ok result) ) ) ) ``` #### C. Memory-Efficient Data Structures ```clarity ;; Use efficient data representations (define-map user-balances principal { ;; Pack multiple values into single map entry balance: uint, last-transfer: uint, transfer-count: uint, flags: uint ;; Bit-packed boolean flags } ) ;; Bit operations for efficient flag storage (SIP-020) (define-constant FLAG-FROZEN (pow u2 u0)) ;; Bit 0 (define-constant FLAG-VERIFIED (pow u2 u1)) ;; Bit 1 (define-constant FLAG-PREMIUM (pow u2 u2)) ;; Bit 2 (define-private (has-flag (flags uint) (flag uint)) (> (bit-and flags flag) u0) ) (define-private (set-flag (flags uint) (flag uint)) (bit-or flags flag) ) ``` ## Performance Monitoring Tools ### 1. Cost Analysis Functions ```clarity ;; Add these to your contracts for monitoring (define-read-only (estimate-transfer-cost) ;; Rough estimation based on SIP-012 costs { runtime: u50000, ;; ~50k runtime units read-count: u1, ;; Balance lookup read-length: u40, ;; Principal + uint write-count: u2, ;; Two balance updates write-length: u80 ;; Two uint writes } ) (define-read-only (estimate-batch-cost (batch-size uint)) ;; Linear scaling with some fixed overhead { runtime: (+ u10000 (* batch-size u45000)), read-count: (+ u1 batch-size), read-length: (+ u40 (* batch-size u40)), write-count: (* batch-size u2), write-length: (* batch-size u80) } ) ``` ### 2. Performance Testing ```typescript // Clarinet testing for performance describe('Performance Tests', () => { it('should handle large batch operations efficiently', () => { const batchSize = 100; const transfers = Array.from({ length: batchSize }, (_, i) => ({ recipient: `ST${i.toString().padStart(38, '0')}`, amount: 1000 })); const result = simnet.callPublicFn( 'my-contract', 'batch-transfer', [Cl.list(transfers.map(t => Cl.tuple({ recipient: Cl.principal(t.recipient), amount: Cl.uint(t.amount) })))], deployer ); expect(result.result).toBeOk(); // Verify transaction cost is within reasonable bounds expect(result.cost.runtime).toBeLessThan(5000000); // 5M runtime limit expect(result.cost.read_count).toBeLessThan(15000); // 15k read limit }); }); ``` ### 3. Gas Optimization Checklist ```clarity ;; Performance optimization checklist: ;; ✅ Data Structure Optimization ;; - Use tuples instead of multiple maps ;; - Pack multiple boolean flags into uint ;; - Choose appropriate data types (uint vs int, string-ascii vs string-utf8) ;; ✅ Algorithm Optimization ;; - Minimize nested loops ;; - Use single-pass operations where possible ;; - Implement early exits in conditionals ;; ✅ Storage Optimization ;; - Leverage SIP-012 dynamic list sizing ;; - Use lazy computation for expensive operations ;; - Implement caching for frequently accessed data ;; ✅ Function Design ;; - Batch operations when possible ;; - Minimize cross-contract calls ;; - Use read-only functions for pure computations ``` ## Real-World Optimization Examples ### 1. NFT Collection with Efficient Metadata ```clarity ;; Optimized NFT contract leveraging SIP-012 improvements (define-non-fungible-token efficient-nft uint) ;; Pack metadata efficiently (define-map token-metadata uint { name: (string-ascii 64), rarity: uint, ;; 0-100 rarity score traits: uint ;; Bit-packed traits } ) ;; Dynamic list for token ownership (SIP-012 benefit) (define-map owner-tokens principal (list 1000 uint)) (define-public (mint-batch (recipients (list 50 principal))) (let ( (start-id (+ (var-get last-token-id) u1)) ) ;; Efficient batch minting (fold mint-to-recipient (zip recipients (range start-id (+ start-id (len recipients)))) (ok start-id) ) ) ) (define-private (mint-to-recipient (data {recipient: principal, token-id: uint}) (result (response uint uint)) ) (match result success-id (begin ;; Single map operations for efficiency (try! (nft-mint? efficient-nft (get token-id data) (get recipient data))) ;; Update owner list (benefits from SIP-012 dynamic sizing) (map-set owner-tokens (get recipient data) (unwrap! (as-max-len? (append (default-to (list) (map-get? owner-tokens (get recipient data))) (get token-id data)) u1000) (err u2)) ) (ok (get token-id data)) ) error (err error) ) ) ``` ### 2. High-Performance DeFi Pool ```clarity ;; Optimized AMM pool contract (define-fungible-token pool-token) ;; Efficient state management (define-data-var pool-state { reserve-a: uint, reserve-b: uint, total-liquidity: uint, last-update: uint, k-value: uint ;; Cached constant product } {reserve-a: u0, reserve-b: u0, total-liquidity: u0, last-update: u0, k-value: u0} ) (define-public (swap (token-in uint) (min-out uint)) (let ( (state (var-get pool-state)) ;; Single calculation for swap output (output (calculate-swap-output token-in (get reserve-a state) (get reserve-b state))) ) ;; Validate slippage (asserts! (>= output min-out) (err u1)) ;; Single state update for efficiency (var-set pool-state (merge state { reserve-a: (+ (get reserve-a state) token-in), reserve-b: (- (get reserve-b state) output), last-update: block-height, k-value: (* (+ (get reserve-a state) token-in) (- (get reserve-b state) output)) })) ;; Execute transfers (try! (contract-call? .token-a transfer token-in tx-sender (as-contract tx-sender) none)) (try! (as-contract (contract-call? .token-b transfer output tx-sender (as-contract tx-sender) none))) (ok output) ) ) ;; Optimized calculation using SIP-012 improved arithmetic costs (define-private (calculate-swap-output (amount-in uint) (reserve-in uint) (reserve-out uint)) (let ( ;; Fee: 0.3% (997/1000) (amount-in-with-fee (* amount-in u997)) (numerator (* amount-in-with-fee reserve-out)) (denominator (+ (* reserve-in u1000) amount-in-with-fee)) ) (/ numerator denominator) ) ) ``` ### 3. Efficient Governance System ```clarity ;; Optimized governance contract (define-map proposals uint { title: (string-ascii 100), description: (string-ascii 500), voting-ends: uint, votes-for: uint, votes-against: uint, executed: bool } ) ;; Efficient vote tracking (define-map votes {proposal-id: uint, voter: principal} { weight: uint, support: bool, timestamp: uint } ) (define-public (vote-batch (votes (list 20 {proposal-id: uint, support: bool}))) (let ( (voter-weight (get-voting-weight tx-sender)) ) ;; Single weight lookup, batch voting (fold execute-vote votes (ok voter-weight)) ) ) (define-private (execute-vote (vote-data {proposal-id: uint, support: bool}) (result (response uint uint)) ) (match result weight (begin ;; Record vote (map-set votes {proposal-id: (get proposal-id vote-data), voter: tx-sender} {weight: weight, support: (get support vote-data), timestamp: block-height} ) ;; Update proposal vote counts efficiently (update-proposal-votes (get proposal-id vote-data) weight (get support vote-data)) ) error (err error) ) ) ``` ## Performance Best Practices Summary ### ✅ Do This 1. **Minimize Database Operations** ```clarity ;; Single map with tuple > multiple maps (define-map user-data principal {name: (string-ascii 50), balance: uint}) ``` 2. **Leverage SIP-012 Dynamic Lists** ```clarity ;; Storage cost based on actual size, not max size (define-data-var items (list 1000 uint) (list)) ``` 3. **Use Batch Operations** ```clarity ;; Process multiple items in single call (define-public (batch-process (items (list 50 uint)))) ``` 4. **Cache Expensive Computations** ```clarity ;; Store results to avoid recomputation (define-map computation-cache uint uint) ``` 5. **Optimize Data Types** ```clarity ;; Use appropriate types ;; uint for positive numbers, string-ascii for ASCII text ``` ### ❌ Avoid This 1. **Multiple Database Hits** ```clarity ;; ❌ Bad: Multiple map operations (map-set map1 key val1) (map-set map2 key val2) ``` 2. **Inefficient Loops** ```clarity ;; ❌ Bad: Nested iterations (map (lambda (x) (filter condition items)) outer-items) ``` 3. **Unnecessary Computations** ```clarity ;; ❌ Bad: Recomputing same values (+ (complex-calculation x) (complex-calculation x)) ``` ## Conclusion SIP-012 provides significant performance improvements that enable more sophisticated Clarity contracts. By understanding the cost model and implementing these optimization strategies, developers can build highly efficient dApps that make the best use of Stacks' computational resources. **Key Takeaways:** - **2x database capacity** enables more complex state management - **Dynamic list sizing** reduces storage costs significantly - **Optimized cost functions** provide more accurate performance modeling - **Batch operations** and **efficient data structures** are crucial for performance - **Profile and measure** your contract's resource usage regularly

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/exponentlabshq/stacks-clarity-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server