466 lines
16 KiB
Rust
466 lines
16 KiB
Rust
#![allow(clippy::integer_arithmetic)]
|
|
#![feature(test)]
|
|
|
|
extern crate test;
|
|
|
|
use {
|
|
crossbeam_channel::{unbounded, Receiver},
|
|
log::*,
|
|
rand::{thread_rng, Rng},
|
|
rayon::prelude::*,
|
|
solana_client::connection_cache::ConnectionCache,
|
|
solana_core::{
|
|
banking_stage::{
|
|
committer::Committer,
|
|
consumer::Consumer,
|
|
leader_slot_metrics::LeaderSlotMetricsTracker,
|
|
qos_service::QosService,
|
|
unprocessed_packet_batches::*,
|
|
unprocessed_transaction_storage::{ThreadType, UnprocessedTransactionStorage},
|
|
BankingStage, BankingStageStats,
|
|
},
|
|
banking_trace::{BankingPacketBatch, BankingTracer},
|
|
},
|
|
solana_entry::entry::{next_hash, Entry},
|
|
solana_gossip::cluster_info::{ClusterInfo, Node},
|
|
solana_ledger::{
|
|
blockstore::Blockstore,
|
|
blockstore_processor::process_entries_for_tests,
|
|
genesis_utils::{create_genesis_config, GenesisConfigInfo},
|
|
get_tmp_ledger_path,
|
|
},
|
|
solana_perf::{
|
|
packet::{to_packet_batches, Packet},
|
|
test_tx::test_tx,
|
|
},
|
|
solana_poh::poh_recorder::{create_test_recorder, WorkingBankEntry},
|
|
solana_runtime::{
|
|
bank::Bank, bank_forks::BankForks, prioritization_fee_cache::PrioritizationFeeCache,
|
|
},
|
|
solana_sdk::{
|
|
genesis_config::GenesisConfig,
|
|
hash::Hash,
|
|
message::Message,
|
|
pubkey,
|
|
signature::{Keypair, Signature, Signer},
|
|
system_instruction, system_transaction,
|
|
timing::{duration_as_us, timestamp},
|
|
transaction::{Transaction, VersionedTransaction},
|
|
},
|
|
solana_streamer::socket::SocketAddrSpace,
|
|
solana_vote_program::{
|
|
vote_state::VoteStateUpdate, vote_transaction::new_vote_state_update_transaction,
|
|
},
|
|
std::{
|
|
iter::repeat_with,
|
|
sync::{atomic::Ordering, Arc, RwLock},
|
|
time::{Duration, Instant},
|
|
},
|
|
test::Bencher,
|
|
};
|
|
|
|
fn check_txs(receiver: &Arc<Receiver<WorkingBankEntry>>, ref_tx_count: usize) {
|
|
let mut total = 0;
|
|
let now = Instant::now();
|
|
loop {
|
|
if let Ok((_bank, (entry, _tick_height))) = receiver.recv_timeout(Duration::new(1, 0)) {
|
|
total += entry.transactions.len();
|
|
}
|
|
if total >= ref_tx_count {
|
|
break;
|
|
}
|
|
if now.elapsed().as_secs() > 60 {
|
|
break;
|
|
}
|
|
}
|
|
assert_eq!(total, ref_tx_count);
|
|
}
|
|
|
|
#[bench]
|
|
fn bench_consume_buffered(bencher: &mut Bencher) {
|
|
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(100_000);
|
|
let bank = Arc::new(Bank::new_for_benches(&genesis_config));
|
|
let ledger_path = get_tmp_ledger_path!();
|
|
{
|
|
let blockstore = Arc::new(
|
|
Blockstore::open(&ledger_path).expect("Expected to be able to open database ledger"),
|
|
);
|
|
let (exit, poh_recorder, poh_service, _signal_receiver) =
|
|
create_test_recorder(&bank, blockstore, None, None);
|
|
|
|
let recorder = poh_recorder.read().unwrap().new_recorder();
|
|
let bank_start = poh_recorder.read().unwrap().bank_start().unwrap();
|
|
|
|
let tx = test_tx();
|
|
let transactions = vec![tx; 4194304];
|
|
let batches = transactions
|
|
.iter()
|
|
.filter_map(|transaction| {
|
|
let packet = Packet::from_data(None, transaction).ok().unwrap();
|
|
DeserializedPacket::new(packet).ok()
|
|
})
|
|
.collect::<Vec<_>>();
|
|
let batches_len = batches.len();
|
|
let mut transaction_buffer = UnprocessedTransactionStorage::new_transaction_storage(
|
|
UnprocessedPacketBatches::from_iter(batches.into_iter(), 2 * batches_len),
|
|
ThreadType::Transactions,
|
|
);
|
|
let (s, _r) = unbounded();
|
|
let committer = Committer::new(None, s, Arc::new(PrioritizationFeeCache::new(0u64)));
|
|
let consumer = Consumer::new(committer, recorder, QosService::new(1), None);
|
|
// This tests the performance of buffering packets.
|
|
// If the packet buffers are copied, performance will be poor.
|
|
bencher.iter(move || {
|
|
consumer.consume_buffered_packets(
|
|
&bank_start,
|
|
&mut transaction_buffer,
|
|
&BankingStageStats::default(),
|
|
&mut LeaderSlotMetricsTracker::new(0),
|
|
);
|
|
});
|
|
|
|
exit.store(true, Ordering::Relaxed);
|
|
poh_service.join().unwrap();
|
|
}
|
|
let _unused = Blockstore::destroy(&ledger_path);
|
|
}
|
|
|
|
fn make_accounts_txs(txes: usize, mint_keypair: &Keypair, hash: Hash) -> Vec<Transaction> {
|
|
let to_pubkey = pubkey::new_rand();
|
|
let dummy = system_transaction::transfer(mint_keypair, &to_pubkey, 1, hash);
|
|
(0..txes)
|
|
.into_par_iter()
|
|
.map(|_| {
|
|
let mut new = dummy.clone();
|
|
let sig: Vec<_> = (0..64).map(|_| thread_rng().gen::<u8>()).collect();
|
|
new.message.account_keys[0] = pubkey::new_rand();
|
|
new.message.account_keys[1] = pubkey::new_rand();
|
|
new.signatures = vec![Signature::new(&sig[0..64])];
|
|
new
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
fn make_programs_txs(txes: usize, hash: Hash) -> Vec<Transaction> {
|
|
let progs = 4;
|
|
(0..txes)
|
|
.map(|_| {
|
|
let from_key = Keypair::new();
|
|
let instructions: Vec<_> = repeat_with(|| {
|
|
let to_key = pubkey::new_rand();
|
|
system_instruction::transfer(&from_key.pubkey(), &to_key, 1)
|
|
})
|
|
.take(progs)
|
|
.collect();
|
|
let message = Message::new(&instructions, Some(&from_key.pubkey()));
|
|
Transaction::new(&[&from_key], message, hash)
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
fn make_vote_txs(txes: usize) -> Vec<Transaction> {
|
|
// 1000 voters
|
|
let num_voters = 1000;
|
|
let (keypairs, vote_keypairs): (Vec<_>, Vec<_>) = (0..num_voters)
|
|
.map(|_| (Keypair::new(), Keypair::new()))
|
|
.unzip();
|
|
(0..txes)
|
|
.map(|i| {
|
|
// Quarter of the votes should be filtered out
|
|
let vote = if i % 4 == 0 {
|
|
VoteStateUpdate::from(vec![(2, 1)])
|
|
} else {
|
|
VoteStateUpdate::from(vec![(i as u64, 1)])
|
|
};
|
|
new_vote_state_update_transaction(
|
|
vote,
|
|
Hash::new_unique(),
|
|
&keypairs[i % num_voters],
|
|
&vote_keypairs[i % num_voters],
|
|
&vote_keypairs[i % num_voters],
|
|
None,
|
|
)
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
enum TransactionType {
|
|
Accounts,
|
|
Programs,
|
|
AccountsAndVotes,
|
|
ProgramsAndVotes,
|
|
}
|
|
|
|
fn bench_banking(bencher: &mut Bencher, tx_type: TransactionType) {
|
|
solana_logger::setup();
|
|
let num_threads = BankingStage::num_threads() as usize;
|
|
// a multiple of packet chunk duplicates to avoid races
|
|
const CHUNKS: usize = 8;
|
|
const PACKETS_PER_BATCH: usize = 192;
|
|
let txes = PACKETS_PER_BATCH * num_threads * CHUNKS;
|
|
let mint_total = 1_000_000_000_000;
|
|
let GenesisConfigInfo {
|
|
mut genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(mint_total);
|
|
|
|
// Set a high ticks_per_slot so we don't run out of ticks
|
|
// during the benchmark
|
|
genesis_config.ticks_per_slot = 10_000;
|
|
|
|
let banking_tracer = BankingTracer::new_disabled();
|
|
let (non_vote_sender, non_vote_receiver) = banking_tracer.create_channel_non_vote();
|
|
let (tpu_vote_sender, tpu_vote_receiver) = banking_tracer.create_channel_tpu_vote();
|
|
let (gossip_vote_sender, gossip_vote_receiver) = banking_tracer.create_channel_gossip_vote();
|
|
|
|
let mut bank = Bank::new_for_benches(&genesis_config);
|
|
// Allow arbitrary transaction processing time for the purposes of this bench
|
|
bank.ns_per_slot = u128::MAX;
|
|
let bank_forks = Arc::new(RwLock::new(BankForks::new(bank)));
|
|
let bank = bank_forks.read().unwrap().get(0).unwrap();
|
|
|
|
// set cost tracker limits to MAX so it will not filter out TXs
|
|
bank.write_cost_tracker()
|
|
.unwrap()
|
|
.set_limits(std::u64::MAX, std::u64::MAX, std::u64::MAX);
|
|
|
|
debug!("threads: {} txs: {}", num_threads, txes);
|
|
|
|
let transactions = match tx_type {
|
|
TransactionType::Accounts | TransactionType::AccountsAndVotes => {
|
|
make_accounts_txs(txes, &mint_keypair, genesis_config.hash())
|
|
}
|
|
TransactionType::Programs | TransactionType::ProgramsAndVotes => {
|
|
make_programs_txs(txes, genesis_config.hash())
|
|
}
|
|
};
|
|
let vote_txs = match tx_type {
|
|
TransactionType::AccountsAndVotes | TransactionType::ProgramsAndVotes => {
|
|
Some(make_vote_txs(txes))
|
|
}
|
|
_ => None,
|
|
};
|
|
|
|
// fund all the accounts
|
|
transactions.iter().for_each(|tx| {
|
|
let fund = system_transaction::transfer(
|
|
&mint_keypair,
|
|
&tx.message.account_keys[0],
|
|
mint_total / txes as u64,
|
|
genesis_config.hash(),
|
|
);
|
|
let x = bank.process_transaction(&fund);
|
|
x.unwrap();
|
|
});
|
|
//sanity check, make sure all the transactions can execute sequentially
|
|
transactions.iter().for_each(|tx| {
|
|
let res = bank.process_transaction(tx);
|
|
assert!(res.is_ok(), "sanity test transactions");
|
|
});
|
|
bank.clear_signatures();
|
|
//sanity check, make sure all the transactions can execute in parallel
|
|
let res = bank.process_transactions(transactions.iter());
|
|
for r in res {
|
|
assert!(r.is_ok(), "sanity parallel execution");
|
|
}
|
|
bank.clear_signatures();
|
|
let verified: Vec<_> = to_packet_batches(&transactions, PACKETS_PER_BATCH);
|
|
let vote_packets = vote_txs.map(|vote_txs| {
|
|
let mut packet_batches = to_packet_batches(&vote_txs, PACKETS_PER_BATCH);
|
|
for batch in packet_batches.iter_mut() {
|
|
for packet in batch.iter_mut() {
|
|
packet.meta_mut().set_simple_vote(true);
|
|
}
|
|
}
|
|
packet_batches
|
|
});
|
|
|
|
let ledger_path = get_tmp_ledger_path!();
|
|
{
|
|
let blockstore = Arc::new(
|
|
Blockstore::open(&ledger_path).expect("Expected to be able to open database ledger"),
|
|
);
|
|
let (exit, poh_recorder, poh_service, signal_receiver) =
|
|
create_test_recorder(&bank, blockstore, None, None);
|
|
let cluster_info = {
|
|
let keypair = Arc::new(Keypair::new());
|
|
let node = Node::new_localhost_with_pubkey(&keypair.pubkey());
|
|
ClusterInfo::new(node.info, keypair, SocketAddrSpace::Unspecified)
|
|
};
|
|
let cluster_info = Arc::new(cluster_info);
|
|
let (s, _r) = unbounded();
|
|
let _banking_stage = BankingStage::new(
|
|
&cluster_info,
|
|
&poh_recorder,
|
|
non_vote_receiver,
|
|
tpu_vote_receiver,
|
|
gossip_vote_receiver,
|
|
None,
|
|
s,
|
|
None,
|
|
Arc::new(ConnectionCache::new("connection_cache_test")),
|
|
bank_forks,
|
|
&Arc::new(PrioritizationFeeCache::new(0u64)),
|
|
);
|
|
|
|
let chunk_len = verified.len() / CHUNKS;
|
|
let mut start = 0;
|
|
|
|
// This is so that the signal_receiver does not go out of scope after the closure.
|
|
// If it is dropped before poh_service, then poh_service will error when
|
|
// calling send() on the channel.
|
|
let signal_receiver = Arc::new(signal_receiver);
|
|
let signal_receiver2 = signal_receiver;
|
|
bencher.iter(move || {
|
|
let now = Instant::now();
|
|
let mut sent = 0;
|
|
if let Some(vote_packets) = &vote_packets {
|
|
tpu_vote_sender
|
|
.send(BankingPacketBatch::new((
|
|
vote_packets[start..start + chunk_len].to_vec(),
|
|
None,
|
|
)))
|
|
.unwrap();
|
|
gossip_vote_sender
|
|
.send(BankingPacketBatch::new((
|
|
vote_packets[start..start + chunk_len].to_vec(),
|
|
None,
|
|
)))
|
|
.unwrap();
|
|
}
|
|
for v in verified[start..start + chunk_len].chunks(chunk_len / num_threads) {
|
|
debug!(
|
|
"sending... {}..{} {} v.len: {}",
|
|
start,
|
|
start + chunk_len,
|
|
timestamp(),
|
|
v.len(),
|
|
);
|
|
for xv in v {
|
|
sent += xv.len();
|
|
}
|
|
non_vote_sender
|
|
.send(BankingPacketBatch::new((v.to_vec(), None)))
|
|
.unwrap();
|
|
}
|
|
|
|
check_txs(&signal_receiver2, txes / CHUNKS);
|
|
|
|
// This signature clear may not actually clear the signatures
|
|
// in this chunk, but since we rotate between CHUNKS then
|
|
// we should clear them by the time we come around again to re-use that chunk.
|
|
bank.clear_signatures();
|
|
trace!(
|
|
"time: {} checked: {} sent: {}",
|
|
duration_as_us(&now.elapsed()),
|
|
txes / CHUNKS,
|
|
sent,
|
|
);
|
|
start += chunk_len;
|
|
start %= verified.len();
|
|
});
|
|
exit.store(true, Ordering::Relaxed);
|
|
poh_service.join().unwrap();
|
|
}
|
|
let _unused = Blockstore::destroy(&ledger_path);
|
|
}
|
|
|
|
#[bench]
|
|
fn bench_banking_stage_multi_accounts(bencher: &mut Bencher) {
|
|
bench_banking(bencher, TransactionType::Accounts);
|
|
}
|
|
|
|
#[bench]
|
|
fn bench_banking_stage_multi_programs(bencher: &mut Bencher) {
|
|
bench_banking(bencher, TransactionType::Programs);
|
|
}
|
|
|
|
#[bench]
|
|
fn bench_banking_stage_multi_accounts_with_voting(bencher: &mut Bencher) {
|
|
bench_banking(bencher, TransactionType::AccountsAndVotes);
|
|
}
|
|
|
|
#[bench]
|
|
fn bench_banking_stage_multi_programs_with_voting(bencher: &mut Bencher) {
|
|
bench_banking(bencher, TransactionType::ProgramsAndVotes);
|
|
}
|
|
|
|
fn simulate_process_entries(
|
|
randomize_txs: bool,
|
|
mint_keypair: &Keypair,
|
|
mut tx_vector: Vec<VersionedTransaction>,
|
|
genesis_config: &GenesisConfig,
|
|
keypairs: &[Keypair],
|
|
initial_lamports: u64,
|
|
num_accounts: usize,
|
|
) {
|
|
let bank = Arc::new(Bank::new_for_benches(genesis_config));
|
|
|
|
for i in 0..(num_accounts / 2) {
|
|
bank.transfer(initial_lamports, mint_keypair, &keypairs[i * 2].pubkey())
|
|
.unwrap();
|
|
}
|
|
|
|
for i in (0..num_accounts).step_by(2) {
|
|
tx_vector.push(
|
|
system_transaction::transfer(
|
|
&keypairs[i],
|
|
&keypairs[i + 1].pubkey(),
|
|
initial_lamports,
|
|
bank.last_blockhash(),
|
|
)
|
|
.into(),
|
|
);
|
|
}
|
|
|
|
// Transfer lamports to each other
|
|
let entry = Entry {
|
|
num_hashes: 1,
|
|
hash: next_hash(&bank.last_blockhash(), 1, &tx_vector),
|
|
transactions: tx_vector,
|
|
};
|
|
process_entries_for_tests(&bank, vec![entry], randomize_txs, None, None).unwrap();
|
|
}
|
|
|
|
fn bench_process_entries(randomize_txs: bool, bencher: &mut Bencher) {
|
|
// entropy multiplier should be big enough to provide sufficient entropy
|
|
// but small enough to not take too much time while executing the test.
|
|
let entropy_multiplier: usize = 25;
|
|
let initial_lamports = 100;
|
|
|
|
// number of accounts need to be in multiple of 4 for correct
|
|
// execution of the test.
|
|
let num_accounts = entropy_multiplier * 4;
|
|
let GenesisConfigInfo {
|
|
genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config((num_accounts + 1) as u64 * initial_lamports);
|
|
|
|
let keypairs: Vec<Keypair> = repeat_with(Keypair::new).take(num_accounts).collect();
|
|
let tx_vector: Vec<VersionedTransaction> = Vec::with_capacity(num_accounts / 2);
|
|
|
|
bencher.iter(|| {
|
|
simulate_process_entries(
|
|
randomize_txs,
|
|
&mint_keypair,
|
|
tx_vector.clone(),
|
|
&genesis_config,
|
|
&keypairs,
|
|
initial_lamports,
|
|
num_accounts,
|
|
);
|
|
});
|
|
}
|
|
|
|
#[bench]
|
|
fn bench_process_entries_without_order_shuffeling(bencher: &mut Bencher) {
|
|
bench_process_entries(false, bencher);
|
|
}
|
|
|
|
#[bench]
|
|
fn bench_process_entries_with_order_shuffeling(bencher: &mut Bencher) {
|
|
bench_process_entries(true, bencher);
|
|
}
|