Skip to content

Commit ba69cc6

Browse files
committed
add time profiling and benchmarking
1 parent c6b4876 commit ba69cc6

File tree

9 files changed

+268
-59
lines changed

9 files changed

+268
-59
lines changed

05-game-of-life/Cargo.toml

+6-6
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ default = ["console_error_panic_hook"]
1515

1616
[dependencies]
1717
wasm-bindgen = "0.2.63"
18-
js-sys = "0.3"
18+
# js-sys = "0.3"
1919

2020
# The `console_error_panic_hook` crate provides better debugging of panics by
2121
# logging them with `console.error`. This is great for development, but requires
@@ -34,11 +34,11 @@ wee_alloc = { version = "0.4.5", optional = true }
3434
wasm-bindgen-test = "0.3.13"
3535

3636
# logging
37-
# [dependencies.web-sys]
38-
# version = "0.3"
39-
# features = [
40-
# "console",
41-
# ]
37+
[dependencies.web-sys]
38+
version = "0.3"
39+
features = [
40+
"console",
41+
]
4242

4343
[profile.release]
4444
# Tell `rustc` to optimize for small code size.

05-game-of-life/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@ A zero-player game to learn how to use Rust, WebAssembly, and JavaScript togethe
1515
- testing the tick function and debugging.
1616
- pausing and resuming the game.
1717
- toggling a cell's state on click.
18+
- optimizing performances with time profiling and benchmarking.
1819

1920
Based on [The Rust and WebAssembly Book](https://rustwasm.github.io/docs/book/) by The Rust and WebAssembly Working Group (2021).

05-game-of-life/after.txt

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
2+
running 0 tests
3+
4+
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
5+
6+
7+
running 1 test
8+
test universe_ticks ... bench: 178,561 ns/iter (+/- 14,685)
9+
10+
test result: ok. 0 passed; 0 failed; 0 ignored; 1 measured; 0 filtered out; finished in 3.32s
11+

05-game-of-life/before.txt

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
2+
running 0 tests
3+
4+
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
5+
6+
7+
running 1 test
8+
test universe_ticks ... bench: 748,300 ns/iter (+/- 50,043)
9+
10+
test result: ok. 0 passed; 0 failed; 0 ignored; 1 measured; 0 filtered out; finished in 4.16s
11+

05-game-of-life/benches/bench.rs

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// to use feature, you need to run: rustup default nightly
2+
// cargo bench | tee before.txt
3+
// cargo bench | tee after.txt
4+
// cargo benchcmp before.txt after.txt // x 4.19
5+
// rustup default stable
6+
#![feature(test)]
7+
8+
extern crate game_of_life;
9+
extern crate test;
10+
11+
#[bench]
12+
fn universe_ticks(b: &mut test::Bencher) {
13+
let mut universe = game_of_life::Universe::new();
14+
15+
b.iter(|| {
16+
universe.tick();
17+
});
18+
}

05-game-of-life/src/lib.rs

+124-52
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,34 @@ mod utils;
22

33
use std::fmt;
44
use wasm_bindgen::prelude::*;
5-
extern crate js_sys;
6-
// extern crate web_sys;
5+
// extern crate js_sys;
6+
extern crate web_sys;
7+
use web_sys::console;
78

89
// A macro to provide `println!(..)`-style syntax for `console.log` logging.
910
// macro_rules! log {
1011
// ($( $t:tt)* ) => {
11-
// web_sys::console::log_1(&format!( $( $t )*).into());
12+
// console::log_1(&format!( $( $t )*).into());
1213
// };
1314
// }
1415

16+
pub struct Timer<'a> {
17+
name: &'a str,
18+
}
19+
20+
impl<'a> Timer<'a> {
21+
pub fn new(name: &'a str) -> Timer<'a> {
22+
console::time_with_label(name);
23+
Timer { name }
24+
}
25+
}
26+
27+
impl<'a> Drop for Timer<'a> {
28+
fn drop(&mut self) {
29+
console::time_end_with_label(self.name);
30+
}
31+
}
32+
1533
#[wasm_bindgen]
1634
#[repr(u8)]
1735
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@@ -42,18 +60,62 @@ impl Universe {
4260
}
4361

4462
fn live_neighbor_count(&self, row: u32, column: u32) -> u8 {
63+
// let mut count = 0;
64+
// for delta_row in [self.height - 1, 0, 1].iter().cloned() {
65+
// for delta_col in [self.width - 1, 0, 1].iter().cloned() {
66+
// if delta_row == 0 && delta_col == 0 {
67+
// continue;
68+
// }
69+
// let neighbor_row = (row + delta_row) % self.height;
70+
// let neighbor_col = (column + delta_col) % self.width;
71+
// let idx = self.get_index(neighbor_row, neighbor_col);
72+
// count += self.cells[idx] as u8;
73+
// }
74+
// }
75+
// count
76+
4577
let mut count = 0;
46-
for delta_row in [self.height - 1, 0, 1].iter().cloned() {
47-
for delta_col in [self.width - 1, 0, 1].iter().cloned() {
48-
if delta_row == 0 && delta_col == 0 {
49-
continue;
50-
}
51-
let neighbor_row = (row + delta_row) % self.height;
52-
let neighbor_col = (column + delta_col) % self.width;
53-
let idx = self.get_index(neighbor_row, neighbor_col);
54-
count += self.cells[idx] as u8;
55-
}
56-
}
78+
79+
let north = if row == 0 { self.height - 1 } else { row - 1 };
80+
81+
let south = if row == self.height - 1 { 0 } else { row + 1 };
82+
83+
let west = if column == 0 {
84+
self.width - 1
85+
} else {
86+
column - 1
87+
};
88+
89+
let east = if column == self.width - 1 {
90+
0
91+
} else {
92+
column + 1
93+
};
94+
95+
let nw = self.get_index(north, west);
96+
count += self.cells[nw] as u8;
97+
98+
let n = self.get_index(north, column);
99+
count += self.cells[n] as u8;
100+
101+
let ne = self.get_index(north, east);
102+
count += self.cells[ne] as u8;
103+
104+
let w = self.get_index(row, west);
105+
count += self.cells[w] as u8;
106+
107+
let e = self.get_index(row, east);
108+
count += self.cells[e] as u8;
109+
110+
let sw = self.get_index(south, west);
111+
count += self.cells[sw] as u8;
112+
113+
let s = self.get_index(south, column);
114+
count += self.cells[s] as u8;
115+
116+
let se = self.get_index(south, east);
117+
count += self.cells[se] as u8;
118+
57119
count
58120
}
59121

@@ -75,44 +137,53 @@ impl Universe {
75137
#[wasm_bindgen]
76138
impl Universe {
77139
pub fn tick(&mut self) {
78-
let mut next = self.cells.clone();
79-
80-
for row in 0..self.height {
81-
for col in 0..self.width {
82-
let idx = self.get_index(row, col);
83-
let cell = self.cells[idx];
84-
let live_neighbors = self.live_neighbor_count(row, col);
85-
86-
// log!(
87-
// "cell[{}, {}] is initially {:?} and has {} live neighbors",
88-
// row,
89-
// col,
90-
// cell,
91-
// live_neighbors
92-
// );
93-
94-
let next_cell = match (cell, live_neighbors) {
95-
// Rule 1: Any live cell with fewer than two live neighbours
96-
// dies, as if caused by underpopulation.
97-
(Cell::Alive, x) if x < 2 => Cell::Dead,
98-
// Rule 2: Any live cell with two or three live neighbours
99-
// lives on to the next generation.
100-
(Cell::Alive, 2) | (Cell::Alive, 3) => Cell::Alive,
101-
// Rule 3: Any live cell with more than three live
102-
// neighbours dies, as if by overpopulation.
103-
(Cell::Alive, x) if x > 3 => Cell::Dead,
104-
// Rule 4: Any dead cell with exactly three live neighbours
105-
// becomes a live cell, as if by reproduction.
106-
(Cell::Dead, 3) => Cell::Alive,
107-
// All other cells remain in the same state.
108-
(otherwise, _) => otherwise,
109-
};
110-
111-
// log!("it becomes {:?}", next_cell);
112-
113-
next[idx] = next_cell;
140+
let _timer = Timer::new("Universe::tick");
141+
let mut next = {
142+
let _timer = Timer::new("allocate next cells"); // allocate next cells: 0.046875 ms
143+
self.cells.clone()
144+
};
145+
146+
{
147+
let _timer = Timer::new("new generation"); // new generation: 0.8623046875 ms
148+
// the vast majority of time is spent actually calculating the next generation of cells.
149+
for row in 0..self.height {
150+
for col in 0..self.width {
151+
let idx = self.get_index(row, col);
152+
let cell = self.cells[idx];
153+
let live_neighbors = self.live_neighbor_count(row, col);
154+
155+
// log!(
156+
// "cell[{}, {}] is initially {:?} and has {} live neighbors",
157+
// row,
158+
// col,
159+
// cell,
160+
// live_neighbors
161+
// );
162+
163+
let next_cell = match (cell, live_neighbors) {
164+
// Rule 1: Any live cell with fewer than two live neighbours
165+
// dies, as if caused by underpopulation.
166+
(Cell::Alive, x) if x < 2 => Cell::Dead,
167+
// Rule 2: Any live cell with two or three live neighbours
168+
// lives on to the next generation.
169+
(Cell::Alive, 2) | (Cell::Alive, 3) => Cell::Alive,
170+
// Rule 3: Any live cell with more than three live
171+
// neighbours dies, as if by overpopulation.
172+
(Cell::Alive, x) if x > 3 => Cell::Dead,
173+
// Rule 4: Any dead cell with exactly three live neighbours
174+
// becomes a live cell, as if by reproduction.
175+
(Cell::Dead, 3) => Cell::Alive,
176+
// All other cells remain in the same state.
177+
(otherwise, _) => otherwise,
178+
};
179+
180+
// log!("it becomes {:?}", next_cell);
181+
182+
next[idx] = next_cell;
183+
}
114184
}
115185
}
186+
let _timer = Timer::new("free old cells"); // free old cells: 0.041015625 ms
116187
self.cells = next;
117188
}
118189

@@ -122,8 +193,9 @@ impl Universe {
122193
let height = 64;
123194

124195
let cells = (0..width * height)
125-
.map(|_i| {
126-
if js_sys::Math::random() < 0.5 {
196+
.map(|i| {
197+
// if js_sys::Math::random() < 0.5 {
198+
if i % 2 == 0 || i % 7 == 0 {
127199
Cell::Alive
128200
} else {
129201
Cell::Dead

05-game-of-life/www/index.html

+5
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
align-items: center;
1616
justify-content: center;
1717
}
18+
#fps {
19+
white-space: pre;
20+
font-family: monospace;
21+
}
1822
</style>
1923
</head>
2024
<body>
@@ -23,6 +27,7 @@
2327
javascript in your browser.</noscript
2428
>
2529
<button id="play-pause"></button>
30+
<div id="fps"></div>
2631
<canvas id="game-of-life-canvas"></canvas>
2732
<script src="./bootstrap.js"></script>
2833
</body>

0 commit comments

Comments
 (0)