Skip to content
This repository was archived by the owner on Feb 26, 2024. It is now read-only.

Commit bb5da63

Browse files
committed
1 parent 8571657 commit bb5da63

16 files changed

+764
-308
lines changed

.cargo/config.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ read = "run --quiet --release -- read"
55

66
solve = "run --quiet --release -- solve"
77
all = "run --quiet --release -- all"
8-
time = "run --quiet --release -- all --release --time"
8+
time = "run --quiet --release -- time"
99

1010
[env]
1111
AOC_YEAR = "2023"

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,7 @@ target/
1919

2020
data/inputs
2121
data/puzzles
22+
23+
# Benchmarks
24+
25+
data/timings.json

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ itertools = "0.12.0"
1919
num = "0.4.1"
2020
pico-args = "0.5.0"
2121
regex = "1.10.2"
22+
tinyjson = "2.5.1"
2223

2324
[lints.clippy]
2425
unnecessary_cast = "deny"

README.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,17 @@ Solutions for [Advent of Code](https://adventofcode.com/) in [Rust](https://www.
2828

2929
| Day | Part 1 | Part 2 |
3030
| :---: | :---: | :---: |
31-
| [Day 1](./src/bin/01.rs) | `38.5µs` | `178.3µs` |
32-
| [Day 2](./src/bin/02.rs) | `60.3µs` | `54.5µs` |
33-
| [Day 3](./src/bin/03.rs) | `54.1µs` | `144.6µs` |
34-
| [Day 4](./src/bin/04.rs) | `108.3µs` | `105.7µs` |
35-
| [Day 5](./src/bin/05.rs) | `16.5µs` | `29.4µs` |
36-
| [Day 6](./src/bin/06.rs) | `192.0ns` | `341.0ns` |
37-
| [Day 7](./src/bin/07.rs) | `179.9µs` | `197.4µs` |
38-
| [Day 8](./src/bin/08.rs) | `437.9µs` | `1.9ms` |
39-
| [Day 9](./src/bin/09.rs) | `260.9µs` | `255.0µs` |
40-
| [Day 10](./src/bin/10.rs) | `233.4µs` | `1.7ms` |
31+
| [Day 1](./src/bin/01.rs) | `40.6µs` | `177.9µs` |
32+
| [Day 2](./src/bin/02.rs) | `55.3µs` | `55.2µs` |
33+
| [Day 3](./src/bin/03.rs) | `57.6µs` | `152.3µs` |
34+
| [Day 4](./src/bin/04.rs) | `108.1µs` | `108.2µs` |
35+
| [Day 5](./src/bin/05.rs) | `15.4µs` | `29.3µs` |
36+
| [Day 6](./src/bin/06.rs) | `203.0ns` | `354.0ns` |
37+
| [Day 7](./src/bin/07.rs) | `178.2µs` | `202.4µs` |
38+
| [Day 8](./src/bin/08.rs) | `412.9µs` | `1.8ms` |
39+
| [Day 9](./src/bin/09.rs) | `255.8µs` | `250.7µs` |
40+
| [Day 10](./src/bin/10.rs) | `233.5µs` | `1.8ms` |
4141
| [Day 11](./src/bin/11.rs) | `1.6ms` | `1.6ms` |
4242

43-
**Total: 9.16ms**
43+
**Total: 9.13ms**
4444
<!--- benchmarking table --->

src/main.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
use advent_of_code::template::commands::{all, download, read, scaffold, solve};
1+
use advent_of_code::template::commands::{all, download, read, scaffold, solve, time};
22
use args::{parse, AppArguments};
33

44
mod args {
55
use std::process;
66

7-
use advent_of_code::template::day::Day;
7+
use advent_of_code::template::Day;
88

99
pub enum AppArguments {
1010
Download {
@@ -26,6 +26,10 @@ mod args {
2626
release: bool,
2727
time: bool,
2828
},
29+
Time {
30+
all: bool,
31+
day: Option<Day>,
32+
},
2933
}
3034

3135
pub fn parse() -> Result<AppArguments, Box<dyn std::error::Error>> {
@@ -36,6 +40,14 @@ mod args {
3640
release: args.contains("--release"),
3741
time: args.contains("--time"),
3842
},
43+
Some("time") => {
44+
let all = args.contains("--all");
45+
46+
AppArguments::Time {
47+
all,
48+
day: args.opt_free_from_str()?,
49+
}
50+
}
3951
Some("download") => AppArguments::Download {
4052
day: args.free_from_str()?,
4153
},
@@ -78,6 +90,7 @@ fn main() {
7890
}
7991
Ok(args) => match args {
8092
AppArguments::All { release, time } => all::handle(release, time),
93+
AppArguments::Time { day, all } => time::handle(day, all),
8194
AppArguments::Download { day } => download::handle(day),
8295
AppArguments::Read { day } => read::handle(day),
8396
AppArguments::Scaffold { day } => scaffold::handle(day),

src/template/aoc_cli.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ pub enum AocCommandError {
1111
CommandNotFound,
1212
CommandNotCallable,
1313
BadExitStatus(Output),
14-
IoError,
1514
}
1615

1716
impl Display for AocCommandError {
@@ -22,7 +21,6 @@ impl Display for AocCommandError {
2221
AocCommandError::BadExitStatus(_) => {
2322
write!(f, "aoc-cli exited with a non-zero status.")
2423
}
25-
AocCommandError::IoError => write!(f, "could not write output files to file system."),
2624
}
2725
}
2826
}

src/template/commands/all.rs

Lines changed: 2 additions & 250 deletions
Original file line numberDiff line numberDiff line change
@@ -1,253 +1,5 @@
1-
use std::io;
2-
3-
use crate::template::{
4-
day::{all_days, Day},
5-
readme_benchmarks::{self, Timings},
6-
ANSI_BOLD, ANSI_ITALIC, ANSI_RESET,
7-
};
1+
use crate::template::{all_days, run_multi::run_multi};
82

93
pub fn handle(is_release: bool, is_timed: bool) {
10-
let mut timings: Vec<Timings> = vec![];
11-
12-
all_days().for_each(|day| {
13-
if day > 1 {
14-
println!();
15-
}
16-
17-
println!("{ANSI_BOLD}Day {day}{ANSI_RESET}");
18-
println!("------");
19-
20-
let output = child_commands::run_solution(day, is_timed, is_release).unwrap();
21-
22-
if output.is_empty() {
23-
println!("Not solved.");
24-
} else {
25-
let val = child_commands::parse_exec_time(&output, day);
26-
timings.push(val);
27-
}
28-
});
29-
30-
if is_timed {
31-
let total_millis = timings.iter().map(|x| x.total_nanos).sum::<f64>() / 1_000_000_f64;
32-
33-
println!("\n{ANSI_BOLD}Total:{ANSI_RESET} {ANSI_ITALIC}{total_millis:.2}ms{ANSI_RESET}");
34-
35-
if is_release {
36-
match readme_benchmarks::update(timings, total_millis) {
37-
Ok(()) => println!("Successfully updated README with benchmarks."),
38-
Err(_) => {
39-
eprintln!("Failed to update readme with benchmarks.");
40-
}
41-
}
42-
}
43-
}
44-
}
45-
46-
#[derive(Debug)]
47-
pub enum Error {
48-
BrokenPipe,
49-
Parser(String),
50-
IO(io::Error),
51-
}
52-
53-
impl From<std::io::Error> for Error {
54-
fn from(e: std::io::Error) -> Self {
55-
Error::IO(e)
56-
}
57-
}
58-
59-
#[must_use]
60-
pub fn get_path_for_bin(day: Day) -> String {
61-
format!("./src/bin/{day}.rs")
62-
}
63-
64-
/// All solutions live in isolated binaries.
65-
/// This module encapsulates interaction with these binaries, both invoking them as well as parsing the timing output.
66-
mod child_commands {
67-
use super::{get_path_for_bin, Day, Error};
68-
use std::{
69-
io::{BufRead, BufReader},
70-
path::Path,
71-
process::{Command, Stdio},
72-
thread,
73-
};
74-
75-
/// Run the solution bin for a given day
76-
pub fn run_solution(day: Day, is_timed: bool, is_release: bool) -> Result<Vec<String>, Error> {
77-
// skip command invocation for days that have not been scaffolded yet.
78-
if !Path::new(&get_path_for_bin(day)).exists() {
79-
return Ok(vec![]);
80-
}
81-
82-
let day_padded = day.to_string();
83-
let mut args = vec!["run", "--quiet", "--bin", &day_padded];
84-
85-
if is_release {
86-
args.push("--release");
87-
}
88-
89-
if is_timed {
90-
// mirror `--time` flag to child invocations.
91-
args.push("--");
92-
args.push("--time");
93-
}
94-
95-
// spawn child command with piped stdout/stderr.
96-
// forward output to stdout/stderr while grabbing stdout lines.
97-
98-
let mut cmd = Command::new("cargo")
99-
.args(&args)
100-
.stdout(Stdio::piped())
101-
.stderr(Stdio::piped())
102-
.spawn()?;
103-
104-
let stdout = BufReader::new(cmd.stdout.take().ok_or(super::Error::BrokenPipe)?);
105-
let stderr = BufReader::new(cmd.stderr.take().ok_or(super::Error::BrokenPipe)?);
106-
107-
let mut output = vec![];
108-
109-
let thread = thread::spawn(move || {
110-
stderr.lines().for_each(|line| {
111-
eprintln!("{}", line.unwrap());
112-
});
113-
});
114-
115-
for line in stdout.lines() {
116-
let line = line.unwrap();
117-
println!("{line}");
118-
output.push(line);
119-
}
120-
121-
thread.join().unwrap();
122-
cmd.wait()?;
123-
124-
Ok(output)
125-
}
126-
127-
pub fn parse_exec_time(output: &[String], day: Day) -> super::Timings {
128-
let mut timings = super::Timings {
129-
day,
130-
part_1: None,
131-
part_2: None,
132-
total_nanos: 0_f64,
133-
};
134-
135-
output
136-
.iter()
137-
.filter_map(|l| {
138-
if !l.contains(" samples)") {
139-
return None;
140-
}
141-
142-
let Some((timing_str, nanos)) = parse_time(l) else {
143-
eprintln!("Could not parse timings from line: {l}");
144-
return None;
145-
};
146-
147-
let part = l.split(':').next()?;
148-
Some((part, timing_str, nanos))
149-
})
150-
.for_each(|(part, timing_str, nanos)| {
151-
if part.contains("Part 1") {
152-
timings.part_1 = Some(timing_str.into());
153-
} else if part.contains("Part 2") {
154-
timings.part_2 = Some(timing_str.into());
155-
}
156-
157-
timings.total_nanos += nanos;
158-
});
159-
160-
timings
161-
}
162-
163-
fn parse_to_float(s: &str, postfix: &str) -> Option<f64> {
164-
s.split(postfix).next()?.parse().ok()
165-
}
166-
167-
fn parse_time(line: &str) -> Option<(&str, f64)> {
168-
// for possible time formats, see: https://github.com/rust-lang/rust/blob/1.64.0/library/core/src/time.rs#L1176-L1200
169-
let str_timing = line
170-
.split(" samples)")
171-
.next()?
172-
.split('(')
173-
.last()?
174-
.split('@')
175-
.next()?
176-
.trim();
177-
178-
let parsed_timing = match str_timing {
179-
s if s.contains("ns") => s.split("ns").next()?.parse::<f64>().ok(),
180-
s if s.contains("µs") => parse_to_float(s, "µs").map(|x| x * 1000_f64),
181-
s if s.contains("ms") => parse_to_float(s, "ms").map(|x| x * 1_000_000_f64),
182-
s => parse_to_float(s, "s").map(|x| x * 1_000_000_000_f64),
183-
}?;
184-
185-
Some((str_timing, parsed_timing))
186-
}
187-
188-
/// copied from: https://github.com/rust-lang/rust/blob/1.64.0/library/std/src/macros.rs#L328-L333
189-
#[cfg(feature = "test_lib")]
190-
macro_rules! assert_approx_eq {
191-
($a:expr, $b:expr) => {{
192-
let (a, b) = (&$a, &$b);
193-
assert!(
194-
(*a - *b).abs() < 1.0e-6,
195-
"{} is not approximately equal to {}",
196-
*a,
197-
*b
198-
);
199-
}};
200-
}
201-
202-
#[cfg(feature = "test_lib")]
203-
mod tests {
204-
use super::parse_exec_time;
205-
206-
use crate::day;
207-
208-
#[test]
209-
fn test_well_formed() {
210-
let res = parse_exec_time(
211-
&[
212-
"Part 1: 0 (74.13ns @ 100000 samples)".into(),
213-
"Part 2: 10 (74.13ms @ 99999 samples)".into(),
214-
"".into(),
215-
],
216-
day!(1),
217-
);
218-
assert_approx_eq!(res.total_nanos, 74130074.13_f64);
219-
assert_eq!(res.part_1.unwrap(), "74.13ns");
220-
assert_eq!(res.part_2.unwrap(), "74.13ms");
221-
}
222-
223-
#[test]
224-
fn test_patterns_in_input() {
225-
let res = parse_exec_time(
226-
&[
227-
"Part 1: @ @ @ ( ) ms (2s @ 5 samples)".into(),
228-
"Part 2: 10s (100ms @ 1 samples)".into(),
229-
"".into(),
230-
],
231-
day!(1),
232-
);
233-
assert_approx_eq!(res.total_nanos, 2100000000_f64);
234-
assert_eq!(res.part_1.unwrap(), "2s");
235-
assert_eq!(res.part_2.unwrap(), "100ms");
236-
}
237-
238-
#[test]
239-
fn test_missing_parts() {
240-
let res = parse_exec_time(
241-
&[
242-
"Part 1: ✖ ".into(),
243-
"Part 2: ✖ ".into(),
244-
"".into(),
245-
],
246-
day!(1),
247-
);
248-
assert_approx_eq!(res.total_nanos, 0_f64);
249-
assert_eq!(res.part_1.is_none(), true);
250-
assert_eq!(res.part_2.is_none(), true);
251-
}
252-
}
4+
run_multi(all_days().collect(), is_release, is_timed);
2535
}

src/template/commands/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ pub mod download;
33
pub mod read;
44
pub mod scaffold;
55
pub mod solve;
6+
pub mod time;

0 commit comments

Comments
 (0)