Logo
新用户订阅 RPC,首月享 6.5 折优惠查看优惠
OnFinality Learn
性能与优化阅读约 11 分钟

Solana 计算预算:限制、指令与测量工作流

一份关于 Solana 计算预算的实用指南:计算单元限制和价格如何运作,如何通过 ComputeBudget 指令进行设置,以及如何在调优前测量实际用量。

TL;DR

Solana 上的计算预算是指一笔交易最多可消耗的计算单元(CU)数量,以及你愿意为每个请求的 CU 支付的价格。预算通过 ComputeBudget 程序指令声明,优先费按请求的限制而非实际用量收取,因此请求过多会浪费资金,请求过少则会导致交易失败。本文解释了其机制,展示了用于设置和检查预算的可运行代码,并提供了一套可重复的测量流程,让你能根据观测数据而非猜测来确定限制大小。文章还涵盖了故障排查、权衡取舍,以及后续获取 RPC 访问和监控的指引。

计算预算控制什么

在 Solana 上,每条指令都会消耗计算单元,这是执行工作的计量单位。一笔交易的计算预算就是整笔交易可使用的 CU 上限。如果执行超过声明的限制,即使底层程序逻辑本可成功,交易也会失败。因此,预算既是安全机制,也是成本杠杆。

预算有两个独立部分。计算单元限制限制执行工作量。计算单元价格设定你为每个请求的 CU 支付多少 lamports 作为优先费。这些通过 ComputeBudget 程序配置,通常使用放在交易开头的 SetComputeUnitLimit 和 SetComputeUnitPrice 指令。将限制视为容量声明,将价格视为区块打包的竞价;它们用途不同,应分别调优。

本文聚焦于机制和可复现的测量方法。关于具体的端点和提供商选择,请使用 RPC Assistant,而不是硬编码关于使用哪个集群或提供商的假设。

  • 计算单元限制:交易可消耗的最大 CU。
  • 计算单元价格:每个请求 CU 的 lamports 数,用于优先费。
  • 优先费计算基于请求的限制,而非实际用量。
  • 限制和价格通过 ComputeBudget 程序指令设置。

假设与前提条件

示例假设有一个可通过 RPC_URL 访问的 Solana JSON-RPC 端点、一个用于发送交易的已充值密钥对,以及一个能构建和签名交易的客户端库。JavaScript 示例使用 @solana/web3.js;Python 示例使用 solana-py 和 solders。版本 API 会变化,请根据你安装的库版本核实方法名。

你还应知道目标集群。Devnet 和主网有不同的费用市场和同一程序的不同观测 CU 用量,因此请在你打算部署的集群上进行测量。如果你仍在选择端点,最佳 Solana RPC 提供商指南和 RPC 端点指南解释了应评估哪些方面。

npm install @solana/web3.js
# or
pip install solana solders

限制与价格如何相互作用

运行时按顺序执行指令,并在执行工作时扣除 CU。声明的限制会与累计用量进行核对。如果交易在限制内完成,则成功,未使用的余量不会被使用。如果超过限制,则失败。这意味着限制必须至少与交易可能走的最坏情况路径一样大,包括被调用程序内部的任何条件分支。

优先费根据请求的限制乘以请求的价格计算,而非实际消耗。请求很高限制但只使用一小部分的交易,仍按完整请求量支付优先费。因此,请求过多是直接成本,而请求过少会导致失败。实际目标是设置略高于观测到的最坏情况用量的限制,并搭配反映当前网络状况的价格。

由于费用取决于请求的限制,调优限制和调优价格在成本上相关,但在机制上独立。降低限制以减少每笔交易费用,然后调整价格以竞争打包。分别测量两者,以便正确归因变化。

在 JavaScript 中设置计算预算

以下示例构建了一笔具有明确限制和价格的交易。将 RPC_URL、PRIVATE_KEY 和指令替换为你自己的。辅助函数创建 ComputeBudget 指令并将它们前置到交易中。

先对测试交易运行此示例。目标是确认指令被接受,并在将模式应用于生产流量之前观察生成的签名和确认行为。

import {
  Connection, Keypair, Transaction, TransactionInstruction,
  ComputeBudgetProgram, sendAndConfirmTransaction, PublicKey
} from '@solana/web3.js';
import bs58 from 'bs58';

const connection = new Connection('RPC_URL', 'confirmed');
const payer = Keypair.fromSecretKey(bs58.decode('PRIVATE_KEY'));

const CU_LIMIT = 200_000;      // requested limit
const MICRO_LAMPORTS_PER_CU = 1_000; // price per CU

const ix = new TransactionInstruction({
  programId: new PublicKey('YourProgram111111111111111111111111111111'),
  keys: [],
  data: Buffer.alloc(0),
});

const tx = new Transaction().add(
  ComputeBudgetProgram.setComputeUnitLimit({ units: CU_LIMIT }),
  ComputeBudgetProgram.setComputeUnitPrice({ microLamports: MICRO_LAMPORTS_PER_CU }),
  ix
);

const sig = await sendAndConfirmTransaction(connection, tx, [payer]);
console.log('confirmed', sig);

在 Python 中设置计算预算

Python 路径使用 solders 创建 ComputeBudget 指令,使用 solana-py 作为客户端。结构与 JavaScript 示例相同:构建指令、前置、签名并发送。将限制和价格保留为命名常量,以便在测量期间在一个地方更改它们。

如果你的客户端库提供模拟方法,请在发送前使用它。模拟会报告消耗的 CU 而不提交交易,这是收集大小数据的最快方式。

from solana.rpc.api import Client
from solana.rpc.commitment import Confirmed
from solana.transaction import Transaction
from solders.keypair import Keypair
from solders.compute_budget import set_compute_unit_limit, set_compute_unit_price
from solders.instruction import Instruction
from solders.pubkey import Pubkey

client = Client('RPC_URL')
payer = Keypair.from_base58_string('PRIVATE_KEY')

CU_LIMIT = 200_000
MICRO_LAMPORTS_PER_CU = 1_000

ix = Instruction(Pubkey.from_string('YourProgram111111111111111111111111111111'), b'', [])

tx = Transaction().add(
    set_compute_unit_limit(CU_LIMIT),
    set_compute_unit_price(MICRO_LAMPORTS_PER_CU),
    ix,
)
tx.recent_blockhash = client.get_latest_blockhash(Confirmed).value.blockhash
tx.sign(payer)

resp = client.send_transaction(tx)
print(resp.value)

测量实际计算用量

不要凭直觉确定限制大小。模拟交易并读取报告的消耗单元数。模拟响应包含交易使用的 CU 数量,这是你决定限制的经验输入。在代表性输入上运行模拟,包括边缘情况和程序接受的最大负载。

一个可辩护的流程是:(1) 收集真实或接近真实的交易样本;(2) 模拟每一笔并记录消耗的 CU;(3) 计算最大值和高百分位数;(4) 将限制设置为略高于最大值并留有小安全余量;(5) 用所选限制重新模拟以确认成功。记录样本大小和集群,因为结果不能跨集群或程序版本移植。

下面的结果表是一个模板。用你自己的测量值填充它;不要将这里的任何数字视为基准。

const sim = await connection.simulateTransaction(tx, [payer]);
console.log('err', sim.value.err);
console.log('unitsConsumed', sim.value.unitsConsumed);
console.log('logs', sim.value.logs);

用于确定限制大小的结果表模板

使用如下表格记录你的测量运行。列将输入用例与观测用量和决策分开,以便审查者能复现推理过程。

用例输入大小模拟 CU观测最大值所选限制余量
小负载(记录)(记录)(记录)(记录)(记录)
典型负载(记录)(记录)(记录)(记录)(记录)
最大负载(记录)(记录)(记录)(记录)(记录)
边缘情况(记录)(记录)(记录)(记录)(记录)

余量列是所选限制与观测最大用量之间的差值。余量太小会在程序行为变化时带来失败风险;余量太大会提高每笔交易的优先费。有意识地选择余量,并在程序或其依赖变化时重新审视。

排查常见的计算预算失败

大多数计算预算问题属于几种模式。交易因超出计算预算错误而失败,这意味着限制低于实际用量。交易确认但成本高于预期,通常意味着限制远高于用量。或者尽管价格很高,交易确认缓慢,这指向费用市场状况而非限制本身。

当交易因超出预算而失败时,在没有限制覆盖的情况下模拟它以查看自然消耗,然后将限制提高到该值以上。当成本高时,将限制降低到接近观测用量并重新测量。当确认缓慢时,独立调整价格,并在样本而非单笔交易上比较确认时间。

还要检查指令顺序。ComputeBudget 指令应出现在它们所配置的指令之前。如果放在之后,配置可能不会按预期应用。

  • 超出预算:将限制提高到模拟用量以上。
  • 意外成本:将限制降低到接近观测用量。
  • 确认缓慢:将价格与限制分开调优。
  • 配置错误:验证 ComputeBudget 指令在前。

限制与权衡

计算预算调优是经验性的且特定于环境。在 devnet 上有效的限制在主网上可能是错误的,今天有效的限制在程序升级后可能失败。将测量得到的限制视为版本化配置,而非常量。在程序、其依赖或指令数据形状发生任何变化后重新测量。

还存在成本权衡。宽松的限制可防止失败,但会提高优先费,因为费用基于请求的限制。严格的限制可降低费用,但增加失败风险。正确的平衡取决于失败交易对你的应用有多大的代价,以及你的输入有多可预测。

最后,计算预算只是交易经济学的一部分。网络状况、账户争用和区块空间都会影响打包。要更广泛地了解基础设施选择,请参阅专用节点和 RPC 定价。

生产监控的后续步骤

一旦你有了测量得到的限制,就在客户端中插桩,记录每笔交易的模拟和实际用量。随时间比较两者;偏差表明程序变化或你未采样的输入情况。当实际用量接近所选限制时发出警报,因为这是重新测量的信号。

关于端点选择和可靠性规划,请查看 API 服务概览和你运营的网络。如果你正在比较用于高交易量提交的提供商,RPC Assistant 可以帮助你选择具体端点;将这里的测量方法作为你限制的真相来源。

一个实际的下一步是构建一个小脚本,模拟一批代表性交易,将结果写入表格,并打印推荐限制。按计划并在每次程序部署后运行它,以便你的计算预算与现实保持一致。

永远不用担心基础设施

OnFinality 消除了 DevOps 的繁重工作,让您能够更聪明、更快地构建。

开始