LogoAnchor 中文文档

零拷贝

学习如何在 Solana 程序中使用 Anchor 的零拷贝反序列化功能来处理大型账户数据。

使用方法

零拷贝是一种反序列化功能,允许程序直接从内存中读取账户数据而无需复制。这在处理大型账户时特别有用。

要使用零拷贝,请将 bytemuck crate 添加到你的依赖项中。添加 min_const_generics 功能以允许在你的零拷贝类型中使用任意大小的数组。

Cargo.toml
[dependencies]
bytemuck = { version = "1.20.0", features = ["min_const_generics"] }
anchor-lang = "0.30.1"

定义零拷贝账户

要定义使用零拷贝的账户类型,请使用 #[account(zero_copy)] 注解结构体。


#[account(zero_copy)]
pub struct Data {
    // 10240 字节 - 8 字节账户标识符
    pub data: [u8; 10232],
}

#[account(zero_copy)] 属性自动实现了零拷贝反序列化所需的几个特性:


#[derive(Copy, Clone)]
#[derive(bytemuck::Zeroable)]
#[derive(bytemuck::Pod)]
#[repr(C)]
struct Data {
  // --snip--
}

使用 AccountLoader 处理零拷贝账户

要反序列化一个零拷贝账户,请使用 AccountLoader<'info, T>,其中 T 是使用 #[account(zero_copy)] 属性定义的零拷贝账户类型。

例如:

#[derive(Accounts)]
pub struct InstructionAccounts<'info> {


    pub zero_copy_account: AccountLoader<'info, Data>,
}

初始化零拷贝账户

init 约束可以与 AccountLoader 类型一起使用来创建零拷贝账户。

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(


        init,
        // 10240 字节是使用 init 约束分配的最大空间
        space = 8 + 10232,
        payer = payer,
    )]
    pub data_account: AccountLoader<'info, Data>,
    #[account(mut)]
    pub payer: Signer<'info>,
    pub system_program: Program<'info, System>,
}

由于 CPI 限制,init 约束最多只能分配 10240 字节。在底层,init 约束会调用 SystemProgram 创建账户。

首次初始化零拷贝账户时,使用 load_init 获取账户数据的可变引用。load_init 方法还会设置账户标识符。

pub fn initialize(ctx: Context<Initialize>) -> Result<()> {


    let account = &mut ctx.accounts.data_account.load_init()?;
    account.data = [1; 10232];
    Ok(())
}

对于需要超过 10240 字节的账户,请使用 zero 约束而不是 initzero 约束通过检查账户标识符是否已设置来验证账户是否未初始化。

#[derive(Accounts)]
pub struct Initialize<'info> {


    #[account(zero)]
    pub data_account: AccountLoader<'info, Data>,
}

使用 zero 约束时,你需要先在单独的指令中通过直接调用 System Program 来创建账户。这允许你创建最大为 Solana 最大账户大小 10MB (10_485_760 字节) 的账户,绕过了 CPI 的限制。

和之前一样,使用 load_init 获取账户数据的可变引用并设置账户标识符。由于 8 字节保留给账户标识符,最大数据大小为 10_485_752 字节 (10MB - 8 字节)。

pub fn initialize(ctx: Context<Initialize>) -> Result<()> {


    let account = &mut ctx.accounts.data_account.load_init()?;
    account.data = [1; 10_485_752];
    Ok(())
}

更新零拷贝账户

当你需要可变访问来更新现有的零拷贝账户时,使用 load_mut:

#[derive(Accounts)]
pub struct Update<'info> {

    #[account(mut)]
    pub data_account: AccountLoader<'info, Data>,
}
pub fn update(ctx: Context<Update>) -> Result<()> {


    let account = &mut ctx.accounts.data_account.load_mut()?;
    account.data = [2; 10232];
    Ok(())
}

读取零拷贝账户

使用 load 仅读取账户数据。

#[derive(Accounts)]
pub struct ReadOnly<'info> {
    pub data_account: AccountLoader<'info, Data>,
}
pub fn read_only(ctx: Context<ReadOnly>) -> Result<()> {


    let account = &ctx.accounts.data_account.load()?;
    msg!("前 10 字节: {:?}", &account.data[..10]);
    Ok(())
}

示例

以下示例展示了在 Anchor 中初始化零拷贝账户的两种方法:

  1. 使用 init 约束在单个指令中初始化账户
  2. 使用 zero 约束初始化数据大于 10240 字节的账户

零拷贝

lib.rs
use anchor_lang::prelude::*;
 
declare_id!("8B7XpDXjPWodpDUWDSzv4q9k73jB5WdNQXZxNBj1hqw1");
 
#[program]
pub mod zero_copy {
    use super::*;
    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        let account = &mut ctx.accounts.data_account.load_init()?;
        account.data = [1; 10232];
        Ok(())
    }
 
    pub fn update(ctx: Context<Update>) -> Result<()> {
        let account = &mut ctx.accounts.data_account.load_mut()?;
        account.data = [2; 10232];
        Ok(())
    }
}
 
#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(
        init,
        // 10240 字节是使用 init 约束分配的最大空间
        space = 8 + 10232,
        payer = payer,
    )]
    pub data_account: AccountLoader<'info, Data>,
    #[account(mut)]
    pub payer: Signer<'info>,
    pub system_program: Program<'info, System>,
}
 
#[derive(Accounts)]
pub struct Update<'info> {
    #[account(mut)]
    pub data_account: AccountLoader<'info, Data>,
}
 
#[account(zero_copy)]
pub struct Data {
    // 10240 字节 - 8 字节账户标识符
    pub data: [u8; 10232],
}

初始化大型账户

当初始化一个需要超过 10,240 字节空间的账户时,你必须将初始化过程分为两步:

  1. 在单独的指令中调用 System Program 创建账户
  2. 在你的程序指令中初始化账户数据

注意,Solana 的最大账户大小为 10MB (10_485_760 字节),8 字节保留给账户标识符。

lib.rs
use anchor_lang::prelude::*;
 
declare_id!("CZgWhy3FYPFgKE5v9atSGaiQzbSB7cM38ofwX1XxeCFH");
 
#[program]
pub mod zero_copy_two {
    use super::*;
    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        let account = &mut ctx.accounts.data_account.load_init()?;
        account.data = [1; 10_485_752];
        Ok(())
    }
}
 
#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(zero)]
    pub data_account: AccountLoader<'info, Data>,
}
 
#[account(zero_copy)]
pub struct Data {
    // 10240 字节 - 8 字节账户标识符
    pub data: [u8; 10_485_752],
}

On this page

在GitHub上编辑