Skip to content

Commit

Permalink
Restructure and update
Browse files Browse the repository at this point in the history
Break out tests and prime wheel generator to separate files.

Upgrade to latest dependencies, except for clap.

Improve section about memory efficiency in the README.
  • Loading branch information
Fairglow committed Nov 6, 2023
1 parent 8e8e37c commit f9c72ed
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 108 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
Cargo.lock
target/
.idea/
18 changes: 9 additions & 9 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "prime-factor"
description = "A prime number factorizer written in Rust"
version = "0.4.3"
version = "0.4.4"
authors = ["Stefan Lindblad <[email protected]>"]
license = "LGPL-2.1-or-later"
edition = "2021"
Expand All @@ -23,16 +23,16 @@ path = "benches/benchmark.rs"
harness = false

[dependencies]
clap = "2.33.3"
log = "0.4.13"
stderrlog = "0.5.1"
genawaiter = "0.99.1"
rayon = "1.5.1"
clap = "2.34"
log = "0.4"
stderrlog = "0.5"
genawaiter = "0.99"
rayon = "1.8"

[dev-dependencies]
criterion = "0.3.5"
rand = { version = "0.8.3", features = ["small_rng"] }
reikna = "0.12.3"
criterion = "0.5"
rand = { version = "0.8", features = ["small_rng"] }
reikna = "0.12"

[profile.release]
codegen-units = 1
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ The library will calculate all the prime number factors of any 128-bit unsigned

## Memory efficiency

Main memory accesses are typically quite slow[^1] and a lot of the algorithms for calculating prime numbers use quite a lot of memory, the cache helps but not enough. During a single main memory load there is time for in the order of hundreds of calculations, which will be wasted cycles unless we can utilize them somehow. This means that we can get an efficient algorithm even with a bit of wasted computations if we can limit the number of memory operations.
A lot of prime number algorithms require a significant amount of memory, but accessing main memory can be a slow process[^1]. While the cache can provide some assistance, it may not be sufficient. With each load from main memory, there is typically enough time for up to hundreds of calculations. These cycles would be wasted, unless we can find some work to do while waiting for the load. Therefore, even with some amount of wasted computations, we can still achieve an efficient algorithm if we can minimize the number of memory operations.

In my personal experience any algorithm that needs to store a lot of data will be limited by the memory accesses, it is often faster to recreate some computations rather than loading them from memory. Therefore do not save values to memory that can be easily computed.

Expand Down
20 changes: 20 additions & 0 deletions src/candidates.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#![deny(unsafe_code)]
#![allow(dead_code)]
use genawaiter::yield_;
use genawaiter::stack::producer_fn;

/// Wheel factorization algorithm with base {2, 3, 5}
/// https://en.wikipedia.org/wiki/Wheel_factorization
#[producer_fn(u128)]
pub async fn prime_wheel_30() {
yield_!(2);
yield_!(3);
yield_!(5);
let mut base = 0u128;
loop {
for n in [7, 11, 13, 17, 19, 23, 29, 31] {
yield_!(base + n);
}
base += 30;
}
}
88 changes: 5 additions & 83 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,11 @@
//! Module for factorizing integers
pub mod candidates;

use std::cmp::{min, Ordering};
use std::convert::From;
use std::fmt;
use genawaiter::yield_;
use genawaiter::stack::{let_gen_using, producer_fn};

/// Wheel factorization algorithm with base {2, 3, 5}
/// https://en.wikipedia.org/wiki/Wheel_factorization
#[producer_fn(u128)]
async fn prime_wheel_30() {
yield_!(2);
yield_!(3);
yield_!(5);
let mut base = 0u128;
loop {
for n in [7, 11, 13, 17, 19, 23, 29, 31] {
yield_!(base + n);
}
base += 30;
}
}
use genawaiter::stack::let_gen_using;
use candidates::prime_wheel_30;

#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct IntFactor {
Expand Down Expand Up @@ -50,7 +36,7 @@ pub struct PrimeFactors {

impl PrimeFactors {
fn new() -> Self {
PrimeFactors { factors: Vec::new() }
PrimeFactors { factors: Vec::with_capacity(8) }
}
fn add(&mut self, integer: u128, exponent: u32) {
self.factors.push(IntFactor { integer, exponent })
Expand Down Expand Up @@ -243,67 +229,3 @@ pub fn u128_lcm(this: u128, that: u128) -> u128 {
let gcd = u128_gcd(this, that);
this * that / gcd
}

#[cfg(test)]
mod tests {
#[allow(unused_imports)]
use crate::*;
use rand::Rng;
use genawaiter::stack::let_gen_using;

#[test]
fn test_early_prime_wheel_numbers() {
let testvec = vec![
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 49, 53, 59,
61, 67, 71, 73, 77, 79, 83, 89, 91, 97, 101, 103, 107, 109, 113
];
let_gen_using!(mpgen, prime_wheel_30);
let mut mp = mpgen.into_iter();
for i in 0..testvec.len() {
let p = mp.next().unwrap();
assert_eq!(testvec[i], p);
}
}

#[test]
fn test_prime_wheel_quality() {
let mut primes: u128 = 0;
let mut others: u128 = 0;
let_gen_using!(mpgen, prime_wheel_30);
let mut mp = mpgen.into_iter();
for _ in 0..1000000 {
let p = mp.next().unwrap();
if u128_is_prime(p) {
primes += 1;
} else {
others += 1;
}
}
let percent = primes as f64 / (primes + others) as f64 * 100.0;
println!("Prime wheel generated {}/{} ({:.3}%) primes",
primes, primes+others, percent);
assert!(percent > 25.0);
}

#[test]
fn test_int_sqrt_pow_of_2() {
let mut rnd = rand::thread_rng();
for _ in 1..1000 {
let n = rnd.gen_range(1..u128_sqrt(u128::MAX));
let sqrt = u128_sqrt(n.pow(2));
assert_eq!(sqrt, n);
}
}

#[test]
fn test_int_sqrt_floor() {
let mut rnd = rand::thread_rng();
for _ in 1..1000 {
// Largest integer in a f64 is 2^53-1 (52 bits mantissa)
let n = rnd.gen_range(1..u64::pow(2, 53) as u128);
let expt = f64::sqrt(n as f64) as u128;
let sqrt = u128_sqrt(n);
assert_eq!(sqrt, expt);
}
}
}
99 changes: 84 additions & 15 deletions tests/test.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,87 @@
#[allow(unused_imports)]
use primefactor::*;
use rand::Rng;
use rayon::prelude::*;
use reikna;

use genawaiter::stack::let_gen_using;
use primefactor::{
candidates::prime_wheel_30,
primefactor_gcd,
PrimeFactors,
u128_gcd,
u128_is_prime,
u128_lcm,
u128_sqrt};

#[test]
fn test_early_prime_wheel_numbers() {
let testvec = vec![
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 49, 53, 59,
61, 67, 71, 73, 77, 79, 83, 89, 91, 97, 101, 103, 107, 109, 113
];
let_gen_using!(mpgen, prime_wheel_30);
let mut mp = mpgen.into_iter();
for i in 0..testvec.len() {
let p = mp.next().unwrap();
assert_eq!(testvec[i], p);
}
}

#[test]
fn test_early_prime_candidate_numbers() {
let testvec = vec![
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 49, 53, 59,
61, 67, 71, 73, 77, 79, 83, 89, 91, 97, 101, 103, 107, 109, 113
];
let_gen_using!(mpgen, prime_wheel_30);
let mut mp = mpgen.into_iter();
for i in 0..testvec.len() {
let p = mp.next().unwrap();
assert_eq!(testvec[i], p);
}
}

#[test]
fn test_prime_wheel_quality() {
let mut primes: u128 = 0;
let mut others: u128 = 0;
let_gen_using!(mpgen, prime_wheel_30);
let mut mp = mpgen.into_iter();
for _ in 0..1000000 {
let p = mp.next().unwrap();
if u128_is_prime(p) {
primes += 1;
} else {
others += 1;
}
}
let percent = primes as f64 / (primes + others) as f64 * 100.0;
println!("Prime wheel generated {}/{} ({:.3}%) primes",
primes, primes+others, percent);
assert!(percent > 25.0);
}

#[test]
fn test_int_sqrt_pow_of_2() {
let mut rnd = rand::thread_rng();
for _ in 1..1000 {
let n = rnd.gen_range(1..u128_sqrt(u128::MAX));
let sqrt = u128_sqrt(n.pow(2));
assert_eq!(sqrt, n);
}
}

#[test]
fn test_int_sqrt_floor() {
let mut rnd = rand::thread_rng();
for _ in 1..1000 {
// Largest integer in a f64 is 2^53-1 (52 bits mantissa)
let n = rnd.gen_range(1..u64::pow(2, 53) as u128);
let expt = f64::sqrt(n as f64) as u128;
let sqrt = u128_sqrt(n);
assert_eq!(sqrt, expt);
}
}

#[test]
fn test_is_prime() {
for num in 2..=1000 {
Expand Down Expand Up @@ -142,18 +220,9 @@ fn find_highest_32bit_prime() {
assert_eq!(found, 4294967291);
}


#[test]
fn find_highest_64bit_prime() {
(0..60).into_par_iter().for_each(|n| {
let num: u128 = (u64::MAX - n) as u128;
match n {
58 => {
assert_eq!(num, 18446744073709551557);
assert_eq!(u128_is_prime(num), true);
println!("#{}: {} is a prime number", n, num);
},
_ => assert_eq!(u128_is_prime(num), false),
}
});
fn worst_case_64bit_prime() {
let num: u128 = (u64::MAX - 58) as u128;
assert_eq!(num, 18446744073709551557);
assert_eq!(u128_is_prime(num), true);
}

0 comments on commit f9c72ed

Please sign in to comment.