From d44431c42ac5a7d27143ffae9f4bd797abdefac9 Mon Sep 17 00:00:00 2001 From: javalsai Date: Tue, 2 Dec 2025 02:25:40 +0100 Subject: [PATCH] perf: several optimizations --- .gitignore | 1 + 2025/01/p1.rs | 20 +++++++---------- 2025/01/p2.rs | 23 ++++++++----------- 2025/tester.rs | 60 +++++++++++++++++++++++++++++++++++++++++++------- 4 files changed, 70 insertions(+), 34 deletions(-) diff --git a/.gitignore b/.gitignore index f5a8b89..917c5d6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # inputs *demo*.txt input.txt +*-large.txt # binaries main diff --git a/2025/01/p1.rs b/2025/01/p1.rs index 333850e..23a5b94 100644 --- a/2025/01/p1.rs +++ b/2025/01/p1.rs @@ -1,24 +1,20 @@ -use std::{hint::unreachable_unchecked, io::BufRead}; +type SmolI = i16; #[unsafe(no_mangle)] extern "Rust" fn challenge_isize(buf: &[u8]) -> isize { let mut count = 0; let mut pos = 50; - for ln in buf.lines() { - let ln = unsafe { ln.unwrap_unchecked() }; - let (dir, amt) = unsafe { (ln.as_bytes().get_unchecked(0), ln.get_unchecked(1..)) }; + let buf = unsafe { str::from_utf8_unchecked(buf.get_unchecked(..(buf.len() - 1))) }; - let amt: i16 = unsafe { amt.parse().unwrap_unchecked() }; - match dir { - b'L' => pos -= amt, - b'R' => pos += amt, - _ => unsafe { unreachable_unchecked() }, - } + for ln in buf.split('\n') { + let (dir, amt) = unsafe { (*ln.as_bytes().get_unchecked(0), ln.get_unchecked(1..)) }; + let amt: SmolI = unsafe { amt.parse().unwrap_unchecked() }; + pos += ((((dir == b'R') as SmolI) << 1) - 1) * amt; pos %= 100; - count += (pos != 0) as i16; + count += (pos == 0) as SmolI; } - count.into() + count as isize } diff --git a/2025/01/p2.rs b/2025/01/p2.rs index b534a90..adf76e0 100644 --- a/2025/01/p2.rs +++ b/2025/01/p2.rs @@ -1,24 +1,19 @@ -use std::{hint::unreachable_unchecked, io::BufRead}; +type SmolI = i32; #[unsafe(no_mangle)] unsafe extern "Rust" fn challenge_isize(buf: &[u8]) -> isize { let mut count = 0; let mut pos = 50; - for ln in buf.lines() { - let ln = unsafe { ln.unwrap_unchecked() }; - let (dir, amt) = (ln.as_bytes()[0], &ln[1..]); + let buf = unsafe { str::from_utf8_unchecked(buf.get_unchecked(..(buf.len() - 1))) }; - let amt: isize = unsafe { amt.parse().unwrap_unchecked() }; - if amt == 0 { - continue; - } + for ln in buf.split('\n') { + let (dir, amt) = unsafe { (*ln.as_bytes().get_unchecked(0), ln.get_unchecked(1..)) }; + + let amt: SmolI = unsafe { amt.parse().unwrap_unchecked() }; + if amt == 0 { continue; } let prev_pos = pos; - match dir { - b'L' => pos -= amt, - b'R' => pos += amt, - _ => unsafe { unreachable_unchecked() }, - } + pos += ((((dir == b'R') as SmolI) << 1) - 1) * amt; count += if pos < 0 { if prev_pos == 0 { @@ -33,5 +28,5 @@ unsafe extern "Rust" fn challenge_isize(buf: &[u8]) -> isize { pos = pos.rem_euclid(100); } - count + count as isize } diff --git a/2025/tester.rs b/2025/tester.rs index 31556ea..092d4f8 100644 --- a/2025/tester.rs +++ b/2025/tester.rs @@ -2,12 +2,12 @@ use std::{ env, - ffi::{CStr, OsStr}, + ffi::OsStr, fs::File, io::{self, Read}, path::{Path, PathBuf}, process::{Command, Stdio}, - time::Instant, + time::{self, Instant}, }; pub mod dl { @@ -133,6 +133,19 @@ pub mod loader { } impl FnVariant { + /// Ideally the fn would be put on the heap, but that requires customly allocating + /// executable memory, a DST pain other stuff I can't be concerned with rn. + pub const fn make_noop_stub_isize() -> Self { + unsafe extern "Rust" fn __stub(_: &[u8]) -> isize { + 0 + } + + FnVariant::Isize(__stub) + } + + /// # Safety + /// + /// Calls external arbitrary FNs. pub unsafe fn call(&self, buf: &[u8]) -> FnRetVariant { use FnRetVariant as R; use FnVariant as V; @@ -146,10 +159,7 @@ pub mod loader { } } - /// # Safety - /// - /// Can return arbitrary fn's depending on the handle's symbols. - pub unsafe fn load_fn_from(handle: &Handle) -> Option { + pub fn load_fn_from(handle: &Handle) -> Option { macro_rules! untry { ($e:expr) => { match $e { @@ -162,6 +172,8 @@ pub mod loader { use ChallengeFn as C; use FnVariant as V; + // SAFETY: This is actually safe as it just creates the fn ptr, no weird fn with weird + // drop. untry!(unsafe { handle.symfn::>(c"challenge_isize") }.map(V::Isize)); untry!(unsafe { handle.symfn::>(c"challenge_isize") }.map(V::Usize)); untry!(unsafe { handle.symfn::>(c"challenge_isize") }.map(V::UsizeDuple)); @@ -171,7 +183,21 @@ pub mod loader { } } -const EXPORT_NAME: &CStr = c"challenge"; +pub mod performer { + use std::time::{Duration, Instant}; + + pub fn get_avg_runt(runs: u32, run: fn()) -> std::time::Duration { + let mut total_t = Duration::ZERO; + for _ in 0..runs { + let t = Instant::now(); + run(); + total_t += t.elapsed(); + } + + total_t / runs + } +} + pub type ChallengeFn = unsafe extern "Rust" fn(&[u8]) -> isize; fn main() -> io::Result<()> { @@ -182,6 +208,11 @@ fn main() -> io::Result<()> { return Ok(()); }; + let (noop_overhead_cold, noop_overhead_hot) = measure_noop_overhead(); + println!( + "\x1b[34minf: noop fn takes {noop_overhead_hot:#?} hot and {noop_overhead_cold:#?} cold\x1b[0m", + ); + let rs_path = PathBuf::from(&day); assert_eq!(rs_path.extension(), Some(OsStr::new("rs"))); let so_path = rs_path.with_extension("so"); @@ -191,7 +222,7 @@ fn main() -> io::Result<()> { let input = buffer_input(&input)?; let challenge = dl::open(so_path).expect("Couldn't load dyn library"); - let challenge_main = unsafe { loader::load_fn_from(&challenge) }.expect(concat!( + let challenge_main = loader::load_fn_from(&challenge).expect(concat!( "Didn't find any appropiate symbol in the compiled .so file. Make sure there is one and is ", stringify!(unsafe extern "Rust" fn(&[u8]) -> isize) )); @@ -208,6 +239,19 @@ fn main() -> io::Result<()> { Ok(()) } +const STUB_ISIZE: loader::FnVariant = loader::FnVariant::make_noop_stub_isize(); +fn measure_noop_overhead() -> (time::Duration, time::Duration) { + let timer = Instant::now(); + // SAFETY: completely safe, not arbitrary fn but our stub + unsafe { STUB_ISIZE.call(&[]) }; + let cold = timer.elapsed(); + let hot = performer::get_avg_runt(u16::MAX.into(), || unsafe { + STUB_ISIZE.call(&[]); + }); + + (cold, hot) +} + fn compile(rs: &Path, so: &Path, rustc: Option<&str>) -> io::Result<()> { if env::var("REBUILD").is_err() { let rsf = File::open(rs)?;