#![feature(test)] #![cfg(feature = "sbf_c")] #![allow(clippy::uninlined_format_args)] #![allow(clippy::arithmetic_side_effects)] 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_program_runtime_environment_v1, }, 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 program_runtime_environment = create_program_runtime_environment_v1( &FeatureSet::default(), &ComputeBudget::default(), true, false, ); let program_runtime_environment = Arc::new(program_runtime_environment.unwrap()); bencher.iter(|| { let _ = Executable::::from_elf( &elf, program_runtime_environment.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::(ARMSTRONG_LIMIT) .unwrap(); inner_iter.write_u64::(0).unwrap(); let elf = load_program_from_file("bench_alu"); with_mock_invoke_context!(invoke_context, bpf_loader::id(), 10000001); let program_runtime_environment = create_program_runtime_environment_v1( &invoke_context.feature_set, &ComputeBudget::default(), true, false, ); let executable = Executable::::from_elf( &elf, Arc::new(program_runtime_environment.unwrap()), ) .unwrap(); let mut verified_executable = Executable::::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.context_object_pointer .mock_set_remaining(std::i64::MAX as u64); let (instructions, result) = vm.execute_program(&verified_executable, 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::()..]) ); bencher.iter(|| { vm.context_object_pointer .mock_set_remaining(std::i64::MAX as u64); vm.execute_program(&verified_executable, 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(&verified_executable, false).1.unwrap() ); assert_eq!(ARMSTRONG_LIMIT, LittleEndian::read_u64(&inner_iter)); assert_eq!( ARMSTRONG_EXPECTED, LittleEndian::read_u64(&inner_iter[mem::size_of::()..]) ); bencher.iter(|| { vm.context_object_pointer .mock_set_remaining(std::i64::MAX as u64); vm.execute_program(&verified_executable, 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.clone()); 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 program_runtime_environment = create_program_runtime_environment_v1( &invoke_context.feature_set, &ComputeBudget::default(), true, false, ); let executable = Executable::::from_elf( &elf, Arc::new(program_runtime_environment.unwrap()), ) .unwrap(); let verified_executable = Executable::::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 program_runtime_environment = create_program_runtime_environment_v1( &invoke_context.feature_set, &ComputeBudget::default(), true, false, ); let executable = Executable::::from_elf( &elf, Arc::new(program_runtime_environment.unwrap()), ) .unwrap(); let verified_executable = Executable::::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(&verified_executable, true); measure.stop(); assert_eq!( 0, vm.context_object_pointer.get_remaining(), "Tuner must consume the whole budget" ); println!( "{:?} compute units took {:?} us ({:?} instructions)", BUDGET - vm.context_object_pointer.get_remaining(), measure.as_us(), instructions, ); } fn clone_regions(regions: &[MemoryRegion]) -> Vec { 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() } }