aboutsummaryrefslogtreecommitdiff
path: root/plans.org
blob: 8a5f3ac94cefc90f8f0ff53107b2f37f7d709002 (plain) (blame)
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
* Minimax improvements
** DONE Alpha beta pruning
- Move ordering too
  - Probably after iterative deepening
** DONE Score caching
** Move generation caching?
** DONE Iterative deepening
- Use results of previous searches for move ordering
** DONE Profile to see what needs optimization the most
- =cargo run --profile profiling -- bench 9= (add =--features search-stats= for counters)
- Profile: =perf record -e cycles:u -m 16 --call-graph dwarf target/profiling/tigers-and-sheep-rs bench 9=
  (=-m 16= keeps the ring buffer under =perf_event_mlock_kb=, otherwise mmap fails)
- First profile, depth 9: 2056 ms, 2.05 M nodes, ~1.0 M nodes/s
  | Where                                     | Share                    |
  |-------------------------------------------+--------------------------|
  | Kernel: clock_gettime syscalls            | 43% of wall time (sys)   |
  | SipHash for the cache HashMap             | ~29% of user cycles      |
  | get_moves (2.0 calls/node, Vec each time) | ~22% of user cycles      |
  | minimax_uncached + inlined evaluate       | ~29% of user cycles      |
  - The kernel time was not the HashMap's page faults, as first thought: the kernel had fallen back
    from the TSC to the HPET clock source, so =Instant::now()= at every interior node was a real
    syscall (=strace -c=: 99% of syscall time in clock_gettime). After a reboot it was back on TSC.
    Check =/sys/devices/system/clocksource/clocksource0/current_clocksource= before comparing timings.
- Search: cache hit rate 29% (1.8 probes/node); 74% of cutoffs on the first move (63% in placement)
** DONE Speed-ups from the profile
All at depth 9 on the TSC clock source; scores and moves the same as the baseline at every step.
| Step                                       | Time ms | Nodes  | Nodes/s | 1st-move cutoffs |
|--------------------------------------------+---------+--------+---------+------------------|
| Baseline                                   |    1205 | 2.05 M | 1.7 M   | 72%              |
| =has_moves= in =check_win=                 |    1045 | 2.05 M | 2.0 M   | 72%              |
| =TranspositionTable= (2^20 slots, 24 MB)   |     650 | 2.07 M | 3.2 M   | 72%              |
| Jumps first, killer moves                  |     572 | 1.67 M | 2.9 M   | 97%              |
- Table: always-replace needed 2.82 M nodes at 2^20 slots (2.31 M even at 2^22), because the 1.5 M
  leaf entries pushed out deeper ones. Two-slot buckets, one kept for the deepest entry, fixed it.
- Ordering: a =sort_by_key= cost ~26% of cycles and made it slower overall than no ordering;
  =get_moves= now lists jumps first and the search rotates killers and the hint to the front instead.
- Killers on their own cut nodes 10% but made the opening search bigger (0.93 M -> 1.04 M nodes)
*** Next candidates
- Check the deadline every N nodes instead of at every node, so a slow clock source can't cost half the search
- =evaluate=: tiger moves and trapped tigers from bitmasks rather than =get_moves= + 25 =get()= calls
- Table lookups are now the top item (~28% of cycles), mostly cache misses into the 24 MB table
* Draws