358 lines
11 KiB
Rust
358 lines
11 KiB
Rust
#![feature(test)]
|
|
#![cfg(feature = "sbf_c")]
|
|
#![allow(clippy::uninlined_format_args)]
|
|
#![allow(clippy::integer_arithmetic)]
|
|
|
|
use {
|
|
solana_rbpf::memory_region::MemoryState,
|
|
solana_sdk::feature_set::bpf_account_data_direct_mapping, std::slice,
|
|
};
|
|
|
|
extern crate test;
|
|
|
|
use {
|
|
byteorder::{ByteOrder, LittleEndian, WriteBytesExt},
|
|
solana_bpf_loader_program::{
|
|
create_vm, serialization::serialize_parameters, syscalls::create_loader,
|
|
},
|
|
solana_measure::measure::Measure,
|
|
solana_program_runtime::{compute_budget::ComputeBudget, invoke_context::InvokeContext},
|
|
solana_rbpf::{
|
|
ebpf::MM_INPUT_START,
|
|
elf::Executable,
|
|
memory_region::MemoryRegion,
|
|
verifier::{RequisiteVerifier, TautologyVerifier},
|
|
vm::ContextObject,
|
|
},
|
|
solana_runtime::{
|
|
bank::Bank,
|
|
bank_client::BankClient,
|
|
genesis_utils::{create_genesis_config, GenesisConfigInfo},
|
|
loader_utils::{load_program, load_program_from_file},
|
|
},
|
|
solana_sdk::{
|
|
account::AccountSharedData,
|
|
bpf_loader,
|
|
client::SyncClient,
|
|
entrypoint::SUCCESS,
|
|
feature_set::FeatureSet,
|
|
instruction::{AccountMeta, Instruction},
|
|
message::Message,
|
|
native_loader,
|
|
pubkey::Pubkey,
|
|
signature::Signer,
|
|
transaction_context::InstructionAccount,
|
|
},
|
|
std::{mem, sync::Arc},
|
|
test::Bencher,
|
|
};
|
|
|
|
const ARMSTRONG_LIMIT: u64 = 500;
|
|
const ARMSTRONG_EXPECTED: u64 = 5;
|
|
|
|
macro_rules! with_mock_invoke_context {
|
|
($invoke_context:ident, $loader_id:expr, $account_size:expr) => {
|
|
let program_key = Pubkey::new_unique();
|
|
let transaction_accounts = vec![
|
|
(
|
|
$loader_id,
|
|
AccountSharedData::new(0, 0, &native_loader::id()),
|
|
),
|
|
(program_key, AccountSharedData::new(1, 0, &$loader_id)),
|
|
(
|
|
Pubkey::new_unique(),
|
|
AccountSharedData::new(2, $account_size, &program_key),
|
|
),
|
|
];
|
|
let instruction_accounts = vec![InstructionAccount {
|
|
index_in_transaction: 2,
|
|
index_in_caller: 2,
|
|
index_in_callee: 0,
|
|
is_signer: false,
|
|
is_writable: true,
|
|
}];
|
|
solana_program_runtime::with_mock_invoke_context!(
|
|
$invoke_context,
|
|
transaction_context,
|
|
transaction_accounts
|
|
);
|
|
$invoke_context
|
|
.transaction_context
|
|
.get_next_instruction_context()
|
|
.unwrap()
|
|
.configure(&[0, 1], &instruction_accounts, &[]);
|
|
$invoke_context.push().unwrap();
|
|
};
|
|
}
|
|
|
|
#[bench]
|
|
fn bench_program_create_executable(bencher: &mut Bencher) {
|
|
let elf = load_program_from_file("bench_alu");
|
|
|
|
let loader = create_loader(
|
|
&FeatureSet::default(),
|
|
&ComputeBudget::default(),
|
|
true,
|
|
false,
|
|
)
|
|
.unwrap();
|
|
bencher.iter(|| {
|
|
let _ =
|
|
Executable::<TautologyVerifier, InvokeContext>::from_elf(&elf, loader.clone()).unwrap();
|
|
});
|
|
}
|
|
|
|
#[bench]
|
|
fn bench_program_alu(bencher: &mut Bencher) {
|
|
let ns_per_s = 1000000000;
|
|
let one_million = 1000000;
|
|
let mut inner_iter = vec![];
|
|
inner_iter
|
|
.write_u64::<LittleEndian>(ARMSTRONG_LIMIT)
|
|
.unwrap();
|
|
inner_iter.write_u64::<LittleEndian>(0).unwrap();
|
|
let elf = load_program_from_file("bench_alu");
|
|
with_mock_invoke_context!(invoke_context, bpf_loader::id(), 10000001);
|
|
|
|
let loader = create_loader(
|
|
&invoke_context.feature_set,
|
|
&ComputeBudget::default(),
|
|
true,
|
|
false,
|
|
)
|
|
.unwrap();
|
|
let executable =
|
|
Executable::<TautologyVerifier, InvokeContext>::from_elf(&elf, loader).unwrap();
|
|
|
|
let mut verified_executable =
|
|
Executable::<RequisiteVerifier, InvokeContext>::verified(executable).unwrap();
|
|
|
|
verified_executable.jit_compile().unwrap();
|
|
create_vm!(
|
|
vm,
|
|
&verified_executable,
|
|
vec![MemoryRegion::new_writable(&mut inner_iter, MM_INPUT_START)],
|
|
vec![],
|
|
&mut invoke_context,
|
|
);
|
|
let mut vm = vm.unwrap();
|
|
|
|
println!("Interpreted:");
|
|
vm.env
|
|
.context_object_pointer
|
|
.mock_set_remaining(std::i64::MAX as u64);
|
|
let (instructions, result) = vm.execute_program(true);
|
|
assert_eq!(SUCCESS, result.unwrap());
|
|
assert_eq!(ARMSTRONG_LIMIT, LittleEndian::read_u64(&inner_iter));
|
|
assert_eq!(
|
|
ARMSTRONG_EXPECTED,
|
|
LittleEndian::read_u64(&inner_iter[mem::size_of::<u64>()..])
|
|
);
|
|
|
|
bencher.iter(|| {
|
|
vm.env
|
|
.context_object_pointer
|
|
.mock_set_remaining(std::i64::MAX as u64);
|
|
vm.execute_program(true).1.unwrap();
|
|
});
|
|
let summary = bencher.bench(|_bencher| Ok(())).unwrap().unwrap();
|
|
println!(" {:?} instructions", instructions);
|
|
println!(" {:?} ns/iter median", summary.median as u64);
|
|
assert!(0f64 != summary.median);
|
|
let mips = (instructions * (ns_per_s / summary.median as u64)) / one_million;
|
|
println!(" {:?} MIPS", mips);
|
|
println!("{{ \"type\": \"bench\", \"name\": \"bench_program_alu_interpreted_mips\", \"median\": {:?}, \"deviation\": 0 }}", mips);
|
|
|
|
println!("JIT to native:");
|
|
assert_eq!(SUCCESS, vm.execute_program(false).1.unwrap());
|
|
assert_eq!(ARMSTRONG_LIMIT, LittleEndian::read_u64(&inner_iter));
|
|
assert_eq!(
|
|
ARMSTRONG_EXPECTED,
|
|
LittleEndian::read_u64(&inner_iter[mem::size_of::<u64>()..])
|
|
);
|
|
|
|
bencher.iter(|| {
|
|
vm.env
|
|
.context_object_pointer
|
|
.mock_set_remaining(std::i64::MAX as u64);
|
|
vm.execute_program(false).1.unwrap();
|
|
});
|
|
let summary = bencher.bench(|_bencher| Ok(())).unwrap().unwrap();
|
|
println!(" {:?} instructions", instructions);
|
|
println!(" {:?} ns/iter median", summary.median as u64);
|
|
assert!(0f64 != summary.median);
|
|
let mips = (instructions * (ns_per_s / summary.median as u64)) / one_million;
|
|
println!(" {:?} MIPS", mips);
|
|
println!("{{ \"type\": \"bench\", \"name\": \"bench_program_alu_jit_to_native_mips\", \"median\": {:?}, \"deviation\": 0 }}", mips);
|
|
}
|
|
|
|
#[bench]
|
|
fn bench_program_execute_noop(bencher: &mut Bencher) {
|
|
let GenesisConfigInfo {
|
|
genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(50);
|
|
let bank = Bank::new_for_benches(&genesis_config);
|
|
let bank = Arc::new(bank);
|
|
let mut bank_client = BankClient::new_shared(&bank);
|
|
|
|
let invoke_program_id = load_program(&bank_client, &bpf_loader::id(), &mint_keypair, "noop");
|
|
let bank = bank_client
|
|
.advance_slot(1, &Pubkey::default())
|
|
.expect("Failed to advance the slot");
|
|
|
|
let mint_pubkey = mint_keypair.pubkey();
|
|
let account_metas = vec![AccountMeta::new(mint_pubkey, true)];
|
|
|
|
let instruction =
|
|
Instruction::new_with_bincode(invoke_program_id, &[u8::MAX, 0, 0, 0], account_metas);
|
|
let message = Message::new(&[instruction], Some(&mint_pubkey));
|
|
|
|
bank_client
|
|
.send_and_confirm_message(&[&mint_keypair], message.clone())
|
|
.unwrap();
|
|
|
|
bencher.iter(|| {
|
|
bank.clear_signatures();
|
|
bank_client
|
|
.send_and_confirm_message(&[&mint_keypair], message.clone())
|
|
.unwrap();
|
|
});
|
|
}
|
|
|
|
#[bench]
|
|
fn bench_create_vm(bencher: &mut Bencher) {
|
|
let elf = load_program_from_file("noop");
|
|
with_mock_invoke_context!(invoke_context, bpf_loader::id(), 10000001);
|
|
const BUDGET: u64 = 200_000;
|
|
invoke_context.mock_set_remaining(BUDGET);
|
|
|
|
let direct_mapping = invoke_context
|
|
.feature_set
|
|
.is_active(&bpf_account_data_direct_mapping::id());
|
|
let loader = create_loader(
|
|
&invoke_context.feature_set,
|
|
&ComputeBudget::default(),
|
|
true,
|
|
false,
|
|
)
|
|
.unwrap();
|
|
let executable =
|
|
Executable::<TautologyVerifier, InvokeContext>::from_elf(&elf, loader).unwrap();
|
|
|
|
let verified_executable =
|
|
Executable::<RequisiteVerifier, InvokeContext>::verified(executable).unwrap();
|
|
|
|
// Serialize account data
|
|
let (_serialized, regions, account_lengths) = serialize_parameters(
|
|
invoke_context.transaction_context,
|
|
invoke_context
|
|
.transaction_context
|
|
.get_current_instruction_context()
|
|
.unwrap(),
|
|
true, // should_cap_ix_accounts
|
|
!direct_mapping, // copy_account_data
|
|
)
|
|
.unwrap();
|
|
|
|
bencher.iter(|| {
|
|
create_vm!(
|
|
vm,
|
|
&verified_executable,
|
|
clone_regions(®ions),
|
|
account_lengths.clone(),
|
|
&mut invoke_context,
|
|
);
|
|
vm.unwrap();
|
|
});
|
|
}
|
|
|
|
#[bench]
|
|
fn bench_instruction_count_tuner(_bencher: &mut Bencher) {
|
|
let elf = load_program_from_file("tuner");
|
|
with_mock_invoke_context!(invoke_context, bpf_loader::id(), 10000001);
|
|
const BUDGET: u64 = 200_000;
|
|
invoke_context.mock_set_remaining(BUDGET);
|
|
|
|
let direct_mapping = invoke_context
|
|
.feature_set
|
|
.is_active(&bpf_account_data_direct_mapping::id());
|
|
|
|
// Serialize account data
|
|
let (_serialized, regions, account_lengths) = serialize_parameters(
|
|
invoke_context.transaction_context,
|
|
invoke_context
|
|
.transaction_context
|
|
.get_current_instruction_context()
|
|
.unwrap(),
|
|
true, // should_cap_ix_accounts
|
|
!direct_mapping, // copy_account_data
|
|
)
|
|
.unwrap();
|
|
|
|
let loader = create_loader(
|
|
&invoke_context.feature_set,
|
|
&ComputeBudget::default(),
|
|
true,
|
|
false,
|
|
)
|
|
.unwrap();
|
|
let executable =
|
|
Executable::<TautologyVerifier, InvokeContext>::from_elf(&elf, loader).unwrap();
|
|
|
|
let verified_executable =
|
|
Executable::<RequisiteVerifier, InvokeContext>::verified(executable).unwrap();
|
|
|
|
create_vm!(
|
|
vm,
|
|
&verified_executable,
|
|
regions,
|
|
account_lengths,
|
|
&mut invoke_context,
|
|
);
|
|
let mut vm = vm.unwrap();
|
|
|
|
let mut measure = Measure::start("tune");
|
|
let (instructions, _result) = vm.execute_program(true);
|
|
measure.stop();
|
|
|
|
assert_eq!(
|
|
0,
|
|
vm.env.context_object_pointer.get_remaining(),
|
|
"Tuner must consume the whole budget"
|
|
);
|
|
println!(
|
|
"{:?} compute units took {:?} us ({:?} instructions)",
|
|
BUDGET - vm.env.context_object_pointer.get_remaining(),
|
|
measure.as_us(),
|
|
instructions,
|
|
);
|
|
}
|
|
|
|
fn clone_regions(regions: &[MemoryRegion]) -> Vec<MemoryRegion> {
|
|
unsafe {
|
|
regions
|
|
.iter()
|
|
.map(|region| match region.state.get() {
|
|
MemoryState::Readable => MemoryRegion::new_readonly(
|
|
slice::from_raw_parts(region.host_addr.get() as *const _, region.len as usize),
|
|
region.vm_addr,
|
|
),
|
|
MemoryState::Writable => MemoryRegion::new_writable(
|
|
slice::from_raw_parts_mut(
|
|
region.host_addr.get() as *mut _,
|
|
region.len as usize,
|
|
),
|
|
region.vm_addr,
|
|
),
|
|
MemoryState::Cow(id) => MemoryRegion::new_cow(
|
|
slice::from_raw_parts(region.host_addr.get() as *const _, region.len as usize),
|
|
region.vm_addr,
|
|
id,
|
|
),
|
|
})
|
|
.collect()
|
|
}
|
|
}
|