From 1524fb804ed80b67809033a826e48c9ed781955f Mon Sep 17 00:00:00 2001 From: Ali Behjati Date: Tue, 14 May 2024 20:45:24 +0200 Subject: [PATCH] refactor: replace sorting with quick select This change replaces the deterministic optimized merge-sort with a highly optimized quick select that performs signficantly faster when publisher count is more than 20. The previous algorithm is very fast running on a normal CPU but doesn't work very well in BPF because all instructions (like load, move, ..) have the same cost as comparisons and are not optimized. Quick select on the other hand, minimized all the operations in the average case due to its minimal reads and writes. In 32-publisher setup, quick select reduces the CU from 16.5k to 11.5k and in the 64-publisher setup 37k to 17.5k. The numbers are the worst cases running on randomized input. Worst measured case happens when all elements are equal and is 13k and 24k for 32-publisher and 64-publisher setup respectively. Unfortunately there is no way to systematically get the the compute usage out of program test. The `test_benchmark` file has a simple code that helps running benchmarks on various number of publishers. Quick select algorithm has a quadratic worst case but it is very unlikely to happen and no publisher alone can manipulate the list of prices by themselves. --- program/c/makefile | 1 - program/c/src/oracle/model/price_model.c | 169 ++++++++------- program/c/src/oracle/model/price_model.h | 83 +------- program/c/src/oracle/model/test_price_model.c | 22 +- .../c/src/oracle/sort/sort_stable_base_gen.c | 94 --------- program/c/src/oracle/sort/test_sort_stable.c | 73 ------- program/c/src/oracle/sort/tmpl/sort_stable.c | 195 ------------------ .../c/src/oracle/sort/tmpl/sort_stable_base.c | 21 -- program/c/src/oracle/upd_aggregate.h | 3 +- program/rust/src/tests/mod.rs | 1 + program/rust/src/tests/pyth_simulator.rs | 3 +- program/rust/src/tests/test_benchmark.rs | 80 +++++++ 12 files changed, 192 insertions(+), 553 deletions(-) delete mode 100644 program/c/src/oracle/sort/sort_stable_base_gen.c delete mode 100644 program/c/src/oracle/sort/test_sort_stable.c delete mode 100644 program/c/src/oracle/sort/tmpl/sort_stable.c delete mode 100644 program/c/src/oracle/sort/tmpl/sort_stable_base.c create mode 100644 program/rust/src/tests/test_benchmark.rs diff --git a/program/c/makefile b/program/c/makefile index 3b791858a..65cc81c34 100644 --- a/program/c/makefile +++ b/program/c/makefile @@ -41,7 +41,6 @@ cpyth-native: features.h test: features.h mkdir -p $(OUT_DIR)/test/ gcc -c ./src/oracle/model/test_price_model.c -o $(OUT_DIR)/test/test_price_model.o -fPIC - gcc -c ./src/oracle/sort/test_sort_stable.c -o $(OUT_DIR)/test/test_sort_stable.o -fPIC gcc -c ./src/oracle/util/test_align.c -o $(OUT_DIR)/test/test_align.o -fPIC gcc -c ./src/oracle/util/test_avg.c -o $(OUT_DIR)/test/test_avg.o -fPIC gcc -c ./src/oracle/util/test_hash.c -o $(OUT_DIR)/test/test_hash.o -fPIC diff --git a/program/c/src/oracle/model/price_model.c b/program/c/src/oracle/model/price_model.c index 846ee898d..5a24d4c07 100644 --- a/program/c/src/oracle/model/price_model.c +++ b/program/c/src/oracle/model/price_model.c @@ -1,83 +1,92 @@ #include "price_model.h" #include "../util/avg.h" /* For avg_2_int64 */ -#define SORT_NAME int64_sort_ascending -#define SORT_KEY_T int64_t -#include "../sort/tmpl/sort_stable.c" +/* + * Quick select implementation highly optimized for reducing the number of compute units in BPF. + * + * On Floyd and Rivest's SELECT algorithm" by Krzysztof C. Kiwiel article + * mentions that the average case of this algorithm is is 2.5n + o(n) comparisons. + * The article itself presents a different algorithm (Floyd and Rivest) which + * performs faster in larger arrays but has a higher overhead. Other determinstic + * algorithms like median of medians have a higher overhead (or comibnations of + * them and quick select) and are not suitable for BPF. + */ +int64_t quick_select( int64_t * a, uint64_t n, uint64_t k ) { + while (1) { + if (n == 1) return a[0]; + + int64_t pivot; + + /* + * Select median of 3 as pivot which massively reduces the number of + * comparisons in the average case and also helps in reducing the likelihood + * of worst case time complexity. + */ + { + if (a[0] < a[n / 2]) { + if (a[n / 2] < a[n - 1]) pivot = a[n / 2]; + else if (a[0] < a[n - 1]) pivot = a[n - 1]; + else pivot = a[0]; + } else { + if (a[n / 2] > a[n - 1]) pivot = a[n / 2]; + else if (a[0] > a[n - 1]) pivot = a[n - 1]; + else pivot = a[0]; + } + } + + /* + * Partition the array around the pivot. This partitioning is done in a + * way that if the array has a lot of duplicate pivots, it doesn't result in + * quadratic time complexity and evenly distributes the pivots. + * + * When the loop is done, the array looks like any of the following: + * 1. ....ji.... + * In this case the elements a[x] for x <= j are <= pivot and a[x] for x >= i are >= pivot. + * + * 2. ....j.i... + * In this case same conditions as above apply but a[j+1] == pivot because the last step + * resulting in j and i in this position is i==j and swapping which means that that element + * is neither < pivot nor > pivot. + * + * In the case of multiple pivot elements, the elements will be evenly distributed because + * i nor j won't move if the element is equal to the pivot and if both be equal to the pivot + * they will swap (which is ineffective) and move forward which results in one pivot to the + * left and one to the right. + */ + uint64_t i = 0, j = n - 1; + while (i <= j) { + if (a[i] < pivot) { + i++; + continue; + } + if (a[j] > pivot) { + j--; + continue; + } + + int64_t tmp = a[i]; + a[i] = a[j]; + a[j] = tmp; + i++; + j--; + } + + if (k < j + 1) n = j + 1; + else if (j + 2 == i && k == j + 1) return pivot; + else { + a += j + 1; + n -= j + 1; + k -= j + 1; + } + } +} -int64_t * +void price_model_core( uint64_t cnt, int64_t * quote, int64_t * _p25, int64_t * _p50, - int64_t * _p75, - void * scratch ) { - - /* Sort the quotes. The sorting implementation used here is a highly - optimized mergesort (merge with an unrolled insertion sorting - network small n base cases). The best case is ~0.5 n lg n compares - and the average and worst cases are ~n lg n compares. - - While not completely data oblivious, this has quite low variance in - operation count practically and this is _better_ than quicksort's - average case and quicksort's worst case is a computational - denial-of-service and timing attack vulnerable O(n^2). Unlike - quicksort, this is also stable (but this stability does not - currently matter ... it might be a factor in future models). - - A data oblivious sorting network approach might be viable here with - and would have a completely deterministic operations count. It - currently isn't used as the best known practical approaches for - general n have a worse algorithmic cost (O( n (lg n)^2 )) and, - while the application probably doesn't need perfect obliviousness, - mergesort is still moderately oblivious and the application can - benefit from mergesort's lower operations cost. (The main drawback - of mergesort over quicksort is that it isn't in place, but memory - footprint isn't an issue here.) - - Given the operations cost model (e.g. cache friendliness is not - incorporated), a radix sort might be viable here (O(n) in best / - average / worst). It currently isn't used as we expect invocations - with small-ish n to be common and radix sort would be have large - coefficients on the O(n) and additional fixed overheads that would - make it more expensive than mergesort in this regime. - - Note: price_model_cnt_valid( cnt ) implies - int64_sort_ascending_cnt_valid( cnt ) currently. - - Note: consider filtering out "NaN" quotes (i.e. INT64_MIN)? */ - - int64_t * sort_quote = int64_sort_ascending_stable( quote, cnt, scratch ); - - /* Extract the p25 - - There are many variants with subtle tradeoffs here. One option is - to interpolate when the ideal p25 is bracketed by two samples (akin - to the p50 interpolation above when the number of quotes is even). - That is, for p25, interpolate between quotes floor((cnt-2)/4) and - ceil((cnt-2)/4) with the weights determined by cnt mod 4. The - current preference is to not do that as it is slightly more - complex, doesn't exactly always minimize the current loss function - and is more exposed to the confidence intervals getting skewed by - bum quotes with the number of quotes is small. - - Another option is to use the inside quote of the above pair. That - is, for p25, use quote ceil((cnt-2)/4) == floor((cnt+1)/4) == - (cnt+1)>>2. The current preference is not to do this as, though - this has stronger bum quote robustness, it results in p25==p50==p75 - when cnt==3. (In this case, the above wants to do an interpolation - between quotes 0 and 1 to for the p25 and between quotes 1 and 2 - for the p75. But limiting to just the inside quote results in - p25/p50/p75 all using the median quote.) - - A tweak to this option, for p25, is to use floor(cnt/4) == cnt>>2. - This is simple, has the same asymptotic behavior for large cnt, has - good behavior in the cnt==3 case and practically as good bum quote - rejection in the moderate cnt case. */ - - uint64_t p25_idx = cnt >> 2; - - *_p25 = sort_quote[p25_idx]; + int64_t * _p75) { /* Extract the p50 */ @@ -85,15 +94,15 @@ price_model_core( uint64_t cnt, uint64_t p50_idx = cnt >> 1; /* ==ceil((cnt-1)/2) */ - *_p50 = sort_quote[p50_idx]; + *_p50 = quick_select(quote, cnt, p50_idx); } else { /* Even number of quotes (at least 2) */ uint64_t p50_idx_right = cnt >> 1; /* == ceil((cnt-1)/2)> 0 */ uint64_t p50_idx_left = p50_idx_right - (uint64_t)1; /* ==floor((cnt-1)/2)>=0 (no overflow/underflow) */ - int64_t vl = sort_quote[p50_idx_left ]; - int64_t vr = sort_quote[p50_idx_right]; + int64_t vl = quick_select(quote, cnt, p50_idx_left); + int64_t vr = quick_select(quote, cnt, p50_idx_right); /* Compute the average of vl and vr (with floor / round toward negative infinity rounding and without possibility of @@ -102,11 +111,15 @@ price_model_core( uint64_t cnt, *_p50 = avg_2_int64( vl, vr ); } + /* Extract the p25 */ + + uint64_t p25_idx = cnt >> 2; + + *_p25 = quick_select(quote, cnt, p25_idx); + /* Extract the p75 (this is the mirror image of the p25 case) */ uint64_t p75_idx = cnt - ((uint64_t)1) - p25_idx; - *_p75 = sort_quote[p75_idx]; - - return sort_quote; + *_p75 = quick_select(quote, cnt, p75_idx); } diff --git a/program/c/src/oracle/model/price_model.h b/program/c/src/oracle/model/price_model.h index c7d14d198..084bc8fd8 100644 --- a/program/c/src/oracle/model/price_model.h +++ b/program/c/src/oracle/model/price_model.h @@ -8,91 +8,12 @@ extern "C" { #endif -/* Returns the minimum and maximum number of quotes the implementation - can handle */ - -static inline uint64_t -price_model_quote_min( void ) { - return (uint64_t)1; -} - -static inline uint64_t -price_model_quote_max( void ) { - return (UINT64_MAX-(uint64_t)alignof(int64_t)+(uint64_t)1) / (uint64_t)sizeof(int64_t); -} - -/* price_model_cnt_valid returns non-zero if cnt is a valid value or - zero if not. */ - -static inline int -price_model_cnt_valid( uint64_t cnt ) { - return price_model_quote_min()<=cnt && cnt<=price_model_quote_max(); -} - -/* price_model_scratch_footprint returns the number of bytes of scratch - space needed for an arbitrarily aligned scratch region required by - price_model to handle price_model_quote_min() to cnt quotes - inclusive. */ - -static inline uint64_t -price_model_scratch_footprint( uint64_t cnt ) { /* Assumes price_model_cnt_valid( cnt ) is true */ - /* cnt int64_t's plus worst case alignment padding, no overflow - possible as cnt is valid at this point */ - return cnt*(uint64_t)sizeof(int64_t)+(uint64_t)alignof(int64_t)-(uint64_t)1; -} - -/* price_model_core minimizes (to quote precision in a floor / round - toward negative infinity sense) the loss model of the given quotes. - Assumes valid inputs (e.g. cnt is at least 1 and not unreasonably - large ... typically a multiple of 3 but this is not required, - quote[i] for i in [0,cnt) are the quotes of interest on input, p25, - p50, p75 point to where to write model outputs, scratch points to a - suitable footprint scratch region). - - Returns a pointer to the quotes sorted in ascending order. As such, - the min and max and any other rank statistic can be extracted easily - on return. This location will either be quote itself or to a - location in scratch. Use price_model below for a variant that always - replaces quote with the sorted quotes (potentially has extra ops for - copying). Further, on return, *_p25, *_p50, *_p75 will hold the loss - model minimizing values for the input quotes and the scratch region - was clobbered. - - Scratch points to a memory region of arbitrary alignment with at - least price_model_scratch_footprint( cnt ) bytes and it will be - clobbered on output. It is sufficient to use a normally aligned / - normally allocated / normally declared array of cnt int64_t's. - - The cost of this function is a fast and low variance (but not - completely data oblivious) O(cnt lg cnt) in the best / average / - worst cases. This function uses no heap / dynamic memory allocation. - It is thread safe provided it passed non-conflicting quote, output - and scratch arrays. It has a bounded call depth ~lg cnt <= ~64 (this - could reduce to O(1) by using a non-recursive sort/select - implementation under the hood if desired). */ - -int64_t * /* Returns pointer to sorted quotes (either quote or ALIGN_UP(scratch,int64_t)) */ +void price_model_core( uint64_t cnt, /* Assumes price_model_cnt_valid( cnt ) is true */ int64_t * quote, /* Assumes quote[i] for i in [0,cnt) is the i-th quote on input */ int64_t * _p25, /* Assumes *_p25 is safe to write to the p25 model output */ int64_t * _p50, /* Assumes *_p50 " */ - int64_t * _p75, /* Assumes *_p75 " */ - void * scratch ); /* Assumes a suitable scratch region */ - -/* Same as the above but always returns quote and quote always holds the - sorted quotes on return. */ - -static inline int64_t * -price_model( uint64_t cnt, - int64_t * quote, - int64_t * _p25, - int64_t * _p50, - int64_t * _p75, - void * scratch ) { - int64_t * tmp = price_model_core( cnt, quote, _p25, _p50, _p75, scratch ); - if( tmp!=quote ) for( uint64_t idx=(uint64_t)0; idx>2; uint64_t p50_idx = cnt>>1; uint64_t p75_idx = cnt - (uint64_t)1 - p25_idx; uint64_t is_even = (uint64_t)!(cnt & (uint64_t)1); - if( val[0]!=quote[ p25_idx ] ) { printf( "FAIL (p25)\n" ); return 1; } - if( val[1]!=avg_2_int64( quote[ p50_idx-is_even ], quote[ p50_idx ] ) ) { printf( "FAIL (p50)\n" ); return 1; } - if( val[2]!=quote[ p75_idx ] ) { printf( "FAIL (p75)\n" ); return 1; } + if( val[0]!=quote0[ p25_idx ] ) { + printf( "FAILED (p25). given: %lld, expected: %lld\n", val[0], quote0[ p25_idx ] ); + return 1; + } + if( val[1]!=avg_2_int64( quote0[ p50_idx-is_even ], quote0[ p50_idx ] ) ) { + printf( "FAIL (p50). given: %lld, expected: %lld\n", val[1], avg_2_int64( quote0[ p50_idx-is_even ], quote0[ p50_idx ] ) ); + return 1; + } + if( val[2]!=quote0[ p75_idx ] ) { + printf( "FAIL (p75). given: %lld, expected: %lld\n", val[2], quote0[ p75_idx ] ); + return 1; + } } # undef N diff --git a/program/c/src/oracle/sort/sort_stable_base_gen.c b/program/c/src/oracle/sort/sort_stable_base_gen.c deleted file mode 100644 index 046abf8b8..000000000 --- a/program/c/src/oracle/sort/sort_stable_base_gen.c +++ /dev/null @@ -1,94 +0,0 @@ -#include -#include - -void -sort_gen( int n ) { - -# if 0 - - /* In register variant (PSEUDO OPS ~ 9+4n+3n(n-1)) - Assumes switch ~ 3 PSEUDO OPS (LDA,LD,JMP) -> 3 switch statements / 9 pseudo ops - Assumes load ~ 2 PSEUDO OPS (LDA,LD) -> n loads / 2n pseudo ops - Assumes store ~ " - Assumes cswap ~ 6 PSEUDO OPS (CMP,MOV,TESTEQ,CMOV,TESTNEQ,CMOV) / 6*0.5*n*(n-1) pseudo ops */ - -//printf( "static inline key_t * /* returns (sorted) x */\n" ); -//printf( "sort_network_stable( key_t * x, /* indexed [0,n) */\n" ); -//printf( " ulong n ) { /* assumes n in [0,%i) */\n", n ); - printf( " int c;\n" ); - printf( " key_t t" ); - for( int i=0; i 3 pseudo ops - Assumes cswap ~ 9 PSEUDO OPS (LDA,LDA,LD,LD,CMP,CMOV,CMOV,ST,ST) / 9*0.5*n*(n-1) pseudo ops */ - -//printf( "static inline key_t * /* returns (sorted) x */\n" ); -//printf( "sort_network_stable( key_t * x, /* indexed [0,n) */\n" ); -//printf( " ulong n ) { /* assumes n in [0,%i) */\n", n ); - - printf( " do { /* BEGIN AUTOGENERATED CODE (n=%2i) *****************************/\n", n ); - printf( " /* This network of comparators and fallthroughs implement a sorting network representation\n" ); - printf( " of an insertion sort. Each case acts as a sort pass with the fallthrough falling through\n" ); - printf( " to smaller ranges of the input. */\n"); - printf( "# define SORT_STABLE_CE(i,j) u = x[(SORT_IDX_T)i]; v = x[(SORT_IDX_T)j]; c = SORT_BEFORE( v, u ); x[(SORT_IDX_T)i] = c ? v : u; x[(SORT_IDX_T)j] = c ? u : v\n" ); - printf( " int c;\n" ); - printf( " SORT_KEY_T u;\n" ); - printf( " SORT_KEY_T v;\n" ); - printf( " switch( n ) {\n" ); - for( int i=n-1; i>=0; i-- ) { - printf( " case (SORT_IDX_T)%2i:", i+1 ); - for( int j=0; j -#include "../util/util.h" - -#define BEFORE(i,j) (((i)>>16)<((j)>>16)) - -#define SORT_NAME sort -#define SORT_KEY_T int -#define SORT_IDX_T int -#define SORT_BEFORE(i,j) BEFORE(i,j) -#include "tmpl/sort_stable.c" - -int test_sort_stable() { - -# define N 96 - int x[N]; - int y[N]; - int w[N]; - - /* Brute force validate small sizes via the 0-1 principle (with - additional information in the keys to validate stability as well). */ - - for( int n=0; n<=24; n++ ) { - for( long b=0L; b<(1L<>i) & 1L))<<16) | i; - for( int i=0; i=n || z[i]!=w[j] ) { printf( "FAIL (corrupt)\n" ); return 1; } - w[j] = -1; /* Mark that this entry has already been confirmed */ - } - for( int i=0; i=n || z[i]!=w[j] ) { printf( "FAIL (corrupt)\n" ); return 1; } - w[j] = -1; /* Mark that this entry has already been confirmed */ - } - for( int i=0; i=0 always true" if idx is an unsigned type or - "n<=UINT64_MAX always true" if key is a byte type). */ - static uint64_t const max = ((UINT64_MAX - (uint64_t)alignof(SORT_KEY_T) + (uint64_t)1) / (uint64_t)sizeof(SORT_KEY_T)); - return !cnt || (((SORT_IDX_T)0)> 1; - SORT_KEY_T * yl = SORT_IMPL(stable_node)( xl,nl, tl ); - - SORT_KEY_T * xr = x + nl; - SORT_KEY_T * tr = t + nl; - SORT_IDX_T nr = n - nl; - SORT_KEY_T * yr = SORT_IMPL(stable_node)( xr,nr, tr ); - - /* If left subsort result ended up in orig array, merge into temp - array. Otherwise, merge into orig array. */ - - if( yl==xl ) x = t; - - /* At this point, note that yl does not overlap with the location for - merge output at this point. yr might overlap (with the right half) - with the location for merge output but this will still work in that - case. */ - - SORT_IDX_T i = (SORT_IDX_T)0; - SORT_IDX_T j = (SORT_IDX_T)0; - SORT_IDX_T k = (SORT_IDX_T)0; - - /* Note that nl and nr are both at least one at this point so at least - one iteration of the loop body is necessary. */ - - for(;;) { /* Minimal C language operations */ - if( SORT_BEFORE( yr[k], yl[j] ) ) { - x[i++] = yr[k++]; - if( k>=nr ) { /* append left stragglers (at least one) */ do x[i++] = yl[j++]; while( j=nl ) { /* append right stragglers (at least one) */ do x[i++] = yr[k++]; while( k0 and nprcs = 3*numv at this point int64_t agg_p25; int64_t agg_p75; - int64_t scratch[ PC_NUM_COMP * 3 ]; // ~0.75KiB for current PC_NUM_COMP (FIXME: DOUBLE CHECK THIS FITS INTO STACK FRAME LIMIT) - price_model_core( (uint64_t)nprcs, prcs, &agg_p25, &agg_price, &agg_p75, scratch ); + price_model_core( (uint64_t)nprcs, prcs, &agg_p25, &agg_price, &agg_p75 ); // get the left and right confidences // note that as valid quotes have positive prices currently and diff --git a/program/rust/src/tests/mod.rs b/program/rust/src/tests/mod.rs index 8208d1536..dd78fd402 100644 --- a/program/rust/src/tests/mod.rs +++ b/program/rust/src/tests/mod.rs @@ -3,6 +3,7 @@ mod test_add_price; mod test_add_product; mod test_add_publisher; mod test_aggregation; +mod test_benchmark; mod test_c_code; mod test_check_valid_signable_account_or_permissioned_funding_account; mod test_del_price; diff --git a/program/rust/src/tests/pyth_simulator.rs b/program/rust/src/tests/pyth_simulator.rs index 876405039..e22867161 100644 --- a/program/rust/src/tests/pyth_simulator.rs +++ b/program/rust/src/tests/pyth_simulator.rs @@ -55,6 +55,7 @@ use { }, solana_sdk::{ account::Account, + commitment_config::CommitmentLevel, signature::{ Keypair, Signer, @@ -206,7 +207,7 @@ impl PythSimulator { self.context .banks_client - .process_transaction(transaction) + .process_transaction_with_commitment(transaction, CommitmentLevel::Processed) .await } diff --git a/program/rust/src/tests/test_benchmark.rs b/program/rust/src/tests/test_benchmark.rs new file mode 100644 index 000000000..adca41479 --- /dev/null +++ b/program/rust/src/tests/test_benchmark.rs @@ -0,0 +1,80 @@ +use { + crate::{ + c_oracle_header::PC_STATUS_TRADING, + tests::pyth_simulator::{ + PythSimulator, + Quote, + }, + }, + solana_program::native_token::LAMPORTS_PER_SOL, + solana_sdk::{ + signature::Keypair, + signer::Signer, + }, +}; + +/// Benchmark the execution of the oracle program +async fn run_benchmark(num_publishers: usize) -> Result<(), Box> { + let mut sim = PythSimulator::new().await; + + let mapping_keypair = sim.init_mapping().await?; + let product_keypair = sim.add_product(&mapping_keypair).await?; + let price_keypair = sim.add_price(&product_keypair, -5).await?; + let price_pubkey = price_keypair.pubkey(); + + let mut publishers_keypairs: Vec<_> = (0..num_publishers).map(|_idx| Keypair::new()).collect(); + publishers_keypairs.sort_by_key(|kp| kp.pubkey()); + + let publishers_pubkeys: Vec<_> = publishers_keypairs.iter().map(|kp| kp.pubkey()).collect(); + + for publisher in publishers_pubkeys.iter() { + sim.airdrop(publisher, 100 * LAMPORTS_PER_SOL).await?; + sim.add_publisher(&price_keypair, *publisher).await?; + } + + let mut i = 0; + for kp in publishers_keypairs.iter() { + let quote = Quote { + price: 10000 - 3 * i, + confidence: i as u64, + status: PC_STATUS_TRADING, + }; + i += 3; + + sim.upd_price(kp, price_pubkey, quote).await?; + } + + // Advance slot once from 1 to 2 + sim.warp_to_slot(2).await?; + + // Final price update to trigger aggregation + let first_kp = publishers_keypairs.first().unwrap(); + let first_quote = Quote { + price: 100, + confidence: 30, + status: PC_STATUS_TRADING, + }; + sim.upd_price(first_kp, price_pubkey, first_quote).await?; + + Ok(()) +} + +#[tokio::test] +async fn test_benchmark_64_pubs() -> Result<(), Box> { + run_benchmark(64).await +} + +#[tokio::test] +async fn test_benchmark_32_pubs() -> Result<(), Box> { + run_benchmark(32).await +} + +#[tokio::test] +async fn test_benchmark_16_pubs() -> Result<(), Box> { + run_benchmark(16).await +} + +#[tokio::test] +async fn test_benchmark_8_pubs() -> Result<(), Box> { + run_benchmark(8).await +}