From da7ab076f2c3bf7589ac6eb58f0c36d148e10f51 Mon Sep 17 00:00:00 2001 From: javalsai Date: Fri, 5 Dec 2025 22:11:15 +0100 Subject: [PATCH] add: d05 & debug utils - includes a script to generate a 2GB big challenge - added utils to internally time functions with minimal overhead - add functionality to tester.rs --- 2025/05/gen.py | 22 +++++ 2025/05/p1-opt.rs | 218 +++++++++++++++++++++++++++++++++++++++++++++ 2025/05/p1-opt3.rs | 88 ++++++++++++++++++ 2025/05/p1.rs | 51 +++++++++++ 2025/05/p2-hu.rs | 40 +++++++++ 2025/05/p2.rs | 35 ++++++++ 2025/tester.rs | 60 +++++++++---- 7 files changed, 497 insertions(+), 17 deletions(-) create mode 100644 2025/05/gen.py create mode 100644 2025/05/p1-opt.rs create mode 100644 2025/05/p1-opt3.rs create mode 100644 2025/05/p1.rs create mode 100644 2025/05/p2-hu.rs create mode 100644 2025/05/p2.rs diff --git a/2025/05/gen.py b/2025/05/gen.py new file mode 100644 index 0000000..d516dca --- /dev/null +++ b/2025/05/gen.py @@ -0,0 +1,22 @@ +import random + +MAX=(2**64)-1 +RANGES=1000000 +IDS=100000000 + +list_of_ids = [] + +with open('2025-huge.txt', 'a') as file: + for x in range(1000): + num1 = random.randint(1, MAX) + num2 = random.randint(1, MAX) + if num1 > num2: + tmp = num1 + num1 = num2 + num2 = tmp + file.write(f'{num1}-{num2}\n') + file.write('\n') + for x in range(IDS): + num = random.randint(2, MAX-1) + if not (num in list_of_ids): + file.write(f'{num}\n') diff --git a/2025/05/p1-opt.rs b/2025/05/p1-opt.rs new file mode 100644 index 0000000..8dd1b40 --- /dev/null +++ b/2025/05/p1-opt.rs @@ -0,0 +1,218 @@ +#[unsafe(no_mangle)] +pub static mut TIMERS: [(&str, Duration); TIMERS_LEN] = [ + ("insert", Duration::ZERO), + ("count", Duration::ZERO), +]; + +#[unsafe(no_mangle)] +pub static TIMERS_LEN: usize = 2; +use std::{ops::RangeInclusive, time::{Duration, Instant}}; + +pub type RangeU = u128; + +pub struct BIntervalNode { + value: RangeInclusive, + len: usize, + lt: Option>>, + gt: Option>>, +} + +impl BIntervalNode { + pub fn new(value: RangeInclusive) -> Self { + Self { + value, + len: 1, + lt: None, + gt: None, + } + } + + fn _is_gt_non_overlapping(lhs: &RangeInclusive, rhs: &RangeInclusive) -> bool { + rhs.start() > lhs.start() + } + + pub fn _overlap_with(&self, value: &RangeInclusive) -> RangeInclusive { + (*self.value.start().min(value.start()))..=(*self.value.end().max(value.end())) + } + + pub fn _overlap(&self, value: &RangeInclusive) -> bool { + (value.start() <= self.value.start() && value.end() >= self.value.start()) + || (value.end() >= self.value.end() && value.start() <= self.value.end()) + } + + pub fn _get_which(&mut self, which: bool) -> &mut Option> { + if which { &mut self.gt } else { &mut self.lt } + } + + /// See [`Self::_swap_ptrs_with()`] for the ptr swap, now lenths. + /// + /// self self + /// (A) (B) + /// (B) C --> D (A) + /// D E E C + /// + /// stays the same: C, D, E + /// A = B + C; B = D + E (C = A - B) + /// A = E + C; B = D + A + /// + /// B' = B - E + A' = A + /// A' = A - B + E + pub fn _swap_with(self: &mut Box, which: bool) { + unsafe fn copy_mut<'a, T>(mutref: *mut T) -> &'a mut T { + unsafe { &mut *mutref } + } + + let total_len = self.len; + let e_len = self._swap_ptrs_with(which); + // SAFETY: its different fields + let sub = unsafe { copy_mut(self._get_which(which).as_mut().unwrap()) }; + + self.len -= sub.len + e_len; + sub.len = total_len; + } + + /// True if the gt one, false if lt + /// + /// self self + /// (A) (B) + /// a b x y + /// (B) C --> D (A) + /// x y a b + /// D E E C + /// + /// `self` here is the **pointer to the pointer** to the root element + /// a = &E (y) + /// y = &A (self) + /// self = B (a) + /// + /// also returns `E`s length if existent or 0 + pub fn _swap_ptrs_with(self: &mut Box, which: bool) -> usize { + unsafe fn copy(optbox: &T) -> T { + unsafe { std::mem::transmute_copy::<_, T>(optbox) } + } + unsafe fn copy_mut<'a, T>(mutref: *mut T) -> &'a mut T { + unsafe { &mut *mutref } + } + + // SAFETY: we clone the mut ptr to separate their lifetime relation, its fine as long as we + // dont walk into the `&mut Option<...>`s (`lt` and `gt` field ptrs) and treat it only as + // the node field's ptr to be updated + let my_sub_field_ptr = unsafe { copy_mut(self._get_which(which)) }; // a + let my_sub_ptr = unsafe { copy_mut(my_sub_field_ptr.as_mut().unwrap()) }; // B + let my_subs_other_sub_field_ptr = unsafe { copy_mut(my_sub_ptr._get_which(!which)) }; // y + + // SAFETY: Copied because it can't move out as the original location must remain valid. + // However this is a triangular rotation and the final place will end up being replaced + // too, so this is safe. + *my_sub_field_ptr = unsafe { copy(my_subs_other_sub_field_ptr) }; + *my_subs_other_sub_field_ptr = Some(unsafe { copy(self) }); + *self = unsafe { copy(my_sub_ptr) }; + + my_sub_field_ptr.as_ref().map(|n| n.len).unwrap_or(0) + } + + /// Returns `true` if the len count has grown somewhere down the line + pub fn _insert(self: &mut Box, value: RangeInclusive) -> bool { + if self._overlap(&value) { + self.value = self._overlap_with(&value); + return false; + } + + let is_gt = Self::_is_gt_non_overlapping(&self.value, &value); + let next_node: &mut _ = if is_gt { &mut self.gt } else { &mut self.lt }; + + if let Some(next_node) = next_node { + let inserted = next_node._insert(value); + if inserted { + self.len += 1; + } + + // first of all, do we balance? + // maybe >= 2 will balance more but itd be too unstable, TODO play with constant + if (self.len + 1) / (next_node.len + 1) > 2 { + // self._swap_with(is_gt); + } + + inserted + } else { + *next_node = Some(Box::new(Self::new(value))); + self.len += 1; + true + } + } + + pub fn insert(self: &mut Box, value: RangeInclusive) { + self._insert(value); + } + + pub fn contains(&self, value: &T) -> bool { + if self.value.start() > value { + self.lt.as_ref().is_some_and(|lt| lt.contains(value)) + } else if self.value.end() < value { + self.gt.as_ref().is_some_and(|gt| gt.contains(value)) + } else { + true + } + } +} + +struct BIntervalTree { + first_node: Box>, +} + +impl BIntervalTree { + fn insert(&mut self, value: RangeInclusive) { + self.first_node.insert(value); + } + + fn contains(&self, value: &T) -> bool { + self.first_node.contains(value) + } +} + +impl FromIterator> for BIntervalTree { + /// # Panic + /// + /// Must not be an empty iterator (this is not design I just want perf on this) + fn from_iter>>(iter: T) -> Self { + let mut iter = iter.into_iter(); + let mut myself = Self { + first_node: Box::new(BIntervalNode::new(iter.next().unwrap())), + }; + + for range in iter { + myself.insert(range); + } + myself + } +} + +#[unsafe(no_mangle)] +extern "Rust" fn challenge_t_usize(buf: &[u8], t: &Instant) -> usize { + let s = unsafe { str::from_utf8_unchecked(buf) }; + let mut lines = s.lines(); + + let mut count = 0; + + let ran_iter = (&mut lines).take_while(|ln| !ln.is_empty()).map(|range| { + let (l, r) = range.split_once('-').unwrap(); + let (l, r) = (l.parse().unwrap(), r.parse().unwrap()); + l..=r + }); + + let ranges = BIntervalTree::::from_iter(ran_iter); + + unsafe { TIMERS[0].1 = t.elapsed() }; + + for id in lines { + let id: RangeU = id.parse().unwrap(); + + if ranges.contains(&id) { + count += 1; + } + } + + unsafe { TIMERS[1].1 = t.elapsed() }; + + count +} diff --git a/2025/05/p1-opt3.rs b/2025/05/p1-opt3.rs new file mode 100644 index 0000000..5fdfce7 --- /dev/null +++ b/2025/05/p1-opt3.rs @@ -0,0 +1,88 @@ +use std::{ + collections::BTreeMap, + time::{Duration, Instant}, +}; + +pub type RangeU = u128; +pub type NestingCountT = i16; + +#[unsafe(no_mangle)] +pub static mut TIMERS: [(&str, Duration); TIMERS_LEN] = [ + ("after-parse", Duration::ZERO), + ("flatten-vec", Duration::ZERO), + ("count-ids", Duration::ZERO), +]; +#[unsafe(no_mangle)] +pub static TIMERS_LEN: usize = 3; + +#[unsafe(no_mangle)] +extern "Rust" fn challenge_t_usize(buf: &[u8], t: &Instant) -> usize { + let mut count = 0; + let s = unsafe { str::from_utf8_unchecked(buf) }; + let mut lines = s.lines(); + + let mut bmap = BTreeMap::<_, NestingCountT>::new(); + + (&mut lines) + .take_while(|ln| !ln.is_empty()) + .for_each(|range| { + let (l, r) = range.split_once('-').unwrap(); + let (l, r) = (l.parse().unwrap(), r.parse().unwrap()); + + bmap.entry(l).and_modify(|k| *k += 1).or_insert(1); + bmap.entry(r).and_modify(|k| *k -= 1).or_insert(-1); + }); + + unsafe { TIMERS[0].1 = t.elapsed() }; + + let mut arr = Vec::with_capacity(size_of::() * bmap.len()); + // I could directly search on the btree, but I feel flattening at once and then searching can + // speed up things bcs continuity indirection and stuff. It also makes it easier to iterate at + // once. This would be O(n) and the latter access in O(log n) (O(n log n) bcs it iterates for + // each id) + let mut last_pushed = false; + let mut nested_ran_acc = 0; + for (idx, nesting) in bmap { + nested_ran_acc += nesting; + if last_pushed != (nested_ran_acc > 0) { + last_pushed = !last_pushed; + arr.push((idx, last_pushed)); + } + } + + unsafe { TIMERS[1].1 = t.elapsed() }; + + let all_ranges_bounds = (arr[0].0, arr[arr.len() - 1].0); + for id in lines { + let id: RangeU = id.parse().unwrap(); + if id < all_ranges_bounds.0 || id > all_ranges_bounds.1 { + continue; + } + + // let idx = arr.binary_search_by_key(&id, |p| p.0).err(|i| i); + // let is_in_any_range = arr.get(idx).map(|p| p.1).unwrap(); + let is_in_any_range = binary_search_inclusive_or_lower_bound(&arr, id); + + if is_in_any_range { + count += 1 + } + } + + unsafe { TIMERS[2].1 = t.elapsed() }; + + count +} + +fn binary_search_inclusive_or_lower_bound(arr: &[(RangeU, bool)], id: RangeU) -> bool { + let all_ranges_bounds = (arr[0].0, arr[arr.len() - 1].0); + + if id < all_ranges_bounds.0 || id > all_ranges_bounds.1 { + return false; + } + + match arr.binary_search_by_key(&id, |p| p.0) { + Ok(_) => true, // the rising edge is true and if falling edge its false but included so + // should get truthed. In general if its found it was an edge so return true + Err(idx) => arr[idx - 1].1, + } +} diff --git a/2025/05/p1.rs b/2025/05/p1.rs new file mode 100644 index 0000000..c254a62 --- /dev/null +++ b/2025/05/p1.rs @@ -0,0 +1,51 @@ +use std::time::{Duration, Instant}; + +#[unsafe(no_mangle)] +pub static mut TIMERS: [(&str, Duration); TIMERS_LEN] = [ + ("after-parse", Duration::ZERO), + ("sort", Duration::ZERO), + ("count-ids", Duration::ZERO), +]; +#[unsafe(no_mangle)] +pub static TIMERS_LEN: usize = 3; + +pub type RangeU = u128; + +#[unsafe(no_mangle)] +extern "Rust" fn challenge_t_usize(buf: &[u8], t: &Instant) -> usize { + let s = unsafe { str::from_utf8_unchecked(buf) }; + let mut lines = s.lines(); + + let mut count = 0; + + let mut ranges: Vec<(RangeU, RangeU)> = Vec::new(); + + for range in &mut lines { + if range.is_empty() { + break; + } + + let (l, r) = range.split_once('-').unwrap(); + let (l, r) = (l.parse().unwrap(), r.parse().unwrap()); + + ranges.push((l, r)); + } + + unsafe { TIMERS[0].1 = t.elapsed() }; + + ranges.sort_by(|a, b| a.0.cmp(&b.0)); + + unsafe { TIMERS[1].1 = t.elapsed() }; + + for id in lines { + let id: RangeU = id.parse().unwrap(); + + if ranges.iter().any(|&(l, r)| (l..=r).contains(&id)) { + count += 1; + } + } + + unsafe { TIMERS[2].1 = t.elapsed() }; + + count +} diff --git a/2025/05/p2-hu.rs b/2025/05/p2-hu.rs new file mode 100644 index 0000000..8e8ce47 --- /dev/null +++ b/2025/05/p2-hu.rs @@ -0,0 +1,40 @@ +pub type RangeU = usize; + +#[unsafe(no_mangle)] +extern "Rust" fn challenge_usize(buf: &[u8]) -> usize { + let s = unsafe { str::from_utf8_unchecked(buf) }; + let mut lines = s.lines(); + + let mut count = 0; + + let mut ranges: Vec<(RangeU, RangeU)> = Vec::new(); + + for range in &mut lines { + if range.is_empty() { + break; + } + + let (l, r) = range.split_once('-').unwrap(); + let (l, r) = (l.parse().unwrap(), r.parse().unwrap()); + + // overlapping here means overlapping or adyacent + count += r - l + 1; + let overlapping: Option<&mut (RangeU, RangeU)> = None; + for (r_l, r_r) in ranges.iter_mut() { + if l <= *r_r && r >= *r_r { + *r_r = r; + // + } + // (|(l, r)| (l..=r).contains(&fresh)) + } + for fresh in l..=r { + println!("{fresh:?}"); + if ranges.iter().any(|&(l, r)| (l..=r).contains(&fresh)) { + count -= 1; + } + } + ranges.push((l, r)); + } + + count +} diff --git a/2025/05/p2.rs b/2025/05/p2.rs new file mode 100644 index 0000000..9379c30 --- /dev/null +++ b/2025/05/p2.rs @@ -0,0 +1,35 @@ +pub type RangeU = usize; + +#[unsafe(no_mangle)] +extern "Rust" fn challenge_usize(buf: &[u8]) -> usize { + let s = unsafe { str::from_utf8_unchecked(buf) }; + let mut lines = s.lines(); + + let mut ranges: Vec<(RangeU, RangeU)> = Vec::new(); + + for range in &mut lines { + if range.is_empty() { + break; + } + + let (l, r) = range.split_once('-').unwrap(); + let (l, r) = (l.parse().unwrap(), r.parse().unwrap()); + + ranges.push((l, r)); + } + + ranges.sort_by(|a, b| a.0.cmp(&b.0)); + ranges + .into_iter() + .fold((0, 0), |(count, rightmost), (l, r)| { + // dbg!((rightmost, r)); + if rightmost >= r { + (count, rightmost) + } else if rightmost >= l { + let overlap = rightmost - l + 1; + (count + r - l - overlap + 1, rightmost.max(r)) + } else { + (count + r - l + 1, r) + } + }).0 +} diff --git a/2025/tester.rs b/2025/tester.rs index b53bb03..48dd0e1 100644 --- a/2025/tester.rs +++ b/2025/tester.rs @@ -7,7 +7,7 @@ use std::{ io::{self, Read}, path::{Path, PathBuf}, process::{Command, Stdio}, - time::{self, Instant}, + time::{self, Duration, Instant}, }; pub mod dl { @@ -268,42 +268,68 @@ pub type ChallengeFn = unsafe extern "Rust" fn(&[u8]) -> isize; fn main() -> io::Result<()> { let rustc = env::var("RUSTC_PATH").ok(); - let Ok([day, input]) = std::env::args().skip(1).next_chunk() else { - eprintln!("\x1b[1;31mUsage: $0 "); + let args = std::env::args().skip(1).collect::>(); + if args.len() < 2 { + eprintln!("\x1b[1;31mUsage: $0 "); return Ok(()); }; + let [days @ .., input] = &args[..] else { + unreachable!("already checked arg count before"); + }; + let (noop_overhead_cold, noop_overhead_hot) = measure_noop_overhead(); info!( "noop fn takes {:#?} hot and {:#?} cold", noop_overhead_hot, noop_overhead_cold ); + println!("\x1b[33m prog: buferring input...\x1b[0m"); + let input = buffer_input(input)?; + if days.len() == 1 { + println!(); + run_input(&days[0], &input, rustc.as_deref()) + } else { + for day in days { + println!(); + println!("\x1b[1;3;4;41m{day}\x1b[0m:"); + run_input(day, &input, rustc.as_deref())?; + } + + Ok(()) + } +} + +fn run_input(day: &str, input: &[u8], rustc: Option<&str>) -> io::Result<()> { let rs_path = PathBuf::from(&day); assert_eq!(rs_path.extension(), Some(OsStr::new("rs"))); let so_path = rs_path.with_extension("so"); - compile(&rs_path, &so_path, rustc.as_deref())?; - println!("\x1b[33mBuferring input...\x1b[0m"); - let input = buffer_input(&input)?; + compile(&rs_path, &so_path, rustc)?; let challenge = dl::open(so_path).expect("Couldn't load dyn library"); - let challenge_main = loader::load_fn_from(&challenge).expect(concat!( + let challenge_main = loader::load_fn_from(&challenge).expect( "Didn't find any appropiate symbol in the compiled .so file. Make sure there is one.", - )); + ); let start = Instant::now(); - let result = unsafe { challenge_main.call(&input, &start) }; + let result = unsafe { challenge_main.call(input, &start) }; + let total_time = start.elapsed(); println!( - "done in {:#?} and yielded result {:?}", - start.elapsed(), - result + "\x1b[32mdone\x1b[0m in \x1b[35m{total_time:#?}\x1b[0m and yielded result \x1b[36m{result:?}\x1b[0m", ); if let Some(timers) = loader::load_timers_from(&challenge) { - println!("with timers:"); - for (name, timer) in timers { - println!(" '{name}': {timer:#?}"); + let mut prev_t = Duration::ZERO; + for (name, cum_timer) in timers { + let timer = *cum_timer - prev_t; + println!( + " '\x1b[36m{name}\x1b[0m': \x1b[35m{timer:#?}\x1b[0m (\x1b[1;33m{:.2}%\x1b[0m) \x1b[35m{cum_timer:#?}\x1b[0m (\x1b[1;33m{:.2}%\x1b[0m)", + (timer.as_nanos() as f64 / total_time.as_nanos() as f64) * 100.0, + (cum_timer.as_nanos() as f64 / total_time.as_nanos() as f64) * 100.0 + ); + + prev_t = *cum_timer; } } @@ -334,14 +360,14 @@ fn compile(rs: &Path, so: &Path, rustc: Option<&str>) -> io::Result<()> { // No need to recompile if so_metadata.modified()? > rs_metadata.modified()? { - println!("\x1b[32mChallenge already compiled\x1b[0m"); + println!("\x1b[32m ok: challenge already compiled\x1b[0m"); return Ok(()); } } } // Recompile - println!("\x1b[33mCompiling {rs:#?}...\x1b[0m"); + println!("\x1b[33m prog: compiling {rs:#?}...\x1b[0m"); let exit = Command::new(rustc.unwrap_or("rustc")) .args([ "--crate-type=cdylib",