Create your own
Lesson illustration

Measuring Compute Unit Consumption

Hello! Welcome to the next lesson in our module on Auditing and Optimizing Solana Programs.

In the previous lesson, we focused on building secure programs by leveraging Anchor's powerful declarative constraints like init, has_one, and close. We learned how to proactively prevent common vulnerabilities such as re-initialization and type confusion attacks right at the boundary of our instructions.

Today, we shift our focus from security to performance. A program that is perfectly secure but too slow or expensive to execute will struggle to find users. The key to performance on Solana is managing Compute Units (CUs). Your learning outcome for this lesson is to measure the compute unit consumption of a program's instructions. We'll explore what CUs are, why they're critical, and two practical methods for measuring them: a high-level approach for entire instructions and a granular technique for pinpointing costly code blocks.

1. What are Compute Units and Why Do They Matter?

On Solana, every operation a program performs—from simple arithmetic to complex cryptographic calculations—consumes Compute Units. Think of it as a "computational budget" for your transaction.

To get a foundational understanding of CUs and their role, let's start with a short reading.

Introduction to Solana Compute Units and Transaction Fees

The article 'Introduction to Solana Compute Units and Transaction Fees' from RareSkills provides an excellent overview. It explains what CUs are, contrasts them with Ethereum's gas model, and clarifies their relationship with transaction fees.

Please read the following sections: The introduction (first three paragraphs). The section titled 'Compute unit optimization'. The section beginning with 'Back to compute unit. So, why would we want to optimize compute units...'. Focus on understanding the difference between CUs and fees, the default transaction CU limit, and the three key reasons for optimizing CU usage.

As the article highlighted, there are three critical reasons to care about your program's CU consumption:

  1. Future Fee Markets: While base fees are currently tied to signatures, Solana's fee market is evolving. Priority fees are based on CUs, and future changes will likely make CU consumption a more direct factor in transaction costs.
  2. Block Inclusion: During periods of high network traffic, validators prioritize transactions that are more profitable to include in a block. Lower CU transactions (especially when paired with a priority fee) are more likely to be processed quickly.
  3. Composability: When another program calls yours via a Cross-Program Invocation (CPI), they share a single transaction's CU budget. A "gas-guzzling" program is a poor neighbor and less likely to be integrated by other developers, limiting its utility.

2. Measuring Total Instruction CU Consumption

The simplest way to measure CU consumption is to look at the total CUs used by an entire instruction. You can do this by running a test and observing the transaction logs from the local validator.

The same RareSkills article provides a perfect hands-on walkthrough of this process. It shows how to build a minimal Anchor program, write a test for it, and find the CU consumption in the log output.

Introduction to Solana Compute Units and Transaction Fees

Let's continue with the RareSkills article to see a practical demonstration of measuring CUs.

Please read the sections starting from the first Rust code block (use anchor_lang::prelude::*;) down to just before the heading 'Smaller integers save compute units'. Follow the example to see: How the solana logs command displays the CU consumption for an instruction. How adding simple logic (pushing to a Vec) increases the CUs consumed, while the base fee remains unchanged.

This method gives you a high-level view: "My initialize instruction cost 593 CUs." It's excellent for baseline measurements and for comparing the overall cost of different instructions. However, when an instruction becomes complex, you need a more precise tool to find out where within that instruction the CUs are being spent.

3. Granular Measurement with the compute_fn! Macro

For fine-grained analysis, the Solana program library provides a handy macro called compute_fn!. You can wrap any block of code with this macro to log the CU usage of just that specific part. This is the developer's equivalent of a profiler, allowing you to pinpoint performance bottlenecks inside your functions.

Let's explore how to use it.

How to Optimize Compute Usage on Solana

The official Solana developer guide, 'How to Optimize Compute Usage on Solana', explains the compute_fn! macro and its usage.

Please read the section 'How to Measure Compute Usage'. Focus on the syntax of the compute_fn! macro. Note that the article links to a GitHub repository with a full working example, which is a great resource for further exploration.

The syntax is straightforward:

use solana_program::log::sol_log_compute_units;

// Or if you want the macro version
// use solana_program::compute_fn;

// ... inside your instruction function ...

sol_log_compute_units(); // Log CUs before the operation

// The code you want to measure
let mut a: Vec<u64> = Vec::new();
a.push(1);

sol_log_compute_units(); // Log CUs after the operation

The compute_fn! macro from the article is a convenient wrapper around this sol_log_compute_units() function. When you run your tests with solana logs active, you'll see output that looks something like this:

Solana Program Compute Units Benchmark Output
This is an example of the console output generated when using compute measurement tools. It clearly labels different benchmarks and shows the CU consumed by each, allowing for precise performance analysis.

With this tool, you can start to answer questions like, "Is my deserialization logic more expensive than my PDA validation?" or "How much does this loop really cost?"

Test your understanding!

You have an Anchor instruction that performs three main tasks:

  1. It deserializes user input.
  2. It runs a complex calculation in a for loop.
  3. It serializes the result back into an account.

You suspect the for loop is the most CU-intensive part. How would you use the methods from this lesson to confirm your suspicion and quantify the cost?

Show answer

You would use a two-step approach:

  1. Get the total cost: Run your anchor test and watch the solana logs to find the total CU consumed by the entire instruction. This gives you your baseline.
  2. Isolate the loop: In your instruction's Rust code, wrap the for loop with the compute_fn! macro (or sol_log_compute_units() calls before and after the loop).

By comparing the CUs consumed by the loop (from compute_fn!) to the total instruction CUs (from the logs), you can calculate the exact percentage of the budget that the loop is using. This confirms whether it's the bottleneck and is the first step toward optimizing it.

What to Measure: A Preview of Optimization

Now that you know how to measure, it's worth seeing a few examples of what you'll be looking for. The next lesson is dedicated to optimization, but seeing the impact of different code patterns will solidify why measurement is so vital.

The code we write can have drastically different CU costs based on small choices.

  • Logging: Simple msg! macros can be surprisingly expensive, especially with operations like Base58 encoding.
  • Data Types: Using a u64 where a u8 would suffice costs more compute.
  • Serialization: The way you serialize and deserialize account data has a huge impact. Zero-copy deserialization is far more efficient than Borsh.
Compute Unit Comparison: p-token vs. spl-token Instructions
This table shows a real-world comparison of CU costs between two different token program implementations. Optimizations in the 'p-token' standard result in dramatically lower CU usage for common operations, highlighting the significant impact of efficient code.

This image demonstrates that the architectural and implementation choices can lead to order-of-magnitude differences in CU consumption. Being able to measure this is the first and most crucial step in building efficient programs.

Conclusion

In this lesson, we explored the "what, why, and how" of measuring Compute Units on Solana. This skill is the foundation of performance optimization, enabling you to build programs that are not only correct and secure but also efficient and cost-effective.

Key Takeaways:

  • Compute Units (CUs) are the measure of computational work for a Solana transaction. Managing them is key for performance, cost, and composability.
  • You can measure the total CU cost of an instruction by running anchor test and observing the output of solana logs.
  • You can perform granular measurement of specific code blocks using the compute_fn! macro or sol_log_compute_units() function to pinpoint performance bottlenecks.
  • Code patterns related to data types, logging, and serialization have a significant impact on CU consumption.

Preview of the Next Lesson

You now have the tools to measure performance. In our next lesson, we will put them to use. We will identify and refactor high-cost operations, diving deeper into the common culprits like inefficient deserialization and excessive logging, and learn specific techniques to optimize them.

Can't find a good explanation? Sign up and we'll make it for you

Sign up