1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
|
# Benchmarking and profiling
## The `bench` command
```bash
cargo run --profile profiling -- bench [depth] # default depth 6
```
[`src/bench.rs`](../src/bench.rs) searches six fixed positions, one from each phase of the game, to a
fixed depth without the TUI, and prints the move, score and time for each, plus a total:
| Label | Position after |
|-------|----------------|
| opening | 0 plies |
| tiger reply | 1 ply |
| mid-placement | 20 plies |
| end of placement | 38 plies |
| early movement | 45 plies |
| movement | 60 plies |
The positions come from `ClaudePlayer` playing itself to depth 4, which is deterministic and plays
much more like a real game than random moves. Each search uses a fresh `ClaudePlayer` with
`max_depth` set to the requested depth and an effectively infinite time limit, so every run does
exactly the same work and timings can be compared. Depth 9 takes well under a second in total.
**The rule for optimisations:** a change that only makes the search faster must leave every move
and every score unchanged. If they change, the change altered what the search computes, not just
how fast.
## Search counters
```bash
cargo run --profile profiling --features search-stats -- bench 9
```
With the `search-stats` feature, each position also prints:
- nodes searched and nodes per second, leaves, terminal positions, repetitions, move generations per node;
- cache probes, hit rate, the share of hits that settled the score without searching, and overwrites;
- cutoffs, and the share of them on the first move tried, which measures move ordering.
The counters are thread-local `SearchStats` fields bumped by the `count!` macro (defined in
`player.rs`, also used in `board.rs`). Without the feature `count!` expands to nothing. Counting
slows the search a little, so compare times only between builds with the same features.
## Profiling
The `profiling` profile is `release` with debug info. The dev shell includes `perf` and `samply`.
```bash
cargo build --profile profiling
perf record -e cycles:u -m 16 --call-graph dwarf target/profiling/tigers-and-sheep-rs bench 9
perf report
```
As a non-root user `perf record` needs `-m 16` (a small ring buffer) with `--call-graph dwarf`,
otherwise mmap fails against the `perf_event_mlock_kb` limit.
Or, for a flame graph in the browser:
```bash
samply record target/profiling/tigers-and-sheep-rs bench 9
```
## Check the clock source first
```bash
cat /sys/devices/system/clocksource/clocksource0/current_clocksource
```
The search reads the clock (`Instant::now()`) once per interior node to check its deadline. With the
`tsc` clock source that's a cheap instruction. If the kernel has fallen back to `hpet`, as has
happened on the development machine, every read becomes a system call and the search runs at nearly
half speed. Only compare timings taken on the same clock source.
## History
[`plans.org`](../plans.org) records each optimisation with its before and after timings, node counts
and cutoff rates, and the next candidates. Add to it when you make a change that affects speed.
|