Skip to main content
FHE operations are computationally expensive. This guide covers optimization strategies.

Operation Costs

OperationRelative Cost
Addition1x
Subtraction1x
Multiplication2-3x
Comparison5-10x
Min/Max5-10x
Select5-10x

Key Strategies

1. Minimize Comparisons

Comparisons are expensive. Batch when possible:
// Bad: Multiple comparisons
let a_gt_b = a.gt(&b)?;
let b_gt_c = b.gt(&c)?;
let c_gt_d = c.gt(&d)?;

// Better: Rethink the algorithm to minimize comparisons

2. Use Smallest Types

// Good: u8 for small values
let price: Encrypted<u8> = price_ref.load()?;  // ~10KB ciphertext

// Avoid: u64 when not needed
let price: Encrypted<u64> = price_ref.load()?;  // ~80KB ciphertext

3. Load Once, Use Multiple Times

// Good
let price = price_ref.load()?;
let result1 = price.add(&a)?;
let result2 = price.add(&b)?;

// Bad
let result1 = price_ref.load()?.add(&a)?;
let result2 = price_ref.load()?.add(&b)?; // Extra load!

4. Batch Arithmetic Before Comparisons

// Good: All arithmetic first
let sum1 = a.add(&b)?;
let sum2 = c.add(&d)?;
let comparison = sum1.ge(&sum2)?;

// Avoid: Interleaving
let comparison1 = a.ge(&b)?;
let sum = c.add(&d)?;
let comparison2 = sum.ge(&e)?;

5. Use Built-in Min/Max

// Good
let min = a.min(&b)?;

// Bad
let is_smaller = a.lt(&b)?;
let min = is_smaller.select(&a, &b)?;

6. Store Results at End

// Good: Single store
let result = a.add(&b)?.mul(&c)?;
let result_ref = result.store()?;

// Avoid: Multiple stores
let sum = a.add(&b)?;
let sum_ref = sum.store()?;  // Unnecessary intermediate store

Memory Optimization

Account Design

// Good: Minimal on-chain storage
pub struct Order {
    pub owner: Pubkey,           // 32 bytes
    pub price_ref: EncryptedRef<u8>,  // 32 bytes (not 10KB!)
    pub qty_ref: EncryptedRef<u8>,    // 32 bytes
}
// Total: 96 bytes

// Bad: Storing raw ciphertexts
pub struct Order {
    pub price_data: [u8; 10000],  // 10KB on-chain!
}

Heap Usage

Monitor heap usage for complex programs:
// Default: ~1MB heap
privora_sdk_program::setup_fhe_allocator!();

// If needed: larger heap
privora_sdk_program::setup_fhe_allocator!(2 * 1024 * 1024);

Algorithm Optimization

Avoid Unnecessary Comparisons

// Instead of checking both conditions
let a_ge_b = a.ge(&b)?;
let a_lt_b = a.lt(&b)?;  // Redundant!

// Use one comparison and derive
let a_ge_b = a.ge(&b)?;
// a_lt_b is the logical negation

Precompute When Possible

If values are known at instruction time, compute before encryption:
// If computing: encrypted_a + plaintext_constant
// Consider: encrypt(plaintext_a + plaintext_constant) on client
// Then send single encrypted value

Benchmarking

In tests, measure operation counts:
#[test]
fn benchmark_operations() {
    let mut env = FheTestEnv::new();

    let start = std::time::Instant::now();

    // Perform operations...

    println!("Time: {:?}", start.elapsed());
}

Summary

  1. Minimize comparisons - they’re 5-10x more expensive
  2. Use smallest types - smaller ciphertexts, faster operations
  3. Load once - avoid redundant fetches
  4. Batch arithmetic - do all arithmetic before comparisons
  5. Store at end - minimize data store writes