Skip to content
This repository was archived by the owner on Jun 30, 2022. It is now read-only.

Commit 2156320

Browse files
jayantkJayant Krishnamurthy
andauthored
Instruction counts and optimizations (#9)
* instruction counts * reduce normalize opcount * instruction counts * tests Co-authored-by: Jayant Krishnamurthy <[email protected]>
1 parent 25cbde5 commit 2156320

File tree

6 files changed

+185
-93
lines changed

6 files changed

+185
-93
lines changed

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
A rust API for describing on-chain pyth account structures. A primer on pyth accounts can be found at https://github.com/pyth-network/pyth-client/blob/main/doc/aggregate_price.md
44

5-
65
Contains a library for use in on-chain program development and an off-chain example program for loading and printing product reference data and aggregate prices from all devnet pyth accounts.
76

87
### Running the Example
@@ -38,3 +37,10 @@ product_account .. 6MEwdxe4g1NeAF9u6KDG14anJpFsVEa2cvr5H6iriFZ8
3837
twap ......... 7426390900
3938
twac ......... 2259870
4039
```
40+
41+
42+
### Development
43+
44+
Run `cargo test-bpf` to build in BPF and run the unit tests.
45+
This command will also build an instruction count program that logs the resource consumption
46+
of various functions.

src/instruction.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,17 @@ pub enum PythClientInstruction {
1919
x: PriceConf,
2020
y: PriceConf,
2121
},
22+
Add {
23+
x: PriceConf,
24+
y: PriceConf,
25+
},
26+
ScaleToExponent {
27+
x: PriceConf,
28+
expo: i32,
29+
},
30+
Normalize {
31+
x: PriceConf,
32+
},
2233
/// Don't do anything for comparison
2334
///
2435
/// No accounts required for this instruction
@@ -45,6 +56,36 @@ pub fn multiply(x: PriceConf, y: PriceConf) -> Instruction {
4556
}
4657
}
4758

59+
pub fn add(x: PriceConf, y: PriceConf) -> Instruction {
60+
Instruction {
61+
program_id: id(),
62+
accounts: vec![],
63+
data: PythClientInstruction::Add { x, y }
64+
.try_to_vec()
65+
.unwrap(),
66+
}
67+
}
68+
69+
pub fn scale_to_exponent(x: PriceConf, expo: i32) -> Instruction {
70+
Instruction {
71+
program_id: id(),
72+
accounts: vec![],
73+
data: PythClientInstruction::ScaleToExponent { x, expo }
74+
.try_to_vec()
75+
.unwrap(),
76+
}
77+
}
78+
79+
pub fn normalize(x: PriceConf) -> Instruction {
80+
Instruction {
81+
program_id: id(),
82+
accounts: vec![],
83+
data: PythClientInstruction::Normalize { x }
84+
.try_to_vec()
85+
.unwrap(),
86+
}
87+
}
88+
4889
/// Noop instruction for comparison purposes
4990
pub fn noop() -> Instruction {
5091
Instruction {

src/price_conf.rs

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ use {
66
const PD_EXPO: i32 = -9;
77
const PD_SCALE: u64 = 1_000_000_000;
88
const MAX_PD_V_U64: u64 = (1 << 28) - 1;
9-
const MAX_PD_V_I64: i64 = MAX_PD_V_U64 as i64;
10-
const MIN_PD_V_I64: i64 = -MAX_PD_V_I64;
119

1210
/**
1311
* A price with a degree of uncertainty, represented as a price +- a confidence interval.
@@ -81,6 +79,7 @@ impl PriceConf {
8179
let other_confidence_pct: u64 = (other.conf * PD_SCALE) / other_price;
8280

8381
// first term is 57 bits, second term is 57 + 58 - 29 = 86 bits. Same exponent as the midprice.
82+
// Note: the computation of the 2nd term consumes about 3k ops. We may want to optimize this.
8483
let conf = (((base.conf * PD_SCALE) / other_price) as u128) + ((other_confidence_pct as u128) * (midprice as u128)) / (PD_SCALE as u128);
8584

8685
// Note that this check only fails if an argument's confidence interval was >> its price,
@@ -155,18 +154,19 @@ impl PriceConf {
155154
* have been normalized to be between `MIN_PD_V_I64` and `MAX_PD_V_I64`.
156155
*/
157156
pub fn normalize(&self) -> Option<PriceConf> {
158-
let mut p = self.price;
157+
// signed division is very expensive in op count
158+
let (mut p, s) = PriceConf::to_unsigned(self.price);
159159
let mut c = self.conf;
160160
let mut e = self.expo;
161161

162-
while p > MAX_PD_V_I64 || p < MIN_PD_V_I64 || c > MAX_PD_V_U64 {
162+
while p > MAX_PD_V_U64 || c > MAX_PD_V_U64 {
163163
p = p / 10;
164164
c = c / 10;
165165
e = e.checked_add(1)?;
166166
}
167167

168168
Some(PriceConf {
169-
price: p,
169+
price: (p as i64) * s,
170170
conf: c,
171171
expo: e,
172172
})
@@ -188,7 +188,8 @@ impl PriceConf {
188188
if delta >= 0 {
189189
let mut p = self.price;
190190
let mut c = self.conf;
191-
while delta > 0 {
191+
// 2nd term is a short-circuit to bound op consumption
192+
while delta > 0 && (p != 0 || c != 0) {
192193
p /= 10;
193194
c /= 10;
194195
delta -= 1;
@@ -203,7 +204,8 @@ impl PriceConf {
203204
let mut p = Some(self.price);
204205
let mut c = Some(self.conf);
205206

206-
while delta < 0 {
207+
// 2nd & 3rd terms are a short-circuit to bound op consumption
208+
while delta < 0 && p.is_some() && c.is_some() {
207209
p = p?.checked_mul(10);
208210
c = c?.checked_mul(10);
209211
delta += 1;
@@ -226,11 +228,10 @@ impl PriceConf {
226228
* some of the computations above.
227229
*/
228230
fn to_unsigned(x: i64) -> (u64, i64) {
229-
// this check is stricter than necessary. it technically only needs to guard against
230-
// i64::MIN, which can't be negated. However, this method should only be used in the context
231-
// of normalized numbers.
232-
assert!(x <= MAX_PD_V_I64 && x >= MIN_PD_V_I64);
233-
if x < 0 {
231+
if x == i64::MIN {
232+
// special case because i64::MIN == -i64::MIN
233+
(i64::MAX as u64 + 1, -1)
234+
} else if x < 0 {
234235
(-x as u64, -1)
235236
} else {
236237
(x as u64, 1)
@@ -240,7 +241,10 @@ impl PriceConf {
240241

241242
#[cfg(test)]
242243
mod test {
243-
use crate::price_conf::{MAX_PD_V_U64, MAX_PD_V_I64, MIN_PD_V_I64, PD_EXPO, PD_SCALE, PriceConf};
244+
use crate::price_conf::{MAX_PD_V_U64, PD_EXPO, PD_SCALE, PriceConf};
245+
246+
const MAX_PD_V_I64: i64 = MAX_PD_V_U64 as i64;
247+
const MIN_PD_V_I64: i64 = -MAX_PD_V_I64;
244248

245249
fn pc(price: i64, conf: u64, expo: i32) -> PriceConf {
246250
PriceConf {

src/processor.rs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ use borsh::BorshDeserialize;
44
use solana_program::{
55
account_info::AccountInfo,
66
entrypoint::ProgramResult,
7-
log::sol_log_compute_units,
8-
msg,
97
pubkey::Pubkey,
108
};
119

@@ -21,24 +19,26 @@ pub fn process_instruction(
2119
let instruction = PythClientInstruction::try_from_slice(input).unwrap();
2220
match instruction {
2321
PythClientInstruction::Divide { numerator, denominator } => {
24-
msg!("Calculating numerator.div(denominator)");
25-
sol_log_compute_units();
26-
let result = numerator.div(&denominator);
27-
sol_log_compute_units();
28-
msg!("result: {:?}", result);
22+
numerator.div(&denominator);
2923
Ok(())
3024
}
3125
PythClientInstruction::Multiply { x, y } => {
32-
msg!("Calculating numerator.mul(denominator)");
33-
sol_log_compute_units();
34-
let result = x.mul(&y);
35-
sol_log_compute_units();
36-
msg!("result: {:?}", result);
26+
x.mul(&y);
27+
Ok(())
28+
}
29+
PythClientInstruction::Add { x, y } => {
30+
x.add(&y);
31+
Ok(())
32+
}
33+
PythClientInstruction::Normalize { x } => {
34+
x.normalize();
35+
Ok(())
36+
}
37+
PythClientInstruction::ScaleToExponent { x, expo } => {
38+
x.scale_to_exponent(expo);
3739
Ok(())
3840
}
3941
PythClientInstruction::Noop => {
40-
msg!("Do nothing");
41-
msg!("{}", 0_u64);
4242
Ok(())
4343
}
4444
}

tests/instruction_count.rs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
use {
2+
pyth_client::{id, instruction, PriceConf},
3+
pyth_client::processor::process_instruction,
4+
solana_program::{
5+
instruction::Instruction,
6+
pubkey::Pubkey,
7+
},
8+
solana_program_test::*,
9+
solana_sdk::{signature::Signer, transaction::Transaction},
10+
};
11+
12+
async fn test_instr(instr: Instruction) {
13+
let (mut banks_client, payer, recent_blockhash) = ProgramTest::new(
14+
"pyth_client",
15+
id(),
16+
processor!(process_instruction),
17+
)
18+
.start()
19+
.await;
20+
let mut transaction = Transaction::new_with_payer(
21+
&[instr],
22+
Some(&payer.pubkey()),
23+
);
24+
transaction.sign(&[&payer], recent_blockhash);
25+
banks_client.process_transaction(transaction).await.unwrap();
26+
}
27+
28+
fn pc(price: i64, conf: u64, expo: i32) -> PriceConf {
29+
PriceConf {
30+
price: price,
31+
conf: conf,
32+
expo: expo,
33+
}
34+
}
35+
36+
#[tokio::test]
37+
async fn test_noop() {
38+
test_instr(instruction::noop()).await;
39+
}
40+
41+
#[tokio::test]
42+
async fn test_scale_to_exponent_down() {
43+
test_instr(instruction::scale_to_exponent(pc(1, u64::MAX, -1000), 1000)).await
44+
}
45+
46+
#[tokio::test]
47+
async fn test_scale_to_exponent_up() {
48+
test_instr(instruction::scale_to_exponent(pc(1, u64::MAX, 1000), -1000)).await
49+
}
50+
51+
#[tokio::test]
52+
async fn test_scale_to_exponent_best_case() {
53+
test_instr(instruction::scale_to_exponent(pc(1, u64::MAX, 10), 10)).await
54+
}
55+
56+
#[tokio::test]
57+
async fn test_normalize_max_conf() {
58+
test_instr(instruction::normalize(pc(1, u64::MAX, 0))).await
59+
}
60+
61+
#[tokio::test]
62+
async fn test_normalize_max_price() {
63+
test_instr(instruction::normalize(pc(i64::MAX, 1, 0))).await
64+
}
65+
66+
#[tokio::test]
67+
async fn test_normalize_min_price() {
68+
test_instr(instruction::normalize(pc(i64::MIN, 1, 0))).await
69+
}
70+
71+
#[tokio::test]
72+
async fn test_normalize_best_case() {
73+
test_instr(instruction::normalize(pc(1, 1, 0))).await
74+
}
75+
76+
#[tokio::test]
77+
async fn test_div_max_price() {
78+
test_instr(instruction::divide(
79+
pc(i64::MAX, 1, 0),
80+
pc(1, 1, 0)
81+
)).await;
82+
}
83+
84+
#[tokio::test]
85+
async fn test_div_max_price_2() {
86+
test_instr(instruction::divide(
87+
pc(i64::MAX, 1, 0),
88+
pc(i64::MAX, 1, 0)
89+
)).await;
90+
}
91+
92+
#[tokio::test]
93+
async fn test_mul_max_price() {
94+
test_instr(instruction::multiply(
95+
pc(i64::MAX, 1, 2),
96+
pc(123, 1, 2),
97+
)).await;
98+
}
99+
100+
#[tokio::test]
101+
async fn test_mul_max_price_2() {
102+
test_instr(instruction::multiply(
103+
pc(i64::MAX, 1, 2),
104+
pc(i64::MAX, 1, 2),
105+
)).await;
106+
}

tests/integration.rs

Lines changed: 0 additions & 65 deletions
This file was deleted.

0 commit comments

Comments
 (0)