Skip to content

Add a fourth approach to the rust version of the Bob problem #2058

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions exercises/practice/bob/.articles/performance/code/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "code"
version = "0.1.0"
edition = "2024"

[dependencies]

[[bin]]
name = "code"
path = "main.rs"
121 changes: 118 additions & 3 deletions exercises/practice/bob/.articles/performance/code/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ fn main() {
println!("Hello, world!");
}

// Reply using match
pub fn reply_match(msg: &str) -> &str {
let message = msg.trim_end();
if message.is_empty() {
Expand All @@ -24,6 +25,7 @@ pub fn reply_match(msg: &str) -> &str {
}
}

// Reply using if chain
pub fn reply_if_chain(msg: &str) -> &str {
let message = msg.trim_end();
if message.is_empty() {
Expand All @@ -47,6 +49,7 @@ pub fn reply_if_chain(msg: &str) -> &str {
"Whatever."
}

// Reply using array
const ANSWERS: &'static [&'static str] = &[
"Whatever.",
"Sure.",
Expand All @@ -71,20 +74,132 @@ pub fn reply_array(msg: &str) -> &str {
ANSWERS[is_questioning + is_yelling]
}

// Reply using state machine
enum State {
Initial,
HasQuestionMark,
NoQuestionMarkUpperCase,
NoQuestionMarkUndefined,
QuestionMarkUpperCase,
QuestionMarkUndefined,
}

const FINE_BE_THAT_WAY: &str = "Fine. Be that way!";
const WHATEVER: &str = "Whatever.";
const SURE: &str = "Sure.";
const CHILL_OUT: &str = "Whoa, chill out!";
const CALM_DOWN: &str = "Calm down, I know what I'm doing!";

pub fn reply_state_machine(message: &str) -> &str {
let message = message.trim();
if message.is_empty() {
return FINE_BE_THAT_WAY;
}
let mut state = State::Initial;
for c in message.chars().rev() {
match state {
State::Initial => {
state = if c == '?' {
State::HasQuestionMark
} else {
if c.is_lowercase() {
return WHATEVER;
}
if c.is_uppercase() {
State::NoQuestionMarkUpperCase
} else {
State::NoQuestionMarkUndefined
}
};
}
State::HasQuestionMark => {
state = if c.is_uppercase() {
State::QuestionMarkUpperCase
} else {
State::QuestionMarkUndefined
};
}
State::NoQuestionMarkUpperCase => {
if c.is_lowercase() {
return WHATEVER;
}
}
State::NoQuestionMarkUndefined => {
if c.is_lowercase() {
return WHATEVER;
}
if c.is_uppercase() {
state = State::NoQuestionMarkUpperCase;
}
}
State::QuestionMarkUpperCase => {
if c.is_lowercase() {
return SURE;
}
}
State::QuestionMarkUndefined => {
if c.is_lowercase() {
return SURE;
}
if c.is_uppercase() {
state = State::QuestionMarkUpperCase;
}
}
}
}
match state {
State::HasQuestionMark | State::QuestionMarkUndefined => SURE,
State::NoQuestionMarkUpperCase => CHILL_OUT,
State::NoQuestionMarkUndefined => WHATEVER,
State::QuestionMarkUpperCase => CALM_DOWN,
_ => panic!("Unexpected final state"),
}
}

#[bench]
/// multiple line question for match
fn multiple_line_question_match(b: &mut Bencher) {
b.iter(|| reply_match("\rDoes this cryogenic chamber make me look fat?\rNo."));
b.iter(|| {
reply_match("\rDoes this cryogenic chamber make me look fat?\rNo.");
reply_match(" ");
reply_match("Does this cryogenic chamber make me look fat?");
reply_match("WHAT'S GOING ON?");
reply_match("ZOMG THE %^*@#$(*^ ZOMBIES ARE COMING!!11!!1!");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A separate function could make sense here, to make it more clear that all benches are running the same code.

});
}

#[bench]
/// multiple line question for if statements
fn multiple_line_question_if(b: &mut Bencher) {
b.iter(|| reply_if_chain("\rDoes this cryogenic chamber make me look fat?\rNo."));
b.iter(|| {
reply_if_chain("\rDoes this cryogenic chamber make me look fat?\rNo.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
reply_if_chain("\rDoes this cryogenic chamber make me look fat?\rNo.");
reply_if_chain(std::hint::black_box("\rDoes this cryogenic chamber make me look fat?\rNo."));

The inputs to the functions under test should be wrapped in black_box, to prevent the compiler from optimizing the function based on knowledge about its input. In my testing, multiple_line_question_array slows down a little from 120 ns/iter to 140 ns/iter, the other approaches stay the same.

reply_if_chain(" ");
reply_if_chain("Does this cryogenic chamber make me look fat?");
reply_if_chain("WHAT'S GOING ON?");
reply_if_chain("ZOMG THE %^*@#$(*^ ZOMBIES ARE COMING!!11!!1!");
});
}

#[bench]
/// multiple line question for answer array
fn multiple_line_question_array(b: &mut Bencher) {
b.iter(|| reply_array("\rDoes this cryogenic chamber make me look fat?\rNo."));
b.iter(|| {
reply_array("\rDoes this cryogenic chamber make me look fat?\rNo.");
reply_array(" ");
reply_array("Does this cryogenic chamber make me look fat?");
reply_array("WHAT'S GOING ON?");
reply_array("ZOMG THE %^*@#$(*^ ZOMBIES ARE COMING!!11!!1!");
});
}

#[bench]
/// multiple line question for state machine
fn multiple_line_question_state_machine(b: &mut Bencher) {
b.iter(|| {
reply_state_machine("\rDoes this cryogenic chamber make me look fat?\rNo.");
reply_state_machine(" ");
reply_state_machine("Does this cryogenic chamber make me look fat?");
reply_state_machine("WHAT'S GOING ON?");
reply_state_machine("ZOMG THE %^*@#$(*^ ZOMBIES ARE COMING!!11!!1!");
});
}
12 changes: 7 additions & 5 deletions exercises/practice/bob/.articles/performance/content.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,21 @@ The [approaches page][approaches] lists two idiomatic approaches to this exercis
1. [Using `if` statements][approach-if].
2. [Using `match` on a `tuple`][approach-match].

For our performance investigation, we'll also include a third approach that [uses an answer array][approach-answer-array].
For our performance investigation, we'll also include a third approach that [uses an answer array][approach-answer-array] and
a fourth that uses a state machine.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
For our performance investigation, we'll also include a third approach that [uses an answer array][approach-answer-array] and
a fourth that uses a state machine.
For our performance investigation, we'll also include a third approach that [uses an answer array][approach-answer-array] and a fourth that uses a state machine.

Our convention in markdown is one sentence per line.


## Benchmarks

To benchmark the approaches, we wrote a [small benchmark application][benchmark-application].

```
test multiple_line_question_match ... bench: 96 ns/iter (+/- 17)
test multiple_line_question_if ... bench: 97 ns/iter (+/- 12)
test multiple_line_question_array ... bench: 100 ns/iter (+/- 2)
test multiple_line_question_array ... bench: 175.80 ns/iter (+/- 5.91)
test multiple_line_question_if ... bench: 157.26 ns/iter (+/- 8.69)
test multiple_line_question_match ... bench: 152.95 ns/iter (+/- 5.79)
test multiple_line_question_state_machine ... bench: 89.17 ns/iter (+/- 9.65)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cannot reproduce this result. On my machine, the state machine approach is consistently the slowest:

test multiple_line_question_array         ... bench:         119.61 ns/iter (+/- 1.57)
test multiple_line_question_if            ... bench:          95.64 ns/iter (+/- 2.83)
test multiple_line_question_match         ... bench:          96.57 ns/iter (+/- 5.44)
test multiple_line_question_state_machine ... bench:         151.83 ns/iter (+/- 7.84)

```

All three approaches are close in performance, but the `if` statements and `match` approaches may be considered to be more idiomatic.
All four approaches are close in performance, but the `if` statements and `match` approaches may be considered to be more idiomatic. The state machine approach is the fastest because it loops through the characters of the message only once.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
All four approaches are close in performance, but the `if` statements and `match` approaches may be considered to be more idiomatic. The state machine approach is the fastest because it loops through the characters of the message only once.
All four approaches are close in performance, but the `if` statements and `match` approaches may be considered to be more idiomatic.
The state machine approach is the fastest because it loops through the characters of the message only once.

same here, one sentence per line


[approaches]: https://exercism.org/tracks/rust/exercises/bob/approaches
[approach-if]: https://exercism.org/tracks/rust/exercises/bob/approaches/if-statements
Expand Down