From b713074e8eafa13754aa2a58870b821a54ae1969 Mon Sep 17 00:00:00 2001 From: Mike Dilger Date: Sun, 1 Oct 2023 09:09:17 +1300 Subject: [PATCH] Added a benchmark. --- README.md | 7 +++++ src/tests.rs | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/README.md b/README.md index 360a9fe..5d2b356 100644 --- a/README.md +++ b/README.md @@ -1 +1,8 @@ Nostr NIP-44 encryption code + + +To test and benchmark: + +```` +cargo test --release -- --nocapture +``` diff --git a/src/tests.rs b/src/tests.rs index 9645822..9b8a8d5 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -490,3 +490,78 @@ const PADDING: [(usize, usize); 24] = [ (1020, 1024), (74123, 81920), ]; + +#[test] +fn bench_encryption_inner() { + const SEC1HEX: &'static str = + "dc4b57c5fe856584b01aab34dad7454b0f715bdfab091bf0dbbe12f65c778838"; + const SEC2HEX: &'static str = + "3072ab28ed7d5c2e4f5efbdcde5fb11455ab7f976225d1779a1751eb6400411a"; + + let sec1bytes = hex::decode(SEC1HEX).unwrap(); + let sec1 = SecretKey::from_slice(&sec1bytes).unwrap(); + + let sec2bytes = hex::decode(SEC2HEX).unwrap(); + let sec2 = SecretKey::from_slice(&sec2bytes).unwrap(); + + let (pub2, _) = sec2.x_only_public_key(&SECP256K1); + + let shared = get_conversation_key(sec1, pub2); + + let message = r##" +The compiler could theoretically make optimizations like the following: + + needle and haystack are always the same, move the call to contains outside the loop and delete the loop + Inline contains + needle and haystack have values known at compile time, contains is always true. Remove the call and replace with true + Nothing is done with the result of contains: delete this function call entirely + benchmark now has no purpose: delete this function + +It is not likely that all of the above happens, but the compiler is definitely able to make some optimizations that could result in a very inaccurate benchmark. This is where black_box comes in: + +use std::hint::black_box; + +// Same `contains` function +fn contains(haystack: &[&str], needle: &str) -> bool { + haystack.iter().any(|x| x == &needle) +} + +pub fn benchmark() { + let haystack = vec!["abc", "def", "ghi", "jkl", "mno"]; + let needle = "ghi"; + for _ in 0..10 { + // Adjust our benchmark loop contents + black_box(contains(black_box(&haystack), black_box(needle))); + } +} + +Run + +This essentially tells the compiler to block optimizations across any calls to black_box. So, it now: + + Treats both arguments to contains as unpredictable: the body of contains can no longer be optimized based on argument values + Treats the call to contains and its result as volatile: the body of benchmark cannot optimize this away + +This makes our benchmark much more realistic to how the function would be used in situ, where arguments are usually not known at compile time and the result is used in some way. +"##; + + let start = std::time::Instant::now(); + let rounds = 32768; + for _ in 0..rounds { + std::hint::black_box({ + let encrypted = encrypt(&shared, message).unwrap(); + let _decrypted = decrypt(&shared, &*encrypted).unwrap(); + }); + } + let elapsed = start.elapsed(); + let total_nanos = elapsed.as_nanos(); + let nanos_per_encryption_and_decryption = total_nanos / rounds as u128; + let nanos_per_encryption_and_decryption_per_char = + 10 * nanos_per_encryption_and_decryption / message.len() as u128; + + println!( + "{}.{} nanoseconds per character (encrypt and decrypt)", + nanos_per_encryption_and_decryption_per_char / 10, + nanos_per_encryption_and_decryption_per_char % 10, + ); +}