Instructions par lots
Les invocations inter-programmes (CPI) entraînent un coût de base de 1 000 unités de calcul par appel. Pour les programmes qui reçoivent fréquemment des CPI au sein de la même instruction, cette surcharge devient un goulot d'étranglement significatif en termes de performance.
Pour résoudre cette inefficacité, Dean a créé l'instruction "batch" dans cette PR pour p-token, permettant plusieurs opérations en un seul appel CPI.
L'instruction par lots
Une instruction par lots permet de traiter plusieurs opérations en un seul CPI au lieu de nécessiter des appels séparés pour chaque opération. Cela réduit considérablement la consommation d'unités de calcul pour les programmes gérant plusieurs opérations liées.
Structure
L'instruction par lots utilise une structure d'en-tête améliorée contenant :
Nombre de comptes : Nombre de comptes requis pour l'instruction interne
Longueur des données : Taille des données d'instruction
Cet en-tête permet un traitement efficace de plusieurs instructions "internes" au sein d'un seul lot. Le système parcourt et traite chaque instruction interne séquentiellement.
Conception du point d'entrée
Le point d'entrée vérifie d'abord le discriminateur d'instruction pour déterminer s'il faut traiter un lot ou une instruction régulière. Cela empêche d'imbriquer des instructions par lots dans d'autres instructions par lots, ce qui serait incorrect.
Comme ceci :
#[inline(always)]
pub fn process_instruction(
_program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
let [discriminator, remaining @ ..] = instruction_data else {
return Err(TokenError::InvalidInstruction.into());
};
let result = if *discriminator == 255 {
// 255 - Batch
#[cfg(feature = "logging")]
pinocchio::msg!("Instruction: Batch");
process_batch(accounts, remaining)
} else {
inner_process_instruction(accounts, instruction_data)
};
result.inspect_err(log_error)
}Traitement par lots
La fonction process_batch gère la logique principale de traitement par lots :
/// The size of the batch instruction header.
///
/// The header of each instruction consists of two `u8` values:
/// * number of the accounts
/// * length of the instruction data
const IX_HEADER_SIZE: usize = 2;
pub fn process_batch(mut accounts: &[AccountInfo], mut instruction_data: &[u8]) -> ProgramResult {
loop {
// Validates the instruction data and accounts offset.
if instruction_data.len() < IX_HEADER_SIZE {
// The instruction data must have at least two bytes.
return Err(TokenError::InvalidInstruction.into());
}
// SAFETY: The instruction data is guaranteed to have at least two bytes
// (header) + one byte (discriminator) and the values are within the bounds
// of an `usize`.
let expected_accounts = unsafe { *instruction_data.get_unchecked(0) as usize };
let data_offset = IX_HEADER_SIZE + unsafe { *instruction_data.get_unchecked(1) as usize };
if instruction_data.len() < data_offset || data_offset == IX_HEADER_SIZE {
return Err(TokenError::InvalidInstruction.into());
}
if accounts.len() < expected_accounts {
return Err(ProgramError::NotEnoughAccountKeys);
}
// Process the instruction.
// SAFETY: The instruction data and accounts lengths are already validated so
// all slices are guaranteed to be valid.
let (ix_accounts, ix_data) = unsafe {
(
accounts.get_unchecked(..expected_accounts),
instruction_data.get_unchecked(IX_HEADER_SIZE..data_offset),
)
};
inner_process_instruction(ix_accounts, ix_data)?;
if data_offset == instruction_data.len() {
// The batch is complete.
break;
}
accounts = &accounts[expected_accounts..];
instruction_data = &instruction_data[data_offset..];
}
Ok(())
}Voici ce qui se passe dans cette fonction :
Validation de l'en-tête : La fonction commence par valider que les données d'instruction contiennent au moins la taille d'en-tête requise (2 octets).
Extraction des comptes et des données : Elle extrait le nombre de comptes attendu et calcule le décalage des données, en validant ces valeurs pour éviter un comportement indéfini.
Traitement des instructions : Chaque instruction interne est traitée à l'aide de la fonction standard
inner_process_instructionavec ses comptes et données spécifiques.Contrôle de boucle : La fonction continue le traitement jusqu'à ce que toutes les instructions par lots soient terminées, en avançant les pointeurs de compte et de données d'instruction pour chaque itération.