pgn2 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/release.yml +103 -0
- data/.gitignore +2 -1
- data/CHANGELOG.md +32 -0
- data/README.md +33 -15
- data/TODO.md +9 -0
- data/bench/IMPROVEMENTS.md +68 -0
- data/bench/baseline_moves.pre-quickwins.txt +23 -0
- data/bench/baseline_moves.txt +6 -6
- data/bench/baseline_parse.pre-quickwins.txt +26 -0
- data/bench/baseline_parse.txt +6 -6
- data/docs/superpowers/specs/2026-08-13-pgn-performance-quick-wins-design.md +227 -0
- data/lib/pgn/game.rb +15 -4
- data/lib/pgn/lexer.rb +60 -38
- data/lib/pgn/move.rb +5 -2
- data/lib/pgn/move_calculator.rb +9 -4
- data/lib/pgn/parser.rb +5 -15
- data/lib/pgn/pgn_parser.rb +79 -78
- data/lib/pgn/pgn_parser.y +20 -22
- data/lib/pgn/position.rb +3 -2
- data/lib/pgn/version.rb +1 -1
- data/spec/parser_spec.rb +6 -38
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b17339791fa63da88b55a180d613c124509f7c286552fd9bb62dcea162eb3325
|
|
4
|
+
data.tar.gz: 2283fa7d6fbf06d54bebbdb9675427c2c51743627423db6bdd91df71a8e2926f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 311d858dca8016414e862b016fe694dff758ea575194d1a4256ebfa02e4427bc389ca291db5eb4a4cf547839e74e717aaeaed68fece0b43a1faf840148b87235
|
|
7
|
+
data.tar.gz: a0ad909b77394c5d7982db61ff37eb4ff02b2e1241b942e48b79bf290c70f5e6e756432fed01a5041179f27546faa2edae2a3b0cd54ff9a7f13a6bb3ac95c006
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Auto-releases on any push to `main` whose version (lib/pgn/version.rb) does
|
|
4
|
+
# not yet have a matching `v<x.y.z>` tag. It gates on the RSpec matrix, then
|
|
5
|
+
# creates the tag and pushes the gem to rubygems.org in this same workflow
|
|
6
|
+
# (a tag pushed by the GITHUB_TOKEN does NOT trigger other workflows, so the
|
|
7
|
+
# publish happens here directly to avoid that limitation).
|
|
8
|
+
#
|
|
9
|
+
# Manual releases still work: `git tag vX.Y.Z && git push origin vX.Y.Z` fires
|
|
10
|
+
# publish.yml (human-pushed tags do trigger workflows).
|
|
11
|
+
#
|
|
12
|
+
# Requires the RUBYGEMS_API_KEY repository secret (already set).
|
|
13
|
+
|
|
14
|
+
on:
|
|
15
|
+
push:
|
|
16
|
+
branches: [main]
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
check:
|
|
20
|
+
name: Detect version bump
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
outputs:
|
|
23
|
+
version: ${{ steps.ver.outputs.version }}
|
|
24
|
+
should_release: ${{ steps.ver.outputs.should_release }}
|
|
25
|
+
steps:
|
|
26
|
+
- uses: actions/checkout@v4
|
|
27
|
+
- id: ver
|
|
28
|
+
run: |
|
|
29
|
+
version="$(ruby -Ilib -rpgn/version -e 'print PGN::VERSION')"
|
|
30
|
+
echo "version=$version" >> "$GITHUB_OUTPUT"
|
|
31
|
+
if git ls-remote --tags origin "refs/tags/v$version" | grep -q .; then
|
|
32
|
+
echo "should_release=false" >> "$GITHUB_OUTPUT"
|
|
33
|
+
echo "v$version already tagged; skipping release"
|
|
34
|
+
else
|
|
35
|
+
echo "should_release=true" >> "$GITHUB_OUTPUT"
|
|
36
|
+
echo "v$version is new; will release"
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
test:
|
|
40
|
+
name: RSpec (ruby ${{ matrix.ruby }})
|
|
41
|
+
needs: check
|
|
42
|
+
if: needs.check.outputs.should_release == 'true'
|
|
43
|
+
runs-on: ubuntu-latest
|
|
44
|
+
strategy:
|
|
45
|
+
fail-fast: true
|
|
46
|
+
matrix:
|
|
47
|
+
ruby: ['3.1', '3.2', '3.3', '3.4']
|
|
48
|
+
steps:
|
|
49
|
+
- uses: actions/checkout@v4
|
|
50
|
+
- uses: ruby/setup-ruby@v1
|
|
51
|
+
with:
|
|
52
|
+
ruby-version: ${{ matrix.ruby }}
|
|
53
|
+
bundler-cache: true
|
|
54
|
+
- run: bundle exec rspec
|
|
55
|
+
|
|
56
|
+
release:
|
|
57
|
+
name: Tag & publish
|
|
58
|
+
needs: [check, test]
|
|
59
|
+
if: needs.check.outputs.should_release == 'true'
|
|
60
|
+
runs-on: ubuntu-latest
|
|
61
|
+
environment: release
|
|
62
|
+
permissions:
|
|
63
|
+
contents: write
|
|
64
|
+
steps:
|
|
65
|
+
- uses: actions/checkout@v4
|
|
66
|
+
- uses: ruby/setup-ruby@v1
|
|
67
|
+
with:
|
|
68
|
+
ruby-version: '3.3'
|
|
69
|
+
bundler-cache: true
|
|
70
|
+
|
|
71
|
+
- name: Create and push tag v${{ needs.check.outputs.version }}
|
|
72
|
+
run: |
|
|
73
|
+
git config user.name "github-actions[bot]"
|
|
74
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
75
|
+
git tag "v${{ needs.check.outputs.version }}" -m "v${{ needs.check.outputs.version }}"
|
|
76
|
+
git push origin "v${{ needs.check.outputs.version }}"
|
|
77
|
+
|
|
78
|
+
- name: Build gem
|
|
79
|
+
run: bundle exec rake build
|
|
80
|
+
|
|
81
|
+
- name: Configure RubyGems credentials
|
|
82
|
+
run: |
|
|
83
|
+
mkdir -p "$HOME/.gem"
|
|
84
|
+
printf -- ":rubygems_api_key: %s\n" "${RUBYGEMS_API_KEY}" > "$HOME/.gem/credentials"
|
|
85
|
+
chmod 0600 "$HOME/.gem/credentials"
|
|
86
|
+
env:
|
|
87
|
+
RUBYGEMS_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }}
|
|
88
|
+
|
|
89
|
+
- name: Push gem
|
|
90
|
+
run: gem push pkg/*.gem
|
|
91
|
+
|
|
92
|
+
- name: Verify publication
|
|
93
|
+
continue-on-error: true
|
|
94
|
+
run: |
|
|
95
|
+
ver="${{ needs.check.outputs.version }}"
|
|
96
|
+
for i in 1 2 3 4 5 6; do
|
|
97
|
+
if gem fetch pgn2 --version "$ver" --platform gem >/dev/null 2>&1; then
|
|
98
|
+
echo "pgn2 $ver published to rubygems.org"
|
|
99
|
+
exit 0
|
|
100
|
+
fi
|
|
101
|
+
sleep 5
|
|
102
|
+
done
|
|
103
|
+
echo "gem push succeeded but fetch verify timed out; check https://rubygems.org/gems/pgn2"
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.1.0 (2026-08-13)
|
|
4
|
+
|
|
5
|
+
### Summary
|
|
6
|
+
|
|
7
|
+
Performance quick wins ("Approach A"): safe, behavior-compatible
|
|
8
|
+
micro-optimizations on top of the 1.0 parser. No public API changes; serialized
|
|
9
|
+
PGN/FEN output stays byte-identical. See
|
|
10
|
+
`docs/superpowers/specs/2026-08-13-pgn-performance-quick-wins-design.md` and
|
|
11
|
+
`bench/IMPROVEMENTS.md` for the design and per-step deltas.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
- **Lexer hot path**: `PGN::Lexer#next_token_pair` returns `[type, value]`
|
|
15
|
+
without allocating a `Token` Struct (or its `keyword_init` Hash), via a shared
|
|
16
|
+
scanning routine that preserves `game_starts` for verbatim `pgn` slicing.
|
|
17
|
+
`PgnParser#next_token` uses it.
|
|
18
|
+
- **`PGN::Game#moves=`**: reuses an existing `MoveText` when its comment is
|
|
19
|
+
already clean (halving `MoveText` allocations on the parse path); still
|
|
20
|
+
re-wraps to preserve the legacy double-`clean_text` for multi-line/nested
|
|
21
|
+
comments.
|
|
22
|
+
- **`PGN::Move#piece=`**: non-allocating castling guard (`start_with?('O')`
|
|
23
|
+
instead of `match('O-O')`), removing a `MatchData` from every `Move.new`.
|
|
24
|
+
- **`PGN::Position`**: `next_player` uses a ternary instead of
|
|
25
|
+
`(PLAYERS - [player])`; `Position#move` skips `castling - restrictions` when
|
|
26
|
+
there are no restrictions.
|
|
27
|
+
- **`PGN::MoveCalculator`**: memoized `destination_coords`, frozen
|
|
28
|
+
`ROOK_RESTRICTIONS` constant, empty short-circuit in `castling_restrictions`.
|
|
29
|
+
- **`PGN::Move#pawn?`**: non-allocating (`==` instead of `%w[P p].include?`).
|
|
30
|
+
- Performance vs 1.0: replay allocations −21% objects / −27% bytes; parse-only
|
|
31
|
+
allocations −3.6% objects / −28% bytes; parse + replay −15% objects / −28%
|
|
32
|
+
bytes. The full hand-rolled SAN parser was deferred (marginal payoff, high
|
|
33
|
+
risk) per the design's "measure first" guidance.
|
|
34
|
+
|
|
3
35
|
## 1.0.0 (2026-08-13)
|
|
4
36
|
|
|
5
37
|
### Summary
|
data/README.md
CHANGED
|
@@ -156,32 +156,37 @@ snapshots captured before any hot-path work — at that point pgn2's parser
|
|
|
156
156
|
were byte-for-byte the original gem's code. The "pgn2" figures are the
|
|
157
157
|
current committed baselines (stdlib `Racc` + `StringScanner` parser,
|
|
158
158
|
column-level copy-on-write `Board`, getbyte-arithmetic `at`). All numbers
|
|
159
|
-
are from the same machine (Ruby 4.0.5, x86_64-linux)
|
|
160
|
-
deterministic
|
|
161
|
-
|
|
159
|
+
are from the same machine (Ruby 4.0.5, x86_64-linux). **Allocation counts
|
|
160
|
+
are deterministic** and the headline signal. **Throughput is reported as the
|
|
161
|
+
median of N back-to-back wall-clock runs** (whittle-era commit `1360bfc` vs
|
|
162
|
+
current `main`): the `benchmark-ips` harness has ±30–60% per-run variance, so
|
|
163
|
+
its single-run `ms/i` is not reliable across versions — e.g. the Racc baseline
|
|
164
|
+
once recorded 371 ms/i (±37%) for parse+replay but the same commit measures
|
|
165
|
+
~1017 ms reproduced today. `bench/baseline_*.txt` remains the harness of
|
|
166
|
+
record for allocations.
|
|
162
167
|
|
|
163
168
|
Move pipeline — immortal game, 45 plies (`bench/profile_moves.rb`):
|
|
164
169
|
|
|
165
170
|
| Metric | original `pgn` | pgn2 | Δ |
|
|
166
171
|
|---|---:|---:|---:|
|
|
167
|
-
| Replay allocations (objects) | 5124 |
|
|
168
|
-
| Replay allocations (bytes) | 262608 |
|
|
169
|
-
| `Board#dup` x45 (objects) | 451 |
|
|
170
|
-
| `Board#dup` x45 (bytes) | 43096 |
|
|
172
|
+
| Replay allocations (objects) | 5124 | 1710 | -3414 (-66.7%) |
|
|
173
|
+
| Replay allocations (bytes) | 262608 | 103760 | -158848 (-60.5%) |
|
|
174
|
+
| `Board#dup` x45 (objects) | 451 | 136 | -315 (-69.8%) |
|
|
175
|
+
| `Board#dup` x45 (bytes) | 43096 | 10336 | -32760 (-76.0%) |
|
|
171
176
|
| `Board#at(str)` x1000 (objects) | 6000 | 0 | -6000 (-100%) |
|
|
172
177
|
| `Board#at(str)` x1000 (bytes) | 240000 | 0 | -240000 (-100%) |
|
|
173
|
-
| Replay throughput |
|
|
178
|
+
| Replay throughput | 841 µs/i | 766 µs/i | ~1.10x faster |
|
|
174
179
|
|
|
175
180
|
Parser — 500 immortal games (`bench/profile_parse.rb`):
|
|
176
181
|
|
|
177
182
|
| Metric | original `pgn` | pgn2 | Δ |
|
|
178
183
|
|---|---:|---:|---:|
|
|
179
|
-
| Parse-only allocations (objects) | 1248065 |
|
|
180
|
-
| Parse-only allocations (bytes) | 120370470 |
|
|
181
|
-
| Parse + replay allocations (objects) | 3778073 |
|
|
182
|
-
| Parse + replay allocations (bytes) | 249570048 |
|
|
183
|
-
| Parse-only throughput |
|
|
184
|
-
| Parse + replay throughput |
|
|
184
|
+
| Parse-only allocations (objects) | 1248065 | 603537 | -644528 (-51.6%) |
|
|
185
|
+
| Parse-only allocations (bytes) | 120370470 | 28257414 | -92113056 (-76.5%) |
|
|
186
|
+
| Parse + replay allocations (objects) | 3778073 | 1427586 | -2350487 (-62.2%) |
|
|
187
|
+
| Parse + replay allocations (bytes) | 249570048 | 78104136 | -171465912 (-68.7%) |
|
|
188
|
+
| Parse-only throughput | 1461 ms/i | 327 ms/i | ~4.5x faster |
|
|
189
|
+
| Parse + replay throughput | 1938 ms/i | 826 ms/i | ~2.35x faster |
|
|
185
190
|
|
|
186
191
|
What changed to get there:
|
|
187
192
|
|
|
@@ -193,9 +198,22 @@ What changed to get there:
|
|
|
193
198
|
6. Parser — `whittle` (≈80% of parse allocations) replaced by stdlib `Racc` +
|
|
194
199
|
`StringScanner`; `PGN::Game#pgn` sliced from per-game byte offsets (no O(n²)
|
|
195
200
|
`@@pgn +=` accumulation).
|
|
201
|
+
7. `PGN::Lexer#next_token_pair` — parser hot path returns `[type, value]`
|
|
202
|
+
without allocating a `Token` Struct (or its `keyword_init` Hash), via a
|
|
203
|
+
shared scanning routine that preserves `game_starts` for verbatim `pgn` slicing.
|
|
204
|
+
8. `PGN::Game#moves=` — reuse existing `MoveText` when its comment is already
|
|
205
|
+
clean; halve `MoveText` allocations on the parse path (still re-wraps to
|
|
206
|
+
preserve the legacy double-`clean_text` for multi-line/nested comments).
|
|
207
|
+
9. `PGN::Move#piece=` — non-allocating castling guard (`start_with?('O')`
|
|
208
|
+
instead of `match('O-O')`), removing a `MatchData` from every `Move.new`.
|
|
209
|
+
10. `PGN::Position#next_player` — ternary instead of `(PLAYERS - [player])`;
|
|
210
|
+
`Position#move` skips `castling - restrictions` when there are none.
|
|
211
|
+
11. `PGN::MoveCalculator` — memoized `destination_coords`, frozen
|
|
212
|
+
`ROOK_RESTRICTIONS`, empty short-circuit in `castling_restrictions`;
|
|
213
|
+
`Move#pawn?` non-allocating.
|
|
196
214
|
|
|
197
215
|
Public output (FEN, PGN) is byte-identical to the original gem; the full
|
|
198
|
-
suite (
|
|
216
|
+
suite (182 examples) stays green. See `bench/IMPROVEMENTS.md` for the per-step
|
|
199
217
|
before/after deltas that produced these tables.
|
|
200
218
|
|
|
201
219
|
## Installation
|
data/TODO.md
CHANGED
|
@@ -10,3 +10,12 @@
|
|
|
10
10
|
|
|
11
11
|
- Support converting a game to pgn format
|
|
12
12
|
- Speed up parsing
|
|
13
|
+
- Collapse PGN::Lexer's per-token allocation chain (Array in `scan_one` ->
|
|
14
|
+
`Token` struct -> Array in racc's `next_token`) to fewer allocations on
|
|
15
|
+
the hot path; keep the full `Token` (with offset/line) only for the
|
|
16
|
+
`#tokens` spec helper, which is its only consumer.
|
|
17
|
+
- Replace the right-recursive `tag_section`/`variation_list` rules in
|
|
18
|
+
`pgn_parser.y` with ordinary left-recursion plus one explicit `.reverse`
|
|
19
|
+
at the point each list is consumed, so the legacy whittle-order
|
|
20
|
+
compatibility quirk is a single greppable line instead of implicit in
|
|
21
|
+
recursion direction.
|
data/bench/IMPROVEMENTS.md
CHANGED
|
@@ -73,3 +73,71 @@ inputs during the migration; 32 explicit parser specs now pin the behavior
|
|
|
73
73
|
permanently (`spec/parser_explicit_spec.rb`).
|
|
74
74
|
|
|
75
75
|
Full suite: 187 examples, 0 failures.
|
|
76
|
+
|
|
77
|
+
## Quick wins (Approach A) — 2026-08-13
|
|
78
|
+
|
|
79
|
+
Safe, behavior-compatible micro-optimizations on top of the Racc parser.
|
|
80
|
+
Spec: `docs/superpowers/specs/2026-08-13-pgn-performance-quick-wins-design.md`.
|
|
81
|
+
"BEFORE" = `bench/baseline_*.pre-quickwins.txt` (working tree immediately before
|
|
82
|
+
this change). "AFTER" = `bench/baseline_*.txt`.
|
|
83
|
+
|
|
84
|
+
### bench/profile_moves.rb (immortal game, 45 plies)
|
|
85
|
+
|
|
86
|
+
| Metric | BEFORE | AFTER | Δ |
|
|
87
|
+
|---|---|---|---|
|
|
88
|
+
| Replay allocations (objects) | 2177 | 1710 | -467 (-21.4%) |
|
|
89
|
+
| Replay allocations (bytes) | 141616 | 103760 | -37856 (-26.7%) |
|
|
90
|
+
| Replay throughput (µs/i) | 931.70 | 848.64 | -83.06 (-8.9%) |
|
|
91
|
+
|
|
92
|
+
### bench/profile_parse.rb (500 immortal games)
|
|
93
|
+
|
|
94
|
+
| Metric | BEFORE | AFTER | Δ |
|
|
95
|
+
|---|---|---|---|
|
|
96
|
+
| Parse-only allocations (objects) | 626037 | 603537 | -22500 (-3.6%) |
|
|
97
|
+
| Parse-only allocations (bytes) | 39417414 | 28257414 | -11160000 (-28.3%) |
|
|
98
|
+
| Parse-only throughput (ms/i) | 318.39 | 274.08 | -44.31 (-13.9%) |
|
|
99
|
+
| Parse + replay allocations (objects) | 1683087 | 1427586 | -255501 (-15.2%) |
|
|
100
|
+
| Parse + replay allocations (bytes) | 108184152 | 78104136 | -30080016 (-27.8%) |
|
|
101
|
+
| Parse + replay throughput (ms/i) | 795.32 | 715.05 | -80.27 (-10.1%) |
|
|
102
|
+
|
|
103
|
+
### Changes applied
|
|
104
|
+
|
|
105
|
+
1. `PGN::Lexer` — added `next_token_pair` (returns `[type, value]`, no `Token`
|
|
106
|
+
Struct) built on a shared private `scan_next` routine that preserves the
|
|
107
|
+
`note_token`/`advance_line`/`game_starts` side effects. `next_token`/`tokens`
|
|
108
|
+
unchanged. `PgnParser#next_token` (in `.y` and generated `.rb`) now uses
|
|
109
|
+
`next_token_pair`. Eliminates the per-token `Token` Struct + its
|
|
110
|
+
`keyword_init` Hash (the larger win in bytes).
|
|
111
|
+
2. `PGN::Game#moves=` — reuses an existing `MoveText` directly when its comment
|
|
112
|
+
is already fully cleaned (nil or brace-free); still re-wraps (preserving the
|
|
113
|
+
legacy double-`clean_text` for multi-line/nested comments) when the comment
|
|
114
|
+
carries braces. Halves `MoveText` allocations on the parse path for
|
|
115
|
+
comment-free corpora.
|
|
116
|
+
3. `PGN::Move#piece=` — replaced the per-`Move.new` `san.match('O-O')` guard
|
|
117
|
+
(allocated a `MatchData` on every move, castling or not) with a
|
|
118
|
+
non-allocating `san.start_with?('O')`. The full hand-rolled SAN parser was
|
|
119
|
+
**deferred** per the spec's "measure first; defer if marginal" guidance:
|
|
120
|
+
it would save only ~1 `MatchData`/ply (~2% of replay, ~1.3% of parse+replay)
|
|
121
|
+
at high risk to SAN edge cases.
|
|
122
|
+
4. `PGN::Position#next_player` — `(PLAYERS - [player]).first` →
|
|
123
|
+
`player == :white ? :black : :white` (removes 2 array allocations/ply).
|
|
124
|
+
`Position#move` — skips `castling - restrictions` when `restrictions` is
|
|
125
|
+
empty (returns the shared `castling` array; safe because castling arrays
|
|
126
|
+
are replaced, never mutated).
|
|
127
|
+
`PGN::MoveCalculator` — memoizes `destination_coords` (was recomputed 2–3
|
|
128
|
+
times/move, each allocating a 2-element array); frozen `ROOK_RESTRICTIONS`
|
|
129
|
+
constant replaces per-call hash literals in `castling_restrictions`; empty
|
|
130
|
+
short-circuit avoids `compact.uniq` on the common empty path.
|
|
131
|
+
`PGN::Move#pawn?` — `%w[P p].include?` → `piece == 'P' || piece == 'p'`.
|
|
132
|
+
`valid_square?` was left unchanged: its `(0..7)` are frozen range literals
|
|
133
|
+
cached by the VM, so inlining would only save method dispatch, not
|
|
134
|
+
allocations (the original rationale was unfounded).
|
|
135
|
+
|
|
136
|
+
### Behavior preservation
|
|
137
|
+
|
|
138
|
+
All 182 specs pass unmodified (`bundle exec rspec`). The lexer refactor keeps
|
|
139
|
+
`next_token`/`tokens` and the `game_starts`-driven verbatim `Game#pgn` slicing
|
|
140
|
+
byte-identical (covered by `spec/lexer_spec.rb`, `spec/parser_explicit_spec.rb`,
|
|
141
|
+
and the fixture round-trip in `spec/game_spec.rb`). Racc parser is in sync with
|
|
142
|
+
`pgn_parser.y` (CI racc-sync check passes). No public API or serialized-output
|
|
143
|
+
changes.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Workload: immortal game, 45 plies
|
|
2
|
+
|
|
3
|
+
=== 1. Replay allocations (45 plies, no parse) ===
|
|
4
|
+
total_allocated objects: 2177
|
|
5
|
+
total_allocated bytes: 141616
|
|
6
|
+
|
|
7
|
+
=== 2. Board#dup x45 (target of flat-board COW) ===
|
|
8
|
+
total_allocated objects: 136
|
|
9
|
+
total_allocated bytes: 10336
|
|
10
|
+
|
|
11
|
+
=== 3. Board#at(str) x1000 (target of coord-arithmetic at) ===
|
|
12
|
+
total_allocated objects: 0
|
|
13
|
+
total_allocated bytes: 0
|
|
14
|
+
|
|
15
|
+
=== 4. Replay throughput (ips, excluding parse) ===
|
|
16
|
+
ruby 4.0.5 (2026-05-20 revision 64336ffd0e) +PRISM [x86_64-linux]
|
|
17
|
+
Warming up --------------------------------------
|
|
18
|
+
replay immortal 83.000 i/100ms
|
|
19
|
+
Calculating -------------------------------------
|
|
20
|
+
replay immortal 1.073k (± 3.2%) i/s (931.70 μs/i) - 5.395k in 5.026523s
|
|
21
|
+
|
|
22
|
+
Done. Snapshot of the working tree immediately before the quick-wins (Approach A) changes.
|
|
23
|
+
Compare bench/baseline_moves.txt (after) against this file.
|
data/bench/baseline_moves.txt
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Workload: immortal game, 45 plies
|
|
2
2
|
|
|
3
3
|
=== 1. Replay allocations (45 plies, no parse) ===
|
|
4
|
-
total_allocated objects:
|
|
5
|
-
total_allocated bytes:
|
|
4
|
+
total_allocated objects: 1710
|
|
5
|
+
total_allocated bytes: 103760
|
|
6
6
|
|
|
7
7
|
=== 2. Board#dup x45 (target of flat-board COW) ===
|
|
8
|
-
total_allocated objects:
|
|
9
|
-
total_allocated bytes:
|
|
8
|
+
total_allocated objects: 136
|
|
9
|
+
total_allocated bytes: 10336
|
|
10
10
|
|
|
11
11
|
=== 3. Board#at(str) x1000 (target of coord-arithmetic at) ===
|
|
12
12
|
total_allocated objects: 0
|
|
@@ -15,8 +15,8 @@ total_allocated bytes: 0
|
|
|
15
15
|
=== 4. Replay throughput (ips, excluding parse) ===
|
|
16
16
|
ruby 4.0.5 (2026-05-20 revision 64336ffd0e) +PRISM [x86_64-linux]
|
|
17
17
|
Warming up --------------------------------------
|
|
18
|
-
replay immortal
|
|
18
|
+
replay immortal 112.000 i/100ms
|
|
19
19
|
Calculating -------------------------------------
|
|
20
|
-
replay immortal 1.
|
|
20
|
+
replay immortal 1.178k (± 4.6%) i/s (848.64 μs/i) - 5.936k in 5.037516s
|
|
21
21
|
|
|
22
22
|
Done. Compare this file against bench/baseline_moves.txt after optimizations.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Corpus: 500 copies of the immortal game
|
|
2
|
+
|
|
3
|
+
=== 1. Parse-only allocations (500 games) ===
|
|
4
|
+
total_allocated objects: 626037
|
|
5
|
+
total_allocated bytes: 39417414
|
|
6
|
+
|
|
7
|
+
=== 2. Parse + replay allocations (500 games) ===
|
|
8
|
+
total_allocated objects: 1683087
|
|
9
|
+
total_allocated bytes: 108184152
|
|
10
|
+
|
|
11
|
+
=== 3. Parse-only throughput (ips) ===
|
|
12
|
+
ruby 4.0.5 (2026-05-20 revision 64336ffd0e) +PRISM [x86_64-linux]
|
|
13
|
+
Warming up --------------------------------------
|
|
14
|
+
parse 500 games 1.000 i/100ms
|
|
15
|
+
Calculating -------------------------------------
|
|
16
|
+
parse 500 games 3.141 (± 0.0%) i/s (318.39 ms/i) - 16.000 in 5.094185s
|
|
17
|
+
|
|
18
|
+
=== 4. Parse + replay throughput (ips) ===
|
|
19
|
+
ruby 4.0.5 (2026-05-20 revision 64336ffd0e) +PRISM [x86_64-linux]
|
|
20
|
+
Warming up --------------------------------------
|
|
21
|
+
parse+replay 500 games 1.000 i/100ms
|
|
22
|
+
Calculating -------------------------------------
|
|
23
|
+
parse+replay 500 games 1.257 (± 0.0%) i/s (795.32 ms/i) - 7.000 in 5.567259s
|
|
24
|
+
|
|
25
|
+
Done. Snapshot of the working tree immediately before the quick-wins (Approach A) changes.
|
|
26
|
+
Compare bench/baseline_parse.txt (after) against this file.
|
data/bench/baseline_parse.txt
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
Corpus: 500 copies of the immortal game
|
|
2
2
|
|
|
3
3
|
=== 1. Parse-only allocations (500 games) ===
|
|
4
|
-
total_allocated objects:
|
|
5
|
-
total_allocated bytes:
|
|
4
|
+
total_allocated objects: 603537
|
|
5
|
+
total_allocated bytes: 28257414
|
|
6
6
|
|
|
7
7
|
=== 2. Parse + replay allocations (500 games) ===
|
|
8
|
-
total_allocated objects:
|
|
9
|
-
total_allocated bytes:
|
|
8
|
+
total_allocated objects: 1427586
|
|
9
|
+
total_allocated bytes: 78104136
|
|
10
10
|
|
|
11
11
|
=== 3. Parse-only throughput (ips) ===
|
|
12
12
|
ruby 4.0.5 (2026-05-20 revision 64336ffd0e) +PRISM [x86_64-linux]
|
|
13
13
|
Warming up --------------------------------------
|
|
14
14
|
parse 500 games 1.000 i/100ms
|
|
15
15
|
Calculating -------------------------------------
|
|
16
|
-
parse 500 games
|
|
16
|
+
parse 500 games 3.649 (±27.4%) i/s (274.08 ms/i) - 19.000 in 5.207546s
|
|
17
17
|
|
|
18
18
|
=== 4. Parse + replay throughput (ips) ===
|
|
19
19
|
ruby 4.0.5 (2026-05-20 revision 64336ffd0e) +PRISM [x86_64-linux]
|
|
20
20
|
Warming up --------------------------------------
|
|
21
21
|
parse+replay 500 games 1.000 i/100ms
|
|
22
22
|
Calculating -------------------------------------
|
|
23
|
-
parse+replay 500 games
|
|
23
|
+
parse+replay 500 games 1.399 (± 0.0%) i/s (715.05 ms/i) - 7.000 in 5.005330s
|
|
24
24
|
|
|
25
25
|
Done. Compare this file against bench/baseline_parse.txt after optimizations.
|