{"id":21870021,"url":"https://github.com/solana-developers/cu_optimizations","last_synced_at":"2025-04-14T23:42:51.006Z","repository":{"id":227756224,"uuid":"771478162","full_name":"solana-developers/cu_optimizations","owner":"solana-developers","description":"Collection of potential CU optimizations for programs. ","archived":false,"fork":false,"pushed_at":"2024-10-18T14:15:18.000Z","size":636,"stargazers_count":64,"open_issues_count":1,"forks_count":4,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-04-14T23:42:47.530Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/solana-developers.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-03-13T11:23:32.000Z","updated_at":"2025-03-17T14:25:29.000Z","dependencies_parsed_at":"2024-05-03T14:24:41.390Z","dependency_job_id":"3ba61ff6-1446-4207-8674-bfd57bd997e1","html_url":"https://github.com/solana-developers/cu_optimizations","commit_stats":null,"previous_names":["woody4618/cu_optimizations"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/solana-developers%2Fcu_optimizations","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/solana-developers%2Fcu_optimizations/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/solana-developers%2Fcu_optimizations/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/solana-developers%2Fcu_optimizations/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/solana-developers","download_url":"https://codeload.github.com/solana-developers/cu_optimizations/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248981259,"owners_count":21193143,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-28T06:09:58.777Z","updated_at":"2025-04-14T23:42:50.978Z","avatar_url":"https://github.com/solana-developers.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Optimizing CU in programs\n\n## Introduction\n\nThe get started there is a [video guide](https://www.youtube.com/watch?v=7CbAK7Oq_o4) on how to optimize CU in programs which covers the contents in this repository.\nAnd also a [Part1](https://www.youtube.com/watch?v=xoJ-3NkYXfY\u0026ab_channel=Solandy%5Bsolandy.sol%5D) and [Part2](https://www.youtube.com/watch?v=Pwly1cOa2hg\u0026ab_channel=Solandy%5Bsolandy.sol%5D) on how optimize programs by SolAndy.\n\nEvery block on Solana has a blockspace limit of 48 million CUs and a 12 million CUs per account write lock. If you exhaust the CU limit your transaction will fail. Optimizing your program CUs has many advantages.\n\nCurrently every transactions on Solana costs 5000 lamports per signature independant on the compute units used.\nFour reasons on why to optimize CU anyway:\n\n1. A smaller transaction is more likely to be included in a block.\n2. Currently every transaction costs 5000 lamports/sig no matter the CU. This may change in the future. Better be prepared.\n3. It makes your program more composable, because when another program does a CPI in your program it also need to cover your CU.\n4. More block space for every one. One block could theoretically hold around tens of thousands of simple transfer transactions for example. So the less intensive each transaction is the more we can get in a block.\n\nBy default every transaction on solana requests 200.000 CUs.\nWith the call setComputeLimit this can be increased to a maximum of 1.4 million.\n\nIf you are only doing a simple transfer, you can set the CU limit to 300 for example.\n\n```js\nconst computeLimitIx = ComputeBudgetProgram.setComputeUnitLimit({\n  units: 300,\n});\n```\n\nThere can also be priority fees which can be set like this:\n\n```js\nconst computePriceIx = ComputeBudgetProgram.setComputeUnitPrice({\n  microLamports: 1,\n});\n```\n\nThis means for every requested CU, 1 microLamport is paid. This would result in a fee of 0.2 lamports.\nThese instructions can be put into a transaction at any position.\n\n```js\nconst transaction = new Transaction().add(computePriceIx, computeLimitIx, ...);\n```\n\nFind Compute Budget code here:\nhttps://github.com/solana-labs/solana/blob/090e11210aa7222d8295610a6ccac4acda711bb9/program-runtime/src/compute_budget.rs#L26-L87\n\nBlocks are packed using the real CU used and not the requested CU.\n\nThere may be a CU cost to loaded account size as well soon with a maximum of 16.000 CU which would be charges heap space at rate of 8cu per 32K. (Max loaded accounts per transaction is 64Mb)\nhttps://github.com/solana-labs/solana/issues/29582\n\n## How to measure CU\n\nThe best way to measure CU is to use the solana program log.\n\nFor that you can use this macro:\n\n```rust\n/// Total extra compute units used per compute_fn! call 409 CU\n/// https://github.com/anza-xyz/agave/blob/d88050cda335f87e872eddbdf8506bc063f039d3/programs/bpf_loader/src/syscalls/logging.rs#L70\n/// https://github.com/anza-xyz/agave/blob/d88050cda335f87e872eddbdf8506bc063f039d3/program-runtime/src/compute_budget.rs#L150\n#[macro_export]\nmacro_rules! compute_fn {\n    ($msg:expr=\u003e $($tt:tt)*) =\u003e {\n        ::solana_program::msg!(concat!($msg, \" {\"));\n        ::solana_program::log::sol_log_compute_units();\n        let res = { $($tt)* };\n        ::solana_program::log::sol_log_compute_units();\n        ::solana_program::msg!(concat!(\" } // \", $msg));\n        res\n    };\n}\n```\n\nYou put it in front and after the code block you want to measure like so:\n\n```rust\n\ncompute_fn!(\"My message\" =\u003e {\n    // Your code here\n});\n\n```\n\nThen you can paste the logs into chatGPT and let i calculate the CU for you if you dont want to do it yourself.\nHere is the original from @thlorenz https://github.com/thlorenz/sol-contracts/blob/master/packages/sol-common/rust/src/lib.rs\n\n# Optimizations\n\n## 1 Logging\n\nLogging is very expensive. Especially logging pubkeys and concatenating strings. Use pubkey.log instead if you need it and only log what is really necessary.\n\n```rust\n// 11962 CU !!\n// Base58 encoding is expensive, concatenation is expensive\ncompute_fn! { \"Log a pubkey to account info\" =\u003e\n    msg!(\"A string {0}\", ctx.accounts.counter.to_account_info().key());\n}\n\n// 262 cu\ncompute_fn! { \"Log a pubkey\" =\u003e\n    ctx.accounts.counter.to_account_info().key().log();\n}\n\nlet pubkey = ctx.accounts.counter.to_account_info().key();\n\n// 206 CU\ncompute_fn! { \"Pubkey.log\" =\u003e\n    pubkey.log();\n}\n\n// 357 CU - string concatenation is expensive\ncompute_fn! { \"Log a pubkey simple concat\" =\u003e\n    msg!(\"A string {0}\", \"5w6z5PWvtkCd4PaAV7avxE6Fy5brhZsFdbRLMt8UefRQ\");\n}\n```\n\n## 2 Data Types\n\nBigger data types cost more CU. Use the smallest data type possible.\n\n```rust\n// 357\ncompute_fn! { \"Push Vector u64 \" =\u003e\n    let mut a: Vec\u003cu64\u003e = Vec::new();\n    a.push(1);\n    a.push(1);\n    a.push(1);\n    a.push(1);\n    a.push(1);\n    a.push(1);\n}\n\n// 211 CU\ncompute_fn! { \"Vector u8 \" =\u003e\n    let mut a: Vec\u003cu8\u003e = Vec::new();\n    a.push(1);\n    a.push(1);\n    a.push(1);\n    a.push(1);\n    a.push(1);\n    a.push(1);\n}\n\n```\n\n## 3 Serialization\n\nBorsh serialization can be expensive depending on the account structs, use zero copy when possible and directly interact with the memory. It also saves more stack space than boxing. Operations on the stack are slightly more efficient though.\n\n```rust\n// 6302 CU\npub fn initialize(_ctx: Context\u003cInitializeCounter\u003e) -\u003e Result\u003c()\u003e {\n    Ok(())\n}\n\n// 5020 CU\npub fn initialize_zero_copy(_ctx: Context\u003cInitializeCounterZeroCopy\u003e) -\u003e Result\u003c()\u003e {\n    Ok(())\n}\n```\n\n```rust\n// 108 CU - total CU including serialization 2600\nlet counter = \u0026mut ctx.accounts.counter;\ncompute_fn! { \"Borsh Serialize\" =\u003e\n    counter.count = counter.count.checked_add(1).unwrap();\n}\n\n// 151 CU - total CU including serialization 1254\nlet counter = \u0026mut ctx.accounts.counter_zero_copy.load_mut()?;\ncompute_fn! { \"Zero Copy Serialize\" =\u003e\n    counter.count = counter.count.checked_add(1).unwrap();\n}\n```\n\n## 4 PDAs\n\nDepending on the seeds find_program_address can use multiple loops and become very expensive. You can save the bump in an account or pass it in from the client to remove this overhead:\n\n```rust\npub fn pdas(ctx: Context\u003cPdaAccounts\u003e) -\u003e Result\u003c()\u003e {\n    let program_id = Pubkey::from_str(\"5w6z5PWvtkCd4PaAV7avxE6Fy5brhZsFdbRLMt8UefRQ\").unwrap();\n\n    // 12,136 CUs\n    compute_fn! { \"Find PDA\" =\u003e\n        Pubkey::find_program_address(\u0026[b\"counter\"], ctx.program_id);\n    }\n\n    // 1,651 CUs\n    compute_fn! { \"Find PDA\" =\u003e\n        Pubkey::create_program_address(\u0026[b\"counter\", \u0026[248_u8]], \u0026program_id).unwrap();\n    }\n\n    Ok(())\n}\n\n#[derive(Accounts)]\npub struct PdaAccounts\u003c'info\u003e {\n    #[account(mut)]\n    pub counter: Account\u003c'info, CounterData\u003e,\n    // 12,136 CUs when not defining the bump\n    #[account(\n        seeds = [b\"counter\"],\n        bump\n    )]\n    pub counter_checked: Account\u003c'info, CounterData\u003e,\n}\n\n#[derive(Accounts)]\npub struct PdaAccounts\u003c'info\u003e {\n    #[account(mut)]\n    pub counter: Account\u003c'info, CounterData\u003e,\n    // only 1600 if using the bump that is saved in the counter_checked account\n    #[account(\n        seeds = [b\"counter\"],\n        bump = counter_checked.bump\n    )]\n    pub counter_checked: Account\u003c'info, CounterData\u003e,\n}\n\n```\n\n## 5 Closures and function\n\nDuring the tests it looks like that closures, function calls and inlining have a similar cost and were well optimized by the compiler.\n\n## 6 CPIs\n\nEvery CPI comes with a cost and you also need to calculate in the costs of the called programs function.\nIf possible avoid doing many CPIs.\nI did not find a difference in CPI cost between anchor and native and the optimization is more in the called function.\nA CPI for a transfer with the system program costs 2215 CU.\nInteresting is though that error handling also costs a lot of CU. So profile how you handle errors and optimize there.\n\n```rust\n// 2,215 CUs\ncompute_fn! { \"CPI system program\" =\u003e\n    let cpi_context = CpiContext::new(\n        ctx.accounts.system_program.to_account_info(),\n        system_program::Transfer {\n            from: ctx.accounts.payer.to_account_info().clone(),\n            to: ctx.accounts.counter.to_account_info().clone(),\n        },\n    );\n    system_program::transfer(cpi_context, 1)?;\n}\n\n// 251 CUs. In an error case though the whole transactions is 1,199 CUs bigger than without. So error handling is expensive\ncompute_fn! { \"Transfer borrowing lamports\" =\u003e\n    let counter_account_info = ctx.accounts.counter.to_account_info();\n    let mut source_lamports = counter_account_info.try_borrow_mut_lamports()?;\n    const AMOUNT: u64 = 1;\n    if **source_lamports \u003c AMOUNT {\n        msg!(\"source account has less than {} lamports\", AMOUNT);\n        let err = Err(anchor_lang::error::Error::from(ProgramError::InsufficientFunds));\n        return err;\n    }\n    **source_lamports -= AMOUNT;\n    let payer_account_info = ctx.accounts.payer.to_account_info();\n    **payer_account_info.try_borrow_mut_lamports()? += AMOUNT;\n}\n```\n\n## 7 Checked Math\n\nIt turns out that checked math is more expensive than unchecked math. This is because on every operation the program needs to check if the operation is valid and does not under- or overflow for example. If you are sure that the operation is valid you can use unchecked math so save some CU.\nAlso the compiler is very good at optimizing the code and inline checks and remove unnecessary calculations if a value is not used for example.\nThis is why the functions have the `no_inline` attribute to prevent the compiler from optimizing the code away and the calculations are in public functions and the result values are logged after the calculation.\n\n```rust\n// Testing checked_mul 97314 CU\ncompute_fn! { \"checked mul\" =\u003e\n    test_checked_mul(test_value_mul, 7, 200, 60);\n}\n\nmsg!(\"Test value mul: {}\", test_value_mul);\n\n// Testing bit_shift 85113 CU\ncompute_fn! { \"bit shift\" =\u003e\n    check_bit_shift(test_value_shift, 7, 200, 60);\n}\n```\n\n## 8 Clone vs Reference\n\nHere we can see that passing by reference is cheaper than cloning.\nInteresting is also that due to the bump allocator that solana\nuses we will run out of memory as soon as we go to 40 iterations\non the loop that clones the vector. This is because the bump allocator\ndoes not free memory and we only have 32 KB of heap space. By passing in the balances vector by reference we can avoid this problem.\n\n```rust\nlet balances = vec![10_u64; 100]; // a vector with 10,000 elements\n\n// 47683 CU\ncompute_fn! { \"Pass by reference\" =\u003e\n    for _ in 0..39 {\n        sum_reference += sum_vector_by_reference(\u0026balances);\n    }\n}\n\nmsg!(\"Sum by reference: {}\", sum_reference);\n\nlet mut sum_clone = 0;\n\n// 49322 CU\ncompute_fn! { \"Clone\" =\u003e\n    for _ in 0..39 {\n        sum_clone += sum_vector_by_value(balances.clone());\n    }\n}\n\nmsg!(\"Sum by clone: {}\", sum_clone);\n```\n\n## 9 Native vs Anchor vs Asm vs C vs unsave rust\n\nAnchor is a great tool for writing programs, but it comes with a cost. Every check that anchor does costs CU. While most checks are useful, there may be room for improvement. The anchor generated code is not necessarily optimized for CU.\n\n| Test Title   | Anchor                                                     | Native                                     | ASM \\*1                                              | C                                   |\n| ------------ | ---------------------------------------------------------- | ------------------------------------------ | ---------------------------------------------------- | ----------------------------------- |\n| Deploy size  | 265677 bytes (1.8500028 sol)                               | 48573 bytes (0.33895896 sol)               | 1389 bytes (0.01055832 sol)                          | 1333 Bytes (0.01016856 sol)         |\n| Counter Inc  | [946 CU](counterAnchor/anchor/programs/counter/src/lib.rs) | [843 CU](counterNative/program/src/lib.rs) | [4 CU](counterAsmSbpf/src/sbpfCounter/sbpfCounter.s) | [5 CU](counterC/counter/src/main.c) |\n| Signer Check | 303 CU                                                     | 103 CU                                     | 1 CU                                                 | -                                   |\n\n| Test Title   | Unsave Rust \\*2                     |\n| ------------ | ----------------------------------- |\n| Deploy size  | 973 bytes (0.00766296 sol)          |\n| Counter Inc  | [5 CU](counterC/counter/src/lib.rs) |\n| Signer Check | -                                   |\n\n*1 Note though that the assembly example is a very simple example and does not include any checks or error handling.\n*2 Can be decreased to 4 CU using [sbf-asm-macros](https://crates.io/crates/sbpf-asm-macros) to remove the return type since it is not needed in success case.\n\nSize will increase with every check that you implement and every instruction that you add. You will also need to increase your program size whenever it becomes bigger.\n\nNote please that all these tests are tests are super specific for a counter and not really realistic for a production program. Theses tests are only to show the differences between the different program types and ways to think about optimizing your program.\n\n## 10 Writing programs in Assembly\n\nTo get started you can watch this [video guide](https://www.youtube.com/watch?v=eacDC0VgyxI\u0026t=1273s\u0026ab_channel=SolPlay) on how to write programs in assembly.\n\nWriting programs in assembly can be very efficient. You can write very small programs that use very little CU. This can be great for example to write primitives that are used in other programs like checking token balances at the end of a swap or similar.\n\nThe downside is that you need to write everything yourself and you need to be very careful with the stack and heap. You also need to be very careful with the memory layout and the program will be harder to read and maintain. Also Anchor and even native rust are way easier to write secure programs with! Only use this if you know what you are doing.\n\nThere are two counter example in this repository. One was written using the [solana-program-rosetta](https://github.com/joncinque/solana-program-rosetta?tab=readme-ov-file#assembly) by Jon Cinque. It is a great tool to get started with writing programs in assembly. It comes complete with bankrun tests written in rust. It also contains examples written in Zig and C which can also bring great performance improvements.\n\nInstead of solana-program-rosetta you can also use [SBPF](https://github.com/deanmlittle/sbpf) by [Dean](https://github.com/deanmlittle) which gives your very convenient functions like `sbpf init`, `sbpf build` and `sbpf deploy`. This one comes with Js tests using `mocha` and `chai` and is also a great tool to get started with writing programs in assembly and makes setting up projects much easier.\n\nIf you want to get started on Solana ASM program writing you should start by reading the [docs on the exact memory layout](https://solana.com/docs/programs/faq#input-parameter-serialization) of the [entry point](https://github.com/anza-xyz/agave/blob/1b3eb3df5e244cdbdedb7eff8978823ef0611669/sdk/program/src/entrypoint.rs#L336) and the registers for heap and stack frame.\n\nGreat [repository links and tips for ASM programs](https://github.com/deanmlittle/awesome-sbpf).\n\nThere is also a VSCode extension by Dean: https://github.com/deanmlittle/vscode-sbpf-asm that helps with autocomplete and syntax highlighting.\n\nIt is probably not realistic to write huge programs in assembly, but for small programs or primitives it can be a useful tool. Also knowing how Rust and C code transforms to assembly code can be useful when optimizing your programs.\n\nHere are some ASM examples for reference:\n\n- [Fibonacci example](https://github.com/deanmlittle/solana-fibonacci-asm)\n- [Sol Transfer](https://github.com/joncinque/solana-program-rosetta/tree/main/transfer-lamports/asm)\n- Hello world: https://github.com/deanmlittle/ezbpf run `sbpf init`\n- The Counter examples can be found here in this repo\n\nIf you want to use AI like chat GPT to write your programs make sure to train it on these examples before you start.\n\n## 11 Other low level optimizations\n\n### Compiler flags\n\nThere is certain compiler flags that you set set to decrease CU usage of yor program. You can set the following flags in your `Cargo.toml`. For example you could disable overflow checks.\nNote thought that changing a flag like overflow checks of course comes with additional risks like overflow bugs.\n\nTODO: Add performance tests on different flags.\nhttps://doc.rust-lang.org/cargo/reference/profiles.html#overflow-checks\n\n### Inline\n\nInlining functions can save CU. The compiler is very good at optimizing the code and inline checks and remove unnecessary calculations if a value is not used for example.\n\n```rust\n#[inline(always)]\nfn add(a: u64, b: u64) -\u003e u64 {\n    a + b\n}\n```\n\nNote though that you need to balance inline always vs inline never. Inlining saves CU but needs more stack space while inline never saves stack space but costs more CU.\n\n### Non standart heap allocators\n\nThe standard heap allocator is a bump heap allocator which does not free memory. This can lead to out of memory errors if you use a lot of memory. You can use a different heap allocator.\n\nMetaplex token meta data program uses smalloc heap for example: https://github.com/metaplex-foundation/mpl-core-candy-machine/pull/10\n\n### Different entry points\n\nThe standard entry points is not necessarily the most efficient one. You can use a different entry point to save CU. For example the no_std entry point:\nhttps://github.com/cavemanloverboy/solana-nostd-entrypoint\nIt uses unsafe rust though.\nYou can read on some comparison of different entry points here: https://github.com/hetdagli234/optimising-solana-programs/tree/main\nand here: https://github.com/febo/eisodos\n\n## 12 Analyze and optimize yourself\n\nMost important here is actually to know that every check and every serialization costs compute and how to profile and optimize it yourself since every program is different. Profile and optimize your programs today!\n\nFeel free to play around with it and add more examples. Also here is a very nice article from @RareSkills_io https://www.rareskills.io/post/solana-compute-unit-price on that topic. I replicated some of the examples :)\n\nAlso here is a nice Twitter thread on the topic: https://x.com/daglihet/status/1840396773833261085 with another CU optimization repository looking also at different entry points and how to optimize them. https://github.com/hetdagli234/optimising-solana-programs/tree/main\n\nSome nice optimizations can also he found in this [twitter post by dev4all](https://twitter.com/kAsky53/status/1777799557759254810).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsolana-developers%2Fcu_optimizations","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsolana-developers%2Fcu_optimizations","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsolana-developers%2Fcu_optimizations/lists"}