Skip to content

Commit

Permalink
Add conflict groups to bench-tps (solana-labs#29513)
Browse files Browse the repository at this point in the history
* Add conflict groups to bench-tps

* Add tests for KeyChunks initialization

* Add validation for num-conflict-groups

* Refactor of destination key generation

* Clarify in comments it is slice, not vector

* Use repeat_with in tests
  • Loading branch information
apfitzge authored Jan 19, 2023
1 parent d463bcc commit 9fa3cb6
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 4 deletions.
87 changes: 84 additions & 3 deletions bench-tps/src/bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,31 @@ struct KeypairChunks<'a> {
}

impl<'a> KeypairChunks<'a> {
/// Split input vector of keypairs into two sets of chunks of given size
/// Split input slice of keypairs into two sets of chunks of given size
fn new(keypairs: &'a [Keypair], chunk_size: usize) -> Self {
// Use `chunk_size` as the number of conflict groups per chunk so that each destination key is unique
Self::new_with_conflict_groups(keypairs, chunk_size, chunk_size)
}

/// Split input slice of keypairs into two sets of chunks of given size. Each chunk
/// has a set of source keys and a set of destination keys. There will be
/// `num_conflict_groups_per_chunk` unique destination keys per chunk, so that the
/// destination keys may conflict with each other.
fn new_with_conflict_groups(
keypairs: &'a [Keypair],
chunk_size: usize,
num_conflict_groups_per_chunk: usize,
) -> Self {
let mut source_keypair_chunks: Vec<Vec<&Keypair>> = Vec::new();
let mut dest_keypair_chunks: Vec<VecDeque<&Keypair>> = Vec::new();
for chunk in keypairs.chunks_exact(2 * chunk_size) {
source_keypair_chunks.push(chunk[..chunk_size].iter().collect());
dest_keypair_chunks.push(chunk[chunk_size..].iter().collect());
dest_keypair_chunks.push(
std::iter::repeat(&chunk[chunk_size..chunk_size + num_conflict_groups_per_chunk])
.flatten()
.take(chunk_size)
.collect(),
);
}
KeypairChunks {
source: source_keypair_chunks,
Expand Down Expand Up @@ -110,8 +128,13 @@ where
chunk_size: usize,
use_randomized_compute_unit_price: bool,
instruction_padding_config: Option<InstructionPaddingConfig>,
num_conflict_groups: Option<usize>,
) -> Self {
let account_chunks = KeypairChunks::new(gen_keypairs, chunk_size);
let account_chunks = if let Some(num_conflict_groups) = num_conflict_groups {
KeypairChunks::new_with_conflict_groups(gen_keypairs, chunk_size, num_conflict_groups)
} else {
KeypairChunks::new(gen_keypairs, chunk_size)
};
let nonce_chunks =
nonce_keypairs.map(|nonce_keypairs| KeypairChunks::new(nonce_keypairs, chunk_size));

Expand Down Expand Up @@ -353,6 +376,7 @@ where
use_randomized_compute_unit_price,
use_durable_nonce,
instruction_padding_config,
num_conflict_groups,
..
} = config;

Expand All @@ -364,6 +388,7 @@ where
tx_count,
use_randomized_compute_unit_price,
instruction_padding_config,
num_conflict_groups,
);

let first_tx_count = loop {
Expand Down Expand Up @@ -1160,4 +1185,60 @@ mod tests {
}
withdraw_durable_nonce_accounts(client, &authority_keypairs, &nonce_keypairs)
}

#[test]
fn test_bench_tps_key_chunks_new() {
let num_keypairs = 16;
let chunk_size = 4;
let keypairs = std::iter::repeat_with(Keypair::new)
.take(num_keypairs)
.collect::<Vec<_>>();

let chunks = KeypairChunks::new(&keypairs, chunk_size);
assert_eq!(
chunks.source[0],
&[&keypairs[0], &keypairs[1], &keypairs[2], &keypairs[3]]
);
assert_eq!(
chunks.dest[0],
&[&keypairs[4], &keypairs[5], &keypairs[6], &keypairs[7]]
);
assert_eq!(
chunks.source[1],
&[&keypairs[8], &keypairs[9], &keypairs[10], &keypairs[11]]
);
assert_eq!(
chunks.dest[1],
&[&keypairs[12], &keypairs[13], &keypairs[14], &keypairs[15]]
);
}

#[test]
fn test_bench_tps_key_chunks_new_with_conflict_groups() {
let num_keypairs = 16;
let chunk_size = 4;
let num_conflict_groups = 2;
let keypairs = std::iter::repeat_with(Keypair::new)
.take(num_keypairs)
.collect::<Vec<_>>();

let chunks =
KeypairChunks::new_with_conflict_groups(&keypairs, chunk_size, num_conflict_groups);
assert_eq!(
chunks.source[0],
&[&keypairs[0], &keypairs[1], &keypairs[2], &keypairs[3]]
);
assert_eq!(
chunks.dest[0],
&[&keypairs[4], &keypairs[5], &keypairs[4], &keypairs[5]]
);
assert_eq!(
chunks.source[1],
&[&keypairs[8], &keypairs[9], &keypairs[10], &keypairs[11]]
);
assert_eq!(
chunks.dest[1],
&[&keypairs[12], &keypairs[13], &keypairs[12], &keypairs[13]]
);
}
}
19 changes: 18 additions & 1 deletion bench-tps/src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use {
crate::spl_convert::FromOtherSolana,
clap::{crate_description, crate_name, App, Arg, ArgMatches},
solana_clap_utils::input_validators::{is_url, is_url_or_moniker},
solana_clap_utils::input_validators::{is_url, is_url_or_moniker, is_within_range},
solana_cli_config::{ConfigInput, CONFIG_FILE},
solana_sdk::{
fee_calculator::FeeRateGovernor,
Expand Down Expand Up @@ -65,6 +65,7 @@ pub struct Config {
pub use_randomized_compute_unit_price: bool,
pub use_durable_nonce: bool,
pub instruction_padding_config: Option<InstructionPaddingConfig>,
pub num_conflict_groups: Option<usize>,
}

impl Default for Config {
Expand Down Expand Up @@ -95,6 +96,7 @@ impl Default for Config {
use_randomized_compute_unit_price: false,
use_durable_nonce: false,
instruction_padding_config: None,
num_conflict_groups: None,
}
}
}
Expand Down Expand Up @@ -342,6 +344,13 @@ pub fn build_args<'a>(version: &'_ str) -> App<'a, '_> {
.takes_value(true)
.help("If set, wraps all instructions in the instruction padding program, with the given amount of padding bytes in instruction data."),
)
.arg(
Arg::with_name("num_conflict_groups")
.long("num-conflict-groups")
.takes_value(true)
.validator(|arg| is_within_range(arg, 1, usize::MAX - 1))
.help("The number of unique destination accounts per transactions 'chunk'. Lower values will result in more transaction conflicts.")
)
}

/// Parses a clap `ArgMatches` structure into a `Config`
Expand Down Expand Up @@ -494,5 +503,13 @@ pub fn extract_args(matches: &ArgMatches) -> Config {
});
}

if let Some(num_conflict_groups) = matches.value_of("num_conflict_groups") {
args.num_conflict_groups = Some(
num_conflict_groups
.parse()
.expect("Can't parse conflict groups"),
);
}

args
}

0 comments on commit 9fa3cb6

Please sign in to comment.