From bdb68852ca38fc9daca28c57dd11fa83bdf9f56c Mon Sep 17 00:00:00 2001 From: Alexander Weiss Date: Sat, 14 Jan 2023 06:51:48 +0100 Subject: [PATCH 1/2] fix cdc compile errors by integrating cdc --- Cargo.lock | 7 -- Cargo.toml | 2 +- src/cdc/LICENSE.txt | 21 ++++ src/cdc/README.md | 60 +++++++++++ src/cdc/mod.rs | 5 + src/cdc/polynom.rs | 51 ++++++++++ src/cdc/rolling_hash.rs | 219 ++++++++++++++++++++++++++++++++++++++++ src/chunker.rs | 5 +- src/main.rs | 2 + 9 files changed, 362 insertions(+), 10 deletions(-) create mode 100644 src/cdc/LICENSE.txt create mode 100644 src/cdc/README.md create mode 100644 src/cdc/mod.rs create mode 100644 src/cdc/polynom.rs create mode 100644 src/cdc/rolling_hash.rs diff --git a/Cargo.lock b/Cargo.lock index cef1bed45..a681f572a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -211,12 +211,6 @@ dependencies = [ "jobserver", ] -[[package]] -name = "cdc" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f421655f68953d1cae92f72da23d7842679bd413e96735dac14ba1bbdf1c155b" - [[package]] name = "cfg-if" version = "1.0.0" @@ -1719,7 +1713,6 @@ dependencies = [ "bytes", "bytesize", "cachedir", - "cdc", "chrono", "clap", "clap_complete", diff --git a/Cargo.toml b/Cargo.toml index 6c53dc65a..d1ed7cfce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ sha2 = "0.10" rand = "0.8" scrypt = { version = "0.10", default-features = false } # chunker / packer -cdc = "0.1" +# cdc = "0.1" integer-sqrt = "0.1" # serialization base64 = "0.20" diff --git a/src/cdc/LICENSE.txt b/src/cdc/LICENSE.txt new file mode 100644 index 000000000..25d14fae9 --- /dev/null +++ b/src/cdc/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Vincent Cantin (https://github.com/green-coder) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/cdc/README.md b/src/cdc/README.md new file mode 100644 index 000000000..600abff78 --- /dev/null +++ b/src/cdc/README.md @@ -0,0 +1,60 @@ +cdc +======== + +A library for performing *Content-Defined Chunking* (CDC) on data streams. Implemented using generic iterators, very easy to use. + +- [API Documentation](https://docs.rs/cdc/) + +## Example + +```rust + let reader: BufReader = BufReader::new(file); + let byte_iter = reader.bytes().map(|b| b.unwrap()); + + // Finds and iterates on the separators. + for separator in SeparatorIter::new(byte_iter) { + println!("Index: {}, hash: {:016x}", separator.index, separator.hash); + } +``` + +Each module is documented via an example which you can find in the `examples/` folder. + +To run them, use a command like: + + cargo run --example separator --release + +**Note:** Some examples are looking for a file named `myLargeFile.bin` which I didn't upload to Github. Please use your own files for testing. + +## What's in the crate + +From low level to high level: + +* A `RollingHash64` trait, for rolling hash with a 64 bits hash value. + +* `Rabin64`, an implementation of the Rabin Fingerprint rolling hash with a 64 bits hash value. + +* `Separator`, a struct which describes a place in a data stream identified as a separator. + +* `SeparatorIter`, an adaptor which takes an `Iterator` as input and which enumerates all the separators found. + +* `Chunk`, a struct which describes a piece of the data stream (index and size). + +* `ChunkIter`, an adaptor which takes an `Iterator` as input and which enumerates chunks. + +## Implementation details + +* The library is not cutting any files, it only provides information on how to do it. + +* You can change the default window size used by `Rabin64`, and how the `SeparatorIter` is choosing the separator. + +* The design of this crate may be subject to changes sometime in the future. I am waiting for some features of `Rust` to mature up, specially the [`impl Trait`](https://github.com/rust-lang/rust/issues/34511) feature. + +## Performance + +There is a **huge** difference between the debug build and the release build in terms of performance. Remember that when you test the lib, use `cargo run --release`. + +I may try to improve the performance of the lib at some point, but for now it is good enough for most usages. + +## License + +Coded with ❤️ , licensed under the terms of the [MIT license](LICENSE.txt). diff --git a/src/cdc/mod.rs b/src/cdc/mod.rs new file mode 100644 index 000000000..b24d6b98a --- /dev/null +++ b/src/cdc/mod.rs @@ -0,0 +1,5 @@ +mod polynom; +mod rolling_hash; + +pub use polynom::{Polynom, Polynom64}; +pub use rolling_hash::{Rabin64, RollingHash64}; diff --git a/src/cdc/polynom.rs b/src/cdc/polynom.rs new file mode 100644 index 000000000..29726b93b --- /dev/null +++ b/src/cdc/polynom.rs @@ -0,0 +1,51 @@ +// The irreductible polynom to be used in the fingerprint function. +pub trait Polynom { + fn degree(&self) -> i32; + fn modulo(&self, m: &Self) -> Self; +} + +pub type Polynom64 = u64; + +impl Polynom for Polynom64 { + // The degree of the polynom. + fn degree(&self) -> i32 { + 63 - self.leading_zeros() as i32 + } + + fn modulo(&self, m: &Self) -> Self { + let mut p = *self; + while p.degree() >= m.degree() { + p ^= m << (p.degree() - m.degree()); + } + + p + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn polynom_degree() { + assert_eq!(0u64.degree(), -1); + assert_eq!(1u64.degree(), 0); + + assert_eq!(((1u64 << 7) - 1).degree(), 6); + assert_eq!((1u64 << 7).degree(), 7); + assert_eq!(((1u64 << 7) + 1).degree(), 7); + } + + #[test] + fn polynom_modulo() { + assert_eq!(7u64.modulo(&3), 1); + assert_eq!(7u64.modulo(&4), 3); + assert_eq!(7u64.modulo(&2), 1); + + assert_eq!(16u64.modulo(&8), 0); + assert_eq!(19u64.modulo(&8), 3); + + assert_eq!(16u64.modulo(&4), 0); + assert_eq!(19u64.modulo(&4), 3); + } +} diff --git a/src/cdc/rolling_hash.rs b/src/cdc/rolling_hash.rs new file mode 100644 index 000000000..0fbf595e3 --- /dev/null +++ b/src/cdc/rolling_hash.rs @@ -0,0 +1,219 @@ +use super::{Polynom, Polynom64}; + +pub trait RollingHash64 { + fn reset(&mut self); + fn prefill_window(&mut self, iter: &mut I) -> usize + where + I: Iterator; + fn reset_and_prefill_window(&mut self, iter: &mut I) -> usize + where + I: Iterator; + fn slide(&mut self, byte: u8); + fn get_hash(&self) -> &Polynom64; +} + +pub struct Rabin64 { + // Configuration + window_size: usize, // The size of the data window used in the hash calculation. + window_size_mask: usize, // = window_size - 1, supposing that it is an exponent of 2. + + // Precalculations + polynom_shift: i32, + out_table: [Polynom64; 256], + mod_table: [Polynom64; 256], + + // Current state + window_data: Vec, + window_index: usize, + pub hash: Polynom64, +} + + +impl Rabin64 { + pub fn calculate_out_table(window_size: usize, mod_polynom: &Polynom64) -> [Polynom64; 256] { + let mut out_table = [0; 256]; + for (b, elem) in out_table.iter_mut().enumerate() { + let mut hash = (b as Polynom64).modulo(mod_polynom); + for _ in 0..window_size - 1 { + hash <<= 8; + hash = hash.modulo(mod_polynom); + } + *elem = hash; + } + + out_table + } + + pub fn calculate_mod_table(mod_polynom: &Polynom64) -> [Polynom64; 256] { + let mut mod_table = [0; 256]; + let k = mod_polynom.degree(); + for (b, elem) in mod_table.iter_mut().enumerate() { + let p: Polynom64 = (b as Polynom64) << k; + *elem = p.modulo(mod_polynom) | p; + } + + mod_table + } + + pub fn new_with_polynom(window_size_nb_bits: u32, mod_polynom: &Polynom64) -> Rabin64 { + let window_size = 1 << window_size_nb_bits; + + let window_data = vec![0; window_size]; + + Rabin64 { + window_size, + window_size_mask: window_size - 1, + polynom_shift: mod_polynom.degree() - 8, + out_table: Self::calculate_out_table(window_size, mod_polynom), + mod_table: Self::calculate_mod_table(mod_polynom), + window_data, + window_index: 0, + hash: 0, + } + } + + #[cfg(test)] + pub fn hash_block(&mut self, bytes: &[u8], mod_polynom: &Polynom64) { + for v in bytes { + self.hash <<= 8; + self.hash |= *v as Polynom64; + self.hash = self.hash.modulo(&mod_polynom); + } + } +} + +impl RollingHash64 for Rabin64 { + fn reset(&mut self) { + self.window_data.clear(); + self.window_data.resize(self.window_size, 0); + self.window_index = 0; + self.hash = 0; + + // Not needed. + // self.slide(1); + } + + // Attempt to fills the window - 1 byte. + fn prefill_window(&mut self, iter: &mut I) -> usize + where + I: Iterator, + { + let mut nb_bytes_read = 0; + for _ in 0..self.window_size - 1 { + match iter.next() { + Some(b) => { + self.slide(b); + nb_bytes_read += 1; + } + None => break, + } + } + + nb_bytes_read + } + + // Combines a reset with a prefill in an optimized way. + fn reset_and_prefill_window(&mut self, iter: &mut I) -> usize + where + I: Iterator, + { + self.hash = 0; + let mut nb_bytes_read = 0; + for _ in 0..self.window_size - 1 { + match iter.next() { + Some(b) => { + // Take the old value out of the window and the hash. + // ... let's suppose that the buffer contains zeroes, do nothing. + + // Put the new value in the window and in the hash. + self.window_data[self.window_index] = b; + let mod_index = (self.hash >> self.polynom_shift) & 255; + self.hash <<= 8; + self.hash |= b as Polynom64; + self.hash ^= self.mod_table[mod_index as usize]; + + // Move the windowIndex to the next position. + self.window_index = (self.window_index + 1) & self.window_size_mask; + + nb_bytes_read += 1; + } + None => break, + } + } + + // Because we didn't overwrite that element in the loop above. + self.window_data[self.window_index] = 0; + + nb_bytes_read + } + + #[inline] + fn slide(&mut self, byte: u8) { + // Take the old value out of the window and the hash. + let out_value = self.window_data[self.window_index]; + self.hash ^= self.out_table[out_value as usize]; + + // Put the new value in the window and in the hash. + self.window_data[self.window_index] = byte; + let mod_index = (self.hash >> self.polynom_shift) & 255; + self.hash <<= 8; + self.hash |= byte as Polynom64; + self.hash ^= self.mod_table[mod_index as usize]; + + // Move the windowIndex to the next position. + self.window_index = (self.window_index + 1) & self.window_size_mask; + } + + #[inline] + fn get_hash(&self) -> &Polynom64 { + &self.hash + } +} + +#[cfg(test)] +mod tests { + use super::super::polynom::Polynom64; + use super::*; + + fn to_hex_string(polynoms: &[Polynom64], prefix: &str) -> String { + let strs: Vec = polynoms + .iter() + .map(|p| format!("{}{:016x} {}", prefix, p, 0)) + .collect(); + strs.join("\n") + } + + #[test] + fn print_tables() { + let out_table = Rabin64::calculate_out_table(32, &MOD_POLYNOM); + let mod_table = Rabin64::calculate_mod_table(&MOD_POLYNOM); + println!("{}", to_hex_string(&out_table[..], "outTable ")); + println!("{}", to_hex_string(&mod_table[..], "modTable ")); + } + + #[test] + fn rabin_hash() { + use std::cmp::max; + + // Random meaningless data. + let data = [ + 17u8, 28, 53, 64, 175, 216, 27, 208, 109, 130, 143, 35, 93, 244, 45, 18, 64, 193, 204, + 59, 169, 139, 53, 59, 55, 65, 242, 73, 60, 198, 45, 22, 56, 90, 81, 181, + ]; + + let mut rabin1 = Rabin64::new(5); + let mut rabin2 = Rabin64::new(5); + + // Block by block, no optimization, used raw modulo formula. + for i in 0..data.len() { + let block = &data[max(31, i) - 31..i + 1]; + rabin1.reset(); + rabin1.hash_block(block, &MOD_POLYNOM); + + rabin2.slide(data[i]); + + //println!("{:02} {:02} {:016x} {:016x} {:?}", i, block.len(), rabin1.hash, rabin2.hash, block); + assert_eq!(rabin1.hash, rabin2.hash); + } + } +} diff --git a/src/chunker.rs b/src/chunker.rs index 59d79c388..394703fa7 100644 --- a/src/chunker.rs +++ b/src/chunker.rs @@ -1,9 +1,10 @@ use std::io::{self, Read}; use anyhow::{anyhow, Result}; -use cdc::{Polynom, Polynom64, Rabin64, RollingHash64}; use rand::{thread_rng, Rng}; +use crate::cdc::{Polynom, Polynom64, Rabin64, RollingHash64}; + const SPLITMASK: u64 = (1u64 << 20) - 1; const KB: usize = 1024; const MB: usize = 1024 * KB; @@ -115,7 +116,7 @@ impl Iterator for ChunkIter { let byte = self.buf[self.pos]; vec.push(byte); self.pos += 1; - self.rabin.slide(&byte); + self.rabin.slide(byte); } self.size_hint -= vec.len(); Some(Ok(vec)) diff --git a/src/main.rs b/src/main.rs index 2bbea01ba..af8d799d8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -47,6 +47,8 @@ mod index; mod repofile; mod repository; +mod cdc; + fn main() -> Result<()> { // this is a workaround until unix_sigpipe (https://github.com/rust-lang/rust/issues/97889) is available. // See also https://github.com/rust-lang/rust/issues/46016 From f597589c5064212ed074d43e9f8c45aa75ca6106 Mon Sep 17 00:00:00 2001 From: Alexander Weiss Date: Fri, 20 Jan 2023 22:53:43 +0100 Subject: [PATCH 2/2] chunker: Optimizations --- src/cdc/polynom.rs | 20 ++++++------ src/cdc/rolling_hash.rs | 68 +++-------------------------------------- src/chunker.rs | 44 +++++++++++++------------- 3 files changed, 37 insertions(+), 95 deletions(-) diff --git a/src/cdc/polynom.rs b/src/cdc/polynom.rs index 29726b93b..58e85617b 100644 --- a/src/cdc/polynom.rs +++ b/src/cdc/polynom.rs @@ -1,7 +1,7 @@ // The irreductible polynom to be used in the fingerprint function. pub trait Polynom { fn degree(&self) -> i32; - fn modulo(&self, m: &Self) -> Self; + fn modulo(self, m: Self) -> Self; } pub type Polynom64 = u64; @@ -12,8 +12,8 @@ impl Polynom for Polynom64 { 63 - self.leading_zeros() as i32 } - fn modulo(&self, m: &Self) -> Self { - let mut p = *self; + fn modulo(self, m: Self) -> Self { + let mut p = self; while p.degree() >= m.degree() { p ^= m << (p.degree() - m.degree()); } @@ -38,14 +38,14 @@ mod tests { #[test] fn polynom_modulo() { - assert_eq!(7u64.modulo(&3), 1); - assert_eq!(7u64.modulo(&4), 3); - assert_eq!(7u64.modulo(&2), 1); + assert_eq!(7u64.modulo(3), 1); + assert_eq!(7u64.modulo(4), 3); + assert_eq!(7u64.modulo(2), 1); - assert_eq!(16u64.modulo(&8), 0); - assert_eq!(19u64.modulo(&8), 3); + assert_eq!(16u64.modulo(8), 0); + assert_eq!(19u64.modulo(8), 3); - assert_eq!(16u64.modulo(&4), 0); - assert_eq!(19u64.modulo(&4), 3); + assert_eq!(16u64.modulo(4), 0); + assert_eq!(19u64.modulo(4), 3); } } diff --git a/src/cdc/rolling_hash.rs b/src/cdc/rolling_hash.rs index 0fbf595e3..2bf1650f9 100644 --- a/src/cdc/rolling_hash.rs +++ b/src/cdc/rolling_hash.rs @@ -28,9 +28,8 @@ pub struct Rabin64 { pub hash: Polynom64, } - impl Rabin64 { - pub fn calculate_out_table(window_size: usize, mod_polynom: &Polynom64) -> [Polynom64; 256] { + pub fn calculate_out_table(window_size: usize, mod_polynom: Polynom64) -> [Polynom64; 256] { let mut out_table = [0; 256]; for (b, elem) in out_table.iter_mut().enumerate() { let mut hash = (b as Polynom64).modulo(mod_polynom); @@ -44,7 +43,7 @@ impl Rabin64 { out_table } - pub fn calculate_mod_table(mod_polynom: &Polynom64) -> [Polynom64; 256] { + pub fn calculate_mod_table(mod_polynom: Polynom64) -> [Polynom64; 256] { let mut mod_table = [0; 256]; let k = mod_polynom.degree(); for (b, elem) in mod_table.iter_mut().enumerate() { @@ -55,7 +54,7 @@ impl Rabin64 { mod_table } - pub fn new_with_polynom(window_size_nb_bits: u32, mod_polynom: &Polynom64) -> Rabin64 { + pub fn new_with_polynom(window_size_nb_bits: u32, mod_polynom: Polynom64) -> Rabin64 { let window_size = 1 << window_size_nb_bits; let window_data = vec![0; window_size]; @@ -71,15 +70,6 @@ impl Rabin64 { hash: 0, } } - - #[cfg(test)] - pub fn hash_block(&mut self, bytes: &[u8], mod_polynom: &Polynom64) { - for v in bytes { - self.hash <<= 8; - self.hash |= *v as Polynom64; - self.hash = self.hash.modulo(&mod_polynom); - } - } } impl RollingHash64 for Rabin64 { @@ -129,7 +119,7 @@ impl RollingHash64 for Rabin64 { self.window_data[self.window_index] = b; let mod_index = (self.hash >> self.polynom_shift) & 255; self.hash <<= 8; - self.hash |= b as Polynom64; + self.hash |= u64::from(b); self.hash ^= self.mod_table[mod_index as usize]; // Move the windowIndex to the next position. @@ -157,7 +147,7 @@ impl RollingHash64 for Rabin64 { self.window_data[self.window_index] = byte; let mod_index = (self.hash >> self.polynom_shift) & 255; self.hash <<= 8; - self.hash |= byte as Polynom64; + self.hash |= u64::from(byte); self.hash ^= self.mod_table[mod_index as usize]; // Move the windowIndex to the next position. @@ -169,51 +159,3 @@ impl RollingHash64 for Rabin64 { &self.hash } } - -#[cfg(test)] -mod tests { - use super::super::polynom::Polynom64; - use super::*; - - fn to_hex_string(polynoms: &[Polynom64], prefix: &str) -> String { - let strs: Vec = polynoms - .iter() - .map(|p| format!("{}{:016x} {}", prefix, p, 0)) - .collect(); - strs.join("\n") - } - - #[test] - fn print_tables() { - let out_table = Rabin64::calculate_out_table(32, &MOD_POLYNOM); - let mod_table = Rabin64::calculate_mod_table(&MOD_POLYNOM); - println!("{}", to_hex_string(&out_table[..], "outTable ")); - println!("{}", to_hex_string(&mod_table[..], "modTable ")); - } - - #[test] - fn rabin_hash() { - use std::cmp::max; - - // Random meaningless data. - let data = [ - 17u8, 28, 53, 64, 175, 216, 27, 208, 109, 130, 143, 35, 93, 244, 45, 18, 64, 193, 204, - 59, 169, 139, 53, 59, 55, 65, 242, 73, 60, 198, 45, 22, 56, 90, 81, 181, - ]; - - let mut rabin1 = Rabin64::new(5); - let mut rabin2 = Rabin64::new(5); - - // Block by block, no optimization, used raw modulo formula. - for i in 0..data.len() { - let block = &data[max(31, i) - 31..i + 1]; - rabin1.reset(); - rabin1.hash_block(block, &MOD_POLYNOM); - - rabin2.slide(data[i]); - - //println!("{:02} {:02} {:016x} {:016x} {:?}", i, block.len(), rabin1.hash, rabin2.hash, block); - assert_eq!(rabin1.hash, rabin2.hash); - } - } -} diff --git a/src/chunker.rs b/src/chunker.rs index 394703fa7..6e843f8d7 100644 --- a/src/chunker.rs +++ b/src/chunker.rs @@ -36,7 +36,7 @@ impl ChunkIter { pos: 0, reader, predicate: default_predicate, - rabin: Rabin64::new_with_polynom(6, &poly), + rabin: Rabin64::new_with_polynom(6, poly), size_hint, // size hint is used to optimize memory allocation; this should be an upper bound on the size min_size: MIN_SIZE, max_size: MAX_SIZE, @@ -151,9 +151,9 @@ pub fn random_poly() -> Result { trait PolynomExtend { fn irreducible(&self) -> bool; - fn gcd(&self, other: &Self) -> Self; - fn add(&self, other: &Self) -> Self; - fn mulmod(&self, other: &Self, modulo: &Self) -> Self; + fn gcd(self, other: Self) -> Self; + fn add(self, other: Self) -> Self; + fn mulmod(self, other: Self, modulo: Self) -> Self; } // implementation goes along the lines of @@ -166,51 +166,51 @@ impl PolynomExtend for Polynom64 { // Finite Fields". fn irreducible(&self) -> bool { for i in 1..=self.degree() / 2 { - if self.gcd(&qp(i, *self)) != 1 { + if self.gcd(qp(i, *self)) != 1 { return false; } } true } - fn gcd(&self, other: &Self) -> Self { - if other == &0 { - return *self; + fn gcd(self, other: Self) -> Self { + if other == 0 { + return self; } - if self == &0 { - return *other; + if self == 0 { + return other; } if self.degree() < other.degree() { - self.gcd(&other.modulo(self)) + self.gcd(other.modulo(self)) } else { - other.gcd(&self.modulo(other)) + other.gcd(self.modulo(other)) } } - fn add(&self, other: &Self) -> Self { - *self ^ *other + fn add(self, other: Self) -> Self { + self ^ other } - fn mulmod(&self, other: &Self, modulo: &Self) -> Self { - if self == &0 || other == &0 { + fn mulmod(self, other: Self, modulo: Self) -> Self { + if self == 0 || other == 0 { return 0; } let mut res: Polynom64 = 0; - let mut a = *self; - let mut b = *other; + let mut a = self; + let mut b = other; if b & 1 > 0 { - res = res.add(&a).modulo(modulo); + res = res.add(a).modulo(modulo); } while b != 0 { a = (a << 1).modulo(modulo); b >>= 1; if b & 1 > 0 { - res = res.add(&a).modulo(modulo); + res = res.add(a).modulo(modulo); } } @@ -226,11 +226,11 @@ fn qp(p: i32, g: Polynom64) -> Polynom64 { for _ in 0..p { // repeatedly square res - res = res.mulmod(&res, &g); + res = res.mulmod(res, g); } // add x - res.add(&2).modulo(&g) + res.add(2).modulo(g) } #[cfg(test)]