Skip to content

Commit 4fd70a3

Browse files
authored
Merge pull request #82 from niklasf/append-ascii
Optimize string conversions
2 parents f95e03e + 18cc653 commit 4fd70a3

File tree

9 files changed

+394
-109
lines changed

9 files changed

+394
-109
lines changed

.github/workflows/test.yml

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,24 @@ jobs:
2525
flags: "-Z minimal-versions --all-features"
2626
runs-on: ubuntu-latest
2727
steps:
28-
- run: sudo apt-get update && sudo apt-get install -y valgrind
2928
- uses: actions/checkout@v4
3029
- uses: dtolnay/rust-toolchain@master
3130
with:
3231
toolchain: ${{ matrix.toolchain }}
3332
- run: cargo test --no-default-features ${{ matrix.flags }}
3433
- run: cargo doc --no-default-features ${{ matrix.flags }}
35-
- run: cargo bench --no-default-features ${{ matrix.flags }}
34+
bench:
35+
runs-on: ubuntu-latest
36+
steps:
37+
- run: sudo apt-get update && sudo apt-get install -y valgrind
38+
- uses: actions/checkout@v4
39+
- uses: dtolnay/rust-toolchain@stable
40+
- run: cargo bench
41+
check_fuzz:
42+
runs-on: ubuntu-latest
43+
steps:
44+
- uses: actions/checkout@v4
45+
- uses: dtolnay/rust-toolchain@nightly
3646
- run: cargo check --manifest-path fuzz/Cargo.toml
3747
check_no_std:
3848
runs-on: ubuntu-latest

benches/benches.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,12 @@ fn bench_zobrist_hash() -> Zobrist64 {
102102
}
103103

104104
fn bench_fen_roundtrip() -> String {
105+
let mut buffer = String::new();
105106
black_box("rnbqkb1r/1p3ppp/p2p1n2/4p3/3NP3/2N1B3/PPP2PPP/R2QKB1R w KQkq - 0 7")
106107
.parse::<Fen>()
107108
.expect("valid fen")
108-
.to_string()
109+
.append_to_string(&mut buffer);
110+
buffer
109111
}
110112

111113
iai::main!(

src/fen.rs

Lines changed: 113 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -50,22 +50,22 @@
5050
//! ```
5151
5252
use core::{
53-
char, fmt,
54-
fmt::{Display, Write as _},
53+
char,
54+
fmt::{self, Display},
5555
num::NonZeroU32,
5656
str::FromStr,
5757
};
5858

5959
use crate::{
60-
Bitboard, Board, ByColor, ByRole, CastlingMode, Color, EnPassantMode, File, FromSetup, Piece,
61-
Position, PositionError, Rank, RemainingChecks, Role, Setup, Square,
60+
util::AppendAscii, Bitboard, Board, ByColor, ByRole, CastlingMode, Color, EnPassantMode, File,
61+
FromSetup, Piece, Position, PositionError, Rank, RemainingChecks, Role, Setup, Square,
6262
};
6363

64-
fn fmt_castling(
65-
f: &mut fmt::Formatter<'_>,
64+
fn append_castling<W: AppendAscii>(
65+
f: &mut W,
6666
board: &Board,
6767
castling_rights: Bitboard,
68-
) -> fmt::Result {
68+
) -> Result<(), W::Error> {
6969
let mut empty = true;
7070

7171
for color in Color::ALL {
@@ -76,7 +76,7 @@ fn fmt_castling(
7676
let candidates = board.by_piece(color.rook()) & color.backrank();
7777

7878
for rook in (castling_rights & color.backrank()).into_iter().rev() {
79-
f.write_char(
79+
f.append_ascii(
8080
if Some(rook) == candidates.first() && king.map_or(false, |k| rook < k) {
8181
color.fold_wb('Q', 'q')
8282
} else if Some(rook) == candidates.last() && king.map_or(false, |k| k < rook) {
@@ -91,42 +91,46 @@ fn fmt_castling(
9191
}
9292

9393
if empty {
94-
f.write_char('-')?;
94+
f.append_ascii('-')?;
9595
}
9696

9797
Ok(())
9898
}
9999

100-
fn fmt_pockets(f: &mut fmt::Formatter<'_>, pockets: &ByColor<ByRole<u8>>) -> fmt::Result {
101-
f.write_char('[')?;
100+
fn append_pockets<W: AppendAscii>(
101+
f: &mut W,
102+
pockets: &ByColor<ByRole<u8>>,
103+
) -> Result<(), W::Error> {
104+
f.append_ascii('[')?;
102105
for color in Color::ALL {
103106
for role in Role::ALL {
104107
let piece = Piece { color, role };
105108
for _ in 0..*pockets.piece(piece) {
106-
f.write_char(piece.char())?;
109+
f.append_ascii(piece.char())?;
107110
}
108111
}
109112
}
110-
f.write_char(']')
113+
f.append_ascii(']')
111114
}
112115

113-
fn fmt_epd(f: &mut fmt::Formatter<'_>, setup: &Setup) -> fmt::Result {
114-
setup.board.board_fen(setup.promoted).fmt(f)?;
116+
fn append_epd<W: AppendAscii>(f: &mut W, setup: &Setup) -> Result<(), W::Error> {
117+
f.reserve(21);
118+
setup.board.board_fen(setup.promoted).append_to(f)?;
115119
if let Some(ref pockets) = setup.pockets {
116-
fmt_pockets(f, pockets)?;
120+
append_pockets(f, pockets)?;
117121
}
118-
f.write_char(' ')?;
119-
f.write_char(setup.turn.char())?;
120-
f.write_char(' ')?;
121-
fmt_castling(f, &setup.board, setup.castling_rights)?;
122-
f.write_char(' ')?;
122+
f.append_ascii(' ')?;
123+
f.append_ascii(setup.turn.char())?;
124+
f.append_ascii(' ')?;
125+
append_castling(f, &setup.board, setup.castling_rights)?;
126+
f.append_ascii(' ')?;
123127
match setup.ep_square {
124-
Some(ref ep_square) => Display::fmt(ep_square, f)?,
125-
None => f.write_char('-')?,
128+
Some(ref ep_square) => ep_square.append_to(f)?,
129+
None => f.append_ascii('-')?,
126130
}
127131
if let Some(ref remaining_checks) = setup.remaining_checks {
128-
f.write_char(' ')?;
129-
Display::fmt(remaining_checks, f)?;
132+
f.append_ascii(' ')?;
133+
remaining_checks.append_to(f)?;
130134
}
131135
Ok(())
132136
}
@@ -272,7 +276,7 @@ impl FromStr for Board {
272276

273277
impl Display for Board {
274278
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
275-
self.board_fen(Bitboard(0)).fmt(f)
279+
self.board_fen(Bitboard(0)).append_to(f)
276280
}
277281
}
278282

@@ -286,39 +290,59 @@ pub struct BoardFen<'b> {
286290
promoted: Bitboard,
287291
}
288292

289-
impl<'b> Display for BoardFen<'b> {
290-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
293+
impl BoardFen<'_> {
294+
fn append_to<W: AppendAscii>(&self, f: &mut W) -> Result<(), W::Error> {
295+
f.reserve(15);
296+
291297
for rank in Rank::ALL.into_iter().rev() {
292-
let mut empty = 0;
298+
let mut prev_file = -1;
293299

294-
for file in File::ALL {
295-
let square = Square::from_coords(file, rank);
300+
for square in self.board.occupied() & rank {
301+
let empty = i32::from(square.file()) - prev_file - 1;
302+
if empty > 0 {
303+
f.append_ascii(char::from(b'0' + empty as u8))?;
304+
}
305+
prev_file = i32::from(square.file());
296306

297-
empty = if let Some(piece) = self.board.piece_at(square) {
298-
if empty > 0 {
299-
f.write_char(char::from_digit(empty, 10).expect("8 files only"))?;
300-
}
301-
f.write_char(piece.char())?;
302-
if self.promoted.contains(square) {
303-
f.write_char('~')?;
304-
}
305-
0
306-
} else {
307-
empty + 1
308-
};
307+
f.append_ascii(self.board.piece_at(square).expect("piece").char())?;
308+
if self.promoted.contains(square) {
309+
f.append_ascii('~')?;
310+
}
309311
}
310312

313+
let empty = i32::from(File::H) - prev_file;
311314
if empty > 0 {
312-
f.write_char(char::from_digit(empty, 10).expect("8 files only"))?;
315+
f.append_ascii(char::from(b'0' + empty as u8))?;
313316
}
314317

315318
if rank > Rank::First {
316-
f.write_char('/')?;
319+
f.append_ascii('/')?;
317320
}
318321
}
319322

320323
Ok(())
321324
}
325+
326+
#[cfg(feature = "alloc")]
327+
pub fn append_to_string(&self, s: &mut alloc::string::String) {
328+
let _ = self.append_to(s);
329+
}
330+
331+
#[cfg(feature = "alloc")]
332+
pub fn append_ascii_to(&self, buf: &mut alloc::vec::Vec<u8>) {
333+
let _ = self.append_to(buf);
334+
}
335+
336+
#[cfg(feature = "std")]
337+
pub fn write_ascii_to<W: std::io::Write>(&self, w: W) -> std::io::Result<()> {
338+
self.append_to(&mut crate::util::WriteAscii(w))
339+
}
340+
}
341+
342+
impl Display for BoardFen<'_> {
343+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
344+
self.append_to(f)
345+
}
322346
}
323347

324348
/// A FEN like `rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1`.
@@ -505,6 +529,29 @@ impl Fen {
505529
pub fn into_position<P: FromSetup>(self, mode: CastlingMode) -> Result<P, PositionError<P>> {
506530
P::from_setup(self.0, mode)
507531
}
532+
533+
fn append_to<W: AppendAscii>(&self, f: &mut W) -> Result<(), W::Error> {
534+
append_epd(f, &self.0)?;
535+
f.append_ascii(' ')?;
536+
f.append_u32(self.0.halfmoves)?;
537+
f.append_ascii(' ')?;
538+
f.append_u32(u32::from(self.0.fullmoves))
539+
}
540+
541+
#[cfg(feature = "alloc")]
542+
pub fn append_to_string(&self, s: &mut alloc::string::String) {
543+
let _ = self.append_to(s);
544+
}
545+
546+
#[cfg(feature = "alloc")]
547+
pub fn append_ascii_to(&self, buf: &mut alloc::vec::Vec<u8>) {
548+
let _ = self.append_to(buf);
549+
}
550+
551+
#[cfg(feature = "std")]
552+
pub fn write_ascii_to<W: std::io::Write>(&self, w: W) -> std::io::Result<()> {
553+
self.append_to(&mut crate::util::WriteAscii(w))
554+
}
508555
}
509556

510557
impl From<Setup> for Fen {
@@ -529,8 +576,7 @@ impl FromStr for Fen {
529576

530577
impl Display for Fen {
531578
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
532-
fmt_epd(f, &self.0)?;
533-
write!(f, " {} {}", self.0.halfmoves, self.0.fullmoves)
579+
self.append_to(f)
534580
}
535581
}
536582

@@ -571,6 +617,25 @@ impl Epd {
571617
pub fn into_position<P: FromSetup>(self, mode: CastlingMode) -> Result<P, PositionError<P>> {
572618
P::from_setup(self.into_setup(), mode)
573619
}
620+
621+
fn append_to<W: AppendAscii>(&self, f: &mut W) -> Result<(), W::Error> {
622+
append_epd(f, &self.0)
623+
}
624+
625+
#[cfg(feature = "alloc")]
626+
pub fn append_to_string(&self, s: &mut alloc::string::String) {
627+
let _ = self.append_to(s);
628+
}
629+
630+
#[cfg(feature = "alloc")]
631+
pub fn append_ascii_to(&self, buf: &mut alloc::vec::Vec<u8>) {
632+
let _ = self.append_to(buf);
633+
}
634+
635+
#[cfg(feature = "std")]
636+
pub fn write_ascii_to<W: std::io::Write>(&self, w: W) -> std::io::Result<()> {
637+
self.append_to(&mut crate::util::WriteAscii(w))
638+
}
574639
}
575640

576641
impl From<Setup> for Epd {
@@ -595,7 +660,7 @@ impl FromStr for Epd {
595660

596661
impl Display for Epd {
597662
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
598-
fmt_epd(f, &self.0)
663+
self.append_to(f)
599664
}
600665
}
601666

src/position.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,19 @@ impl Outcome {
4747
_ => return Err(ParseOutcomeError::Invalid),
4848
})
4949
}
50-
}
5150

52-
impl fmt::Display for Outcome {
53-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54-
f.write_str(match *self {
51+
pub const fn as_str(self) -> &'static str {
52+
match self {
5553
Outcome::Decisive { winner: White } => "1-0",
5654
Outcome::Decisive { winner: Black } => "0-1",
5755
Outcome::Draw => "1/2-1/2",
58-
})
56+
}
57+
}
58+
}
59+
60+
impl fmt::Display for Outcome {
61+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62+
f.write_str(self.as_str())
5963
}
6064
}
6165

0 commit comments

Comments
 (0)