From 039f87d762000abdb8d0b6ca8b7bd53213669ab3 Mon Sep 17 00:00:00 2001 From: Dylan Thies Date: Sat, 6 Jan 2024 11:30:48 -0500 Subject: [PATCH] Trying to optimize heap allocations --- .gitignore | 1 + 2023/Cargo.lock | 6 +++ 2023/day-20/Cargo.toml | 8 ++++ 2023/day-20/src/main.rs | 7 ++++ 2023/day-20/src/part1.rs | 90 ++++++++++------------------------------ 2023/day-20/src/part2.rs | 35 +++++----------- 2023/day-7/Cargo.toml | 1 + 2023/day-7/src/part1.rs | 79 +++++++++++++++++------------------ 2023/day-7/src/part2.rs | 69 ++++++++++++++---------------- 9 files changed, 127 insertions(+), 169 deletions(-) diff --git a/.gitignore b/.gitignore index 3badaa1..b6cb6fe 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,4 @@ tags .vscode/settings.json input.txt +dhat-heap.json diff --git a/2023/Cargo.lock b/2023/Cargo.lock index 18bf40c..82681e4 100644 --- a/2023/Cargo.lock +++ b/2023/Cargo.lock @@ -259,10 +259,15 @@ dependencies = [ name = "day-20" version = "2023.0.0" dependencies = [ + "dhat", + "error-stack", "itertools 0.12.0", + "log", "nom", "num", "rstest", + "test-log", + "thiserror", ] [[package]] @@ -359,6 +364,7 @@ dependencies = [ "nom", "num", "num-traits", + "thiserror", ] [[package]] diff --git a/2023/day-20/Cargo.toml b/2023/day-20/Cargo.toml index 90b7e82..b5b045d 100644 --- a/2023/day-20/Cargo.toml +++ b/2023/day-20/Cargo.toml @@ -11,6 +11,14 @@ repository.workspace = true nom = { workspace = true } itertools = {workspace = true } num.workspace = true +dhat = { workspace = true } +thiserror.workspace = true +error-stack.workspace = true +log.workspace = true [dev-dependencies] rstest.workspace = true +test-log.workspace = true + +[features] +dhat-heap = [] diff --git a/2023/day-20/src/main.rs b/2023/day-20/src/main.rs index 92b6cb1..ccc4b81 100644 --- a/2023/day-20/src/main.rs +++ b/2023/day-20/src/main.rs @@ -1,9 +1,16 @@ #![warn(clippy::all, clippy::pedantic)] +#[cfg(feature = "dhat-heap")] +#[global_allocator] +static ALLOC: dhat::Alloc = dhat::Alloc; + use day_20::part1; use day_20::part2; fn main() { + #[cfg(feature = "dhat-heap")] + let _profiler = dhat::Profiler::new_heap(); + let input = include_str!("./input.txt"); let part1_result = part1(input); println!("part 1: {part1_result}"); diff --git a/2023/day-20/src/part1.rs b/2023/day-20/src/part1.rs index 89fbf5c..2085549 100644 --- a/2023/day-20/src/part1.rs +++ b/2023/day-20/src/part1.rs @@ -1,6 +1,6 @@ #![warn(clippy::all, clippy::pedantic)] -use std::collections::{BTreeMap, HashMap, VecDeque}; +use std::collections::{BTreeMap, VecDeque}; use nom::{ branch::alt, @@ -27,21 +27,18 @@ struct Module<'a> { } impl<'a> Module<'a> { - fn handle_pulse(&self, from: &'a str, is_high_pulse: bool) -> (Self, Option) { - let mut m = self.clone(); - match (&m.mod_type, is_high_pulse) { - (ModuleType::Broadcast, _) => (m, Some(is_high_pulse)), - (ModuleType::FlipFlop(_), true) => (m, None), - (ModuleType::FlipFlop(is_on), false) => { - let was_on = *is_on; - m.mod_type = ModuleType::FlipFlop(!is_on); - (m, Some(!was_on)) + fn handle_pulse(&mut self, from: &'a str, is_high_pulse: bool) -> Option { + //let mut m = self.clone(); + match (&mut self.mod_type, is_high_pulse) { + (ModuleType::Broadcast, _) => Some(is_high_pulse), + (ModuleType::FlipFlop(_), true) => None, + (ModuleType::FlipFlop(ref mut is_on), false) => { + *is_on = !*is_on; + Some(*is_on) } - (ModuleType::Conjunction(map), is_high_pulse) => { - let mut map = map.clone(); - map.insert(from, is_high_pulse); - m.mod_type = ModuleType::Conjunction(map.clone()); - (m, Some(!map.values().all(|x| *x))) + (ModuleType::Conjunction(memory), is_high_pulse) => { + *memory.get_mut(from).unwrap() = is_high_pulse; + Some(!memory.values().all(|x| *x)) } } } @@ -76,8 +73,8 @@ impl<'a> Module<'a> { } fn push_button<'a>( - mut setup: BTreeMap<&'a str, Module<'a>>, -) -> (BTreeMap<&'a str, Module<'a>>, (usize, usize)) { + setup: &mut BTreeMap<&'a str, Module<'a>>, +) -> (usize, usize) { let mut queue = VecDeque::from(vec![("broadcaster", None, false)]); let mut low_signals = 1; let mut high_signals = 0; @@ -88,14 +85,14 @@ fn push_button<'a>( break; } let (current_label, from, signal) = queue.pop_front().unwrap(); - let Some(current) = setup.get(current_label) else { + let Some(current) = setup.get_mut(current_label) else { // if not found then in a sink continue; }; - let (new_current, signal_to_send) = current.handle_pulse(from.unwrap_or("button"), signal); + let signal_to_send = current.handle_pulse(from.unwrap_or("button"), signal); if let Some(signal_to_send) = signal_to_send { - new_current + current .connections .iter() .map(|x| (*x, Some(current_label), signal_to_send)) @@ -111,11 +108,12 @@ fn push_button<'a>( high_signals += usize::from(signal_to_send); }); } - setup.insert(current_label, new_current); + //setup.insert(current_label, new_current); } - (setup, (low_signals, high_signals)) + (low_signals, high_signals) } +#[allow(dead_code)] fn setup_to_key(setup: &BTreeMap<&str, Module>) -> String { setup.values().map(Module::state_hash).collect::() } @@ -130,55 +128,13 @@ fn setup_to_key(setup: &BTreeMap<&str, Module>) -> String { #[must_use] pub fn part1(input: &str) -> String { let (_, mut setup) = parse_input(input).expect("aoc input always valid"); - let mut cache = HashMap::new(); - let mut high; - let mut low; let mut high_count = 0; let mut low_count = 0; - let mut key = setup_to_key(&setup); - let mut next_key; - let mut iteration = 0; - let cycle_start = loop { - if iteration >= 1000 { - break iteration; - } - (setup, (low, high)) = push_button(setup); - next_key = setup_to_key(&setup); - if let Some(x) = cache.insert(key, (high, low, next_key.clone(), iteration)) { - break x.3; - } + for _ in 0..1000 { + let (low, high) = push_button(&mut setup); high_count += high; low_count += low; - key = next_key; - iteration += 1; }; - if cycle_start < 1000 { - let cycle_len = iteration - cycle_start; - let nnn = 1000 - iteration; - let number_of_cycles = nnn / cycle_len; - let left_over_pushes = nnn % cycle_len; - let (cycle_high, cycle_low) = cache - .iter() - .filter_map(|(_, (high, low, _, iter))| { - (*iter >= cycle_start && *iter <= iteration).then_some((high, low)) - }) - .fold((0, 0), |(high_total, low_total), (high, low)| { - (high_total + high, low_total + low) - }); - high_count += number_of_cycles * cycle_high; - low_count += number_of_cycles * cycle_low; - let (left_high, left_low) = cache - .iter() - .filter_map(|(_, (high, low, _, iter))| { - (*iter >= cycle_start && *iter <= iteration).then_some((high, low)) - }) - .take(left_over_pushes) - .fold((0, 0), |(high_total, low_total), (high, low)| { - (high_total + high, low_total + low) - }); - high_count += left_high; - low_count += left_low; - } (high_count * low_count).to_string() } @@ -252,7 +208,7 @@ mod test { use super::*; - #[rstest] + #[test_log::test(rstest)] #[case( "broadcaster -> a, b, c %a -> b diff --git a/2023/day-20/src/part2.rs b/2023/day-20/src/part2.rs index c0b85a7..1f5212d 100644 --- a/2023/day-20/src/part2.rs +++ b/2023/day-20/src/part2.rs @@ -38,13 +38,13 @@ struct Module<'a> { } impl<'a> Module<'a> { - fn handle_pulse(&mut self, from: &'a str, pulse: Signal) -> Vec<(&'a str, &'a str, Signal)> { + fn handle_pulse(&mut self, from: &'a str, pulse: Signal) -> Option{ /*println!( "{from} -{}-> {}", if pulse == Signal::Low { "low" } else { "high" }, self.label );*/ - let signal_to_send = match (&mut self.mod_type, pulse) { + match (&mut self.mod_type, pulse) { (ModuleType::Broadcast, _) => Some(pulse), (ModuleType::FlipFlop(_), Signal::High) => None, (ModuleType::FlipFlop(ref mut state), Signal::Low) => { @@ -67,15 +67,6 @@ impl<'a> Module<'a> { Signal::High }) } - }; - //println!("{self:#?}"); - if let Some(signal_to_send) = signal_to_send { - self.connections - .iter() - .map(|x| (*x, self.label, signal_to_send)) - .collect() - } else { - vec![] } } @@ -114,7 +105,6 @@ impl<'a> Module<'a> { fn push_button<'a>( setup: &mut BTreeMap<&'a str, Module<'a>>, - cache: &mut BTreeMap<&'a str, Vec>, ) -> (bool, Vec<(&'a str, Signal)>) { let mut queue = VecDeque::from(vec![("broadcaster", "button", Signal::Low)]); let mut triggered = Vec::new(); @@ -127,14 +117,13 @@ fn push_button<'a>( continue; }; - cache - .entry(current_label) - .and_modify(|list| list.push(current.state_hash())) - .or_insert(vec![current.state_hash()]); - - let signal_to_send = current.handle_pulse(from, signal); - triggered.push((current_label, signal)); - queue.extend(signal_to_send); + if let Some(signal_to_send) = current.handle_pulse(from, signal){ + triggered.push((current_label, signal)); + current.connections.iter().for_each(|con| { + queue.push_back((con, current_label, signal_to_send)); + }); + //queue.extend(signal_to_send); + } } (false, triggered) } @@ -177,7 +166,6 @@ pub fn part2(input: &str) -> String { }) .collect::>(); - let mut cache = BTreeMap::new(); //loop through pushing the button till we found all the connecting nodes cycles for i in 0_u64.. { if penultimate_modules.is_empty() { @@ -186,7 +174,7 @@ pub fn part2(input: &str) -> String { //push the button //println!("push the button {i}"); - let (triggered_early, triggered) = push_button(&mut setup, &mut cache); + let (triggered_early, triggered) = push_button(&mut setup); if triggered_early { return (i + 1).to_string(); } @@ -209,7 +197,6 @@ pub fn part2(input: &str) -> String { //get the LCM of all the last nodes connections // TODO this is ugly cause it assumes lineailty from the end of the input - //println!("{cache:#?}"); lcm(&penulttimate_lengths).to_string() } @@ -300,7 +287,7 @@ mod test { #[rstest] #[case( - "broadcaster -> a, inv + "broadcaster -> a %c -> d %a -> b &inv -> con diff --git a/2023/day-7/Cargo.toml b/2023/day-7/Cargo.toml index f893f90..e35acda 100644 --- a/2023/day-7/Cargo.toml +++ b/2023/day-7/Cargo.toml @@ -13,6 +13,7 @@ itertools.workspace = true num.workspace = true num-traits.workspace = true dhat = { workspace = true } +thiserror.workspace = true [features] dhat-heap = [] diff --git a/2023/day-7/src/part1.rs b/2023/day-7/src/part1.rs index f6f4f2f..3a24edb 100644 --- a/2023/day-7/src/part1.rs +++ b/2023/day-7/src/part1.rs @@ -4,13 +4,14 @@ use nom::{character::complete, multi::separated_list1, sequence::separated_pair, use std::{ cmp::{Ord, Ordering, PartialOrd}, collections::BTreeMap, - str::FromStr, }; +use thiserror::Error; -#[derive(Debug)] -struct Day1Part1Error; +#[derive(Debug, Error)] +#[error("Day7 part 1 error")] +struct Day7Part1Error; -#[derive(Debug, Ord, Eq, PartialEq, PartialOrd)] +#[derive(Debug, Ord, Eq, PartialEq, PartialOrd, Copy, Clone)] enum Card { Two, Three, @@ -26,24 +27,24 @@ enum Card { King, Ace, } -impl FromStr for Card { - type Err = Day1Part1Error; - fn from_str(input: &str) -> Result { +impl TryFrom for Card { + type Error = Day7Part1Error; + fn try_from(input: char) -> Result { match input { - "2" => Ok(Self::Two), - "3" => Ok(Self::Three), - "4" => Ok(Self::Four), - "5" => Ok(Self::Five), - "6" => Ok(Self::Six), - "7" => Ok(Self::Seven), - "8" => Ok(Self::Eight), - "9" => Ok(Self::Nine), - "T" => Ok(Self::Ten), - "J" => Ok(Self::Jack), - "Q" => Ok(Self::Queen), - "K" => Ok(Self::King), - "A" => Ok(Self::Ace), - _ => Err(Day1Part1Error), + '2' => Ok(Self::Two), + '3' => Ok(Self::Three), + '4' => Ok(Self::Four), + '5' => Ok(Self::Five), + '6' => Ok(Self::Six), + '7' => Ok(Self::Seven), + '8' => Ok(Self::Eight), + '9' => Ok(Self::Nine), + 'T' => Ok(Self::Ten), + 'J' => Ok(Self::Jack), + 'Q' => Ok(Self::Queen), + 'K' => Ok(Self::King), + 'A' => Ok(Self::Ace), + _ => Err(Day7Part1Error), } } } @@ -82,30 +83,26 @@ enum HandType { impl From<&Hand> for HandType { fn from(value: &Hand) -> Self { let map = value.cards.iter().fold(BTreeMap::new(), |mut acc, card| { - if let Some(c) = acc.get_mut(card) { - *c += 1; - } else { - acc.insert(card, 1); - } + acc.entry(card).and_modify(|c| *c += 1).or_insert(1); acc }); match map - .iter() - .sorted_by(|a, b| b.1.cmp(a.1)) - .collect::>()[..] + .into_values() + .sorted_by(|a, &b| b.cmp(a)) + .as_slice() { - [(_, 5), ..] => Self::FiveOfAKind, - [(_, 4), ..] => Self::FourOfAKind, - [(_, 3), (_, 2), ..] => Self::FullHouse, - [(_, 3), ..] => Self::ThreeOfAKind, - [(_, 2), (_, 2), ..] => Self::TwoPair, - [(_, 2), ..] => Self::OnePair, + [5, ..] => Self::FiveOfAKind, + [4, ..] => Self::FourOfAKind, + [3, 2, ..] => Self::FullHouse, + [3, ..] => Self::ThreeOfAKind, + [2, 2, ..] => Self::TwoPair, + [2, ..] => Self::OnePair, _ => Self::HighCard, } } } -#[derive(Debug, Eq, PartialEq)] +#[derive(Debug, Eq, PartialEq, Copy, Clone)] struct Hand { pub cards: [Card; 5], pub bet: u32, @@ -123,10 +120,10 @@ impl Ord for Hand { match c { Ordering::Equal => self .cards - .iter() - .interleave(other.cards.iter()) + .into_iter() + .interleave(other.cards) .tuples::<(_, _)>() - .find_map(|(a, b)| match a.cmp(b) { + .find_map(|(a, b)| match a.cmp(&b) { Ordering::Equal => None, x => Some(x), }) @@ -149,7 +146,7 @@ pub fn part1(input: &str) -> String { hands.sort(); hands - .iter() + .into_iter() .enumerate() .map(|(i, hand)| (i + 1) * hand.bet as usize) .sum::() @@ -161,7 +158,7 @@ fn parse_hand(input: &str) -> IResult<&str, Hand> { separated_pair(complete::alphanumeric1, complete::space1, complete::u32)(input)?; let cards = cards .chars() - .filter_map(|c| c.to_string().parse().ok()) + .filter_map(|c| c.try_into().ok()) .collect::>() .try_into() .expect("should work"); diff --git a/2023/day-7/src/part2.rs b/2023/day-7/src/part2.rs index 61bc168..a5debd6 100644 --- a/2023/day-7/src/part2.rs +++ b/2023/day-7/src/part2.rs @@ -5,7 +5,6 @@ use std::fmt; use std::{ cmp::{Ord, Ordering, PartialOrd}, collections::BTreeMap, - str::FromStr, }; #[derive(Debug)] @@ -27,23 +26,23 @@ enum Card { King, Ace, } -impl FromStr for Card { - type Err = Day1Part2Error; - fn from_str(input: &str) -> Result { +impl TryFrom for Card { + type Error = Day1Part2Error; + fn try_from(input: char) -> Result { match input { - "2" => Ok(Self::Two), - "3" => Ok(Self::Three), - "4" => Ok(Self::Four), - "5" => Ok(Self::Five), - "6" => Ok(Self::Six), - "7" => Ok(Self::Seven), - "8" => Ok(Self::Eight), - "9" => Ok(Self::Nine), - "T" => Ok(Self::Ten), - "J" => Ok(Self::Joker), - "Q" => Ok(Self::Queen), - "K" => Ok(Self::King), - "A" => Ok(Self::Ace), + '2' => Ok(Self::Two), + '3' => Ok(Self::Three), + '4' => Ok(Self::Four), + '5' => Ok(Self::Five), + '6' => Ok(Self::Six), + '7' => Ok(Self::Seven), + '8' => Ok(Self::Eight), + '9' => Ok(Self::Nine), + 'T' => Ok(Self::Ten), + 'J' => Ok(Self::Joker), + 'Q' => Ok(Self::Queen), + 'K' => Ok(Self::King), + 'A' => Ok(Self::Ace), _ => Err(Day1Part2Error), } } @@ -102,28 +101,24 @@ enum HandType { impl From<&Hand> for HandType { fn from(value: &Hand) -> Self { - let mut map = value.cards.iter().fold(BTreeMap::new(), |mut acc, card| { - if let Some(c) = acc.get_mut(card) { - *c += 1; - } else { - acc.insert(card, 1); - } + let mut map = value.cards.into_iter().fold(BTreeMap::new(), |mut acc, card| { + acc.entry(card).and_modify(|c| *c += 1).or_insert(1); acc }); let jokers = map.remove(&Card::Joker).unwrap_or(0); match map - .iter() - .sorted_by(|a, b| b.1.cmp(a.1)) - .collect::>()[..] + .into_values() + .sorted_by(|a, &b| b.cmp(a)) + .as_slice() { - [(_, x), ..] if jokers + x == 5 => Self::FiveOfAKind, + [x, ..] if jokers + x == 5 => Self::FiveOfAKind, [] if jokers == 5 => Self::FiveOfAKind, - [(_, x), ..] if jokers + x == 4 => Self::FourOfAKind, - [(_, 3), (_, 2)] => Self::FullHouse, - [(_, 2), (_, 2)] if jokers == 1 => Self::FullHouse, - [(_, x), ..] if jokers + x == 3 => Self::ThreeOfAKind, - [(_, 2), (_, 2), ..] => Self::TwoPair, - [(_, x), ..] if jokers + x == 2 => Self::OnePair, + [x, ..] if jokers + x == 4 => Self::FourOfAKind, + [3, 2] => Self::FullHouse, + [2, 2] if jokers == 1 => Self::FullHouse, + [x, ..] if jokers + x == 3 => Self::ThreeOfAKind, + [2, 2, ..] => Self::TwoPair, + [x, ..] if jokers + x == 2 => Self::OnePair, _ => Self::HighCard, } } @@ -147,10 +142,10 @@ impl Ord for Hand { match c { Ordering::Equal => self .cards - .iter() - .interleave(other.cards.iter()) + .into_iter() + .interleave(other.cards) .tuples::<(_, _)>() - .find_map(|(a, b)| match a.cmp(b) { + .find_map(|(a, b)| match a.cmp(&b) { Ordering::Equal => None, x => Some(x), }) @@ -185,7 +180,7 @@ fn parse_hand(input: &str) -> IResult<&str, Hand> { separated_pair(complete::alphanumeric1, complete::space1, complete::u32)(input)?; let cards = cards .chars() - .filter_map(|c| c.to_string().parse().ok()) + .filter_map(|c| c.try_into().ok()) .collect::>() .try_into() .expect("should work");