From a69960a4ecf5d452bcebae3f75d056fd6c451a6e Mon Sep 17 00:00:00 2001 From: Giles Cope Date: Fri, 26 Feb 2021 23:04:42 +0000 Subject: [PATCH 1/6] u8::to_string() specialisation (far less asm). --- library/alloc/src/string.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/library/alloc/src/string.rs b/library/alloc/src/string.rs index b1f860d6b64a8..1cdfa39cd7984 100644 --- a/library/alloc/src/string.rs +++ b/library/alloc/src/string.rs @@ -2224,6 +2224,25 @@ impl ToString for char { } } +#[stable(feature = "u8_to_string_specialization", since="1.999.0")] +impl ToString for u8 { + #[inline] + fn to_string(&self) -> String { + let mut result = String::with_capacity(3); + let mut n = *self; + if n >= 100 { + result.push((b'0' + n / 100) as char); + n %= 100; + } + if !result.is_empty() || n >= 10 { + result.push((b'0' + n / 10) as char); + n %= 10; + }; + result.push((b'0' + n) as char); + result + } +} + #[stable(feature = "str_to_string_specialization", since = "1.9.0")] impl ToString for str { #[inline] From d07c43af3129c8e160bb62d98ebcb8b9cf6f3ccd Mon Sep 17 00:00:00 2001 From: Giles Cope Date: Thu, 4 Mar 2021 07:04:21 +0000 Subject: [PATCH 2/6] Alternative LUT rather than dividing. --- library/alloc/src/string.rs | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/library/alloc/src/string.rs b/library/alloc/src/string.rs index 1cdfa39cd7984..714c24a766585 100644 --- a/library/alloc/src/string.rs +++ b/library/alloc/src/string.rs @@ -2224,7 +2224,7 @@ impl ToString for char { } } -#[stable(feature = "u8_to_string_specialization", since="1.999.0")] +#[stable(feature = "u8_to_string_specialization", since = "1.999.0")] impl ToString for u8 { #[inline] fn to_string(&self) -> String { @@ -2243,6 +2243,39 @@ impl ToString for u8 { } } +// 2 digit decimal look up table +static DEC_DIGITS_LUT: &[u8; 200] = b"0001020304050607080910111213141516171819\ + 2021222324252627282930313233343536373839\ + 4041424344454647484950515253545556575859\ + 6061626364656667686970717273747576777879\ + 8081828384858687888990919293949596979899"; + +#[stable(feature = "i8_to_string_specialization", since = "1.999.0")] +impl ToString for i8 { + #[inline] + fn to_string(&self) -> String { + let mut vec: Vec = if *self < 0 { + let mut v = Vec::with_capacity(4); + v.push(b'-'); + v + } else { + Vec::with_capacity(3) + }; + let mut n = self.abs(); + if n >= 10 { + if n >= 100 { + n -= 100; + vec.push(b'1'); + } + let nn = n * 2; + vec.extend_from_slice(&DEC_DIGITS_LUT[nn as usize..=nn as usize + 1]); + } else { + vec.push(b'0' + (n as u8)); + } + unsafe { String::from_utf8_unchecked(vec) } + } +} + #[stable(feature = "str_to_string_specialization", since = "1.9.0")] impl ToString for str { #[inline] From a678b9a2ae6fdca2bfa7aed8f73723a7cf238d16 Mon Sep 17 00:00:00 2001 From: Giles Cope Date: Thu, 4 Mar 2021 22:11:04 +0000 Subject: [PATCH 3/6] less uB in i8 --- library/alloc/src/string.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/library/alloc/src/string.rs b/library/alloc/src/string.rs index 714c24a766585..e80f14eaca8c3 100644 --- a/library/alloc/src/string.rs +++ b/library/alloc/src/string.rs @@ -2254,14 +2254,18 @@ static DEC_DIGITS_LUT: &[u8; 200] = b"0001020304050607080910111213141516171819\ impl ToString for i8 { #[inline] fn to_string(&self) -> String { - let mut vec: Vec = if *self < 0 { + let mut n = *self; + let mut vec: Vec = if n < 0 { + // convert the negative num to positive by summing 1 to it's 2 complement + // ( -128u8.abs() would panic ) + n = (!n).wrapping_add(1); let mut v = Vec::with_capacity(4); v.push(b'-'); v } else { Vec::with_capacity(3) }; - let mut n = self.abs(); + let mut n = n as u8; if n >= 10 { if n >= 100 { n -= 100; From e83378b55f15d3b200111c9de066ea80f51c1b80 Mon Sep 17 00:00:00 2001 From: Giles Cope Date: Sun, 7 Mar 2021 22:08:22 +0000 Subject: [PATCH 4/6] vec![0;4] is a fast path. After much tweaking found a way to get similar asm size as the u8 to_string implementation. --- library/alloc/src/string.rs | 42 ++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/library/alloc/src/string.rs b/library/alloc/src/string.rs index e80f14eaca8c3..14bf6eead0540 100644 --- a/library/alloc/src/string.rs +++ b/library/alloc/src/string.rs @@ -2243,39 +2243,37 @@ impl ToString for u8 { } } -// 2 digit decimal look up table -static DEC_DIGITS_LUT: &[u8; 200] = b"0001020304050607080910111213141516171819\ - 2021222324252627282930313233343536373839\ - 4041424344454647484950515253545556575859\ - 6061626364656667686970717273747576777879\ - 8081828384858687888990919293949596979899"; - #[stable(feature = "i8_to_string_specialization", since = "1.999.0")] impl ToString for i8 { #[inline] fn to_string(&self) -> String { - let mut n = *self; - let mut vec: Vec = if n < 0 { - // convert the negative num to positive by summing 1 to it's 2 complement - // ( -128u8.abs() would panic ) - n = (!n).wrapping_add(1); - let mut v = Vec::with_capacity(4); - v.push(b'-'); - v + let mut vec = vec![0; 4]; + let n = *self; + let mut free = 0; + let mut n: u8 = if n.is_negative() { + vec[free] = b'-'; + free += 1; + i8::unsigned_abs(n) } else { - Vec::with_capacity(3) + n as u8 }; - let mut n = n as u8; if n >= 10 { if n >= 100 { n -= 100; - vec.push(b'1'); + vec[free] = b'1'; + free += 1; } - let nn = n * 2; - vec.extend_from_slice(&DEC_DIGITS_LUT[nn as usize..=nn as usize + 1]); - } else { - vec.push(b'0' + (n as u8)); + debug_assert!(n < 100); + vec[free] = b'0' + n / 10; + free += 1; + n %= 10; } + debug_assert!(n < 10); + vec[free] = b'0' + n; + free += 1; + vec.truncate(free); + + // SAFETY: Vec only contains ascii so valid utf8 unsafe { String::from_utf8_unchecked(vec) } } } From 6a58b6af3231505344d450fba99a50c1d5c5ec01 Mon Sep 17 00:00:00 2001 From: Squirrel Date: Mon, 8 Mar 2021 20:51:27 +0000 Subject: [PATCH 5/6] Update library/alloc/src/string.rs Co-authored-by: LingMan --- library/alloc/src/string.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/library/alloc/src/string.rs b/library/alloc/src/string.rs index 14bf6eead0540..7292ebdaeec49 100644 --- a/library/alloc/src/string.rs +++ b/library/alloc/src/string.rs @@ -2248,15 +2248,12 @@ impl ToString for i8 { #[inline] fn to_string(&self) -> String { let mut vec = vec![0; 4]; - let n = *self; let mut free = 0; - let mut n: u8 = if n.is_negative() { + if self.is_negative() { vec[free] = b'-'; free += 1; - i8::unsigned_abs(n) - } else { - n as u8 - }; + } + let mut n = self.unsigned_abs(); if n >= 10 { if n >= 100 { n -= 100; From 05330aaf42c794d441a91dc261e3202f965e0ce2 Mon Sep 17 00:00:00 2001 From: Giles Cope Date: Mon, 8 Mar 2021 22:35:37 +0000 Subject: [PATCH 6/6] Closer similarities. --- library/alloc/src/string.rs | 42 ++++++++++++++----------------------- 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/library/alloc/src/string.rs b/library/alloc/src/string.rs index 7292ebdaeec49..8b0f3047a0a8b 100644 --- a/library/alloc/src/string.rs +++ b/library/alloc/src/string.rs @@ -2228,18 +2228,18 @@ impl ToString for char { impl ToString for u8 { #[inline] fn to_string(&self) -> String { - let mut result = String::with_capacity(3); + let mut buf = String::with_capacity(3); let mut n = *self; - if n >= 100 { - result.push((b'0' + n / 100) as char); - n %= 100; - } - if !result.is_empty() || n >= 10 { - result.push((b'0' + n / 10) as char); + if n >= 10 { + if n >= 100 { + buf.push((b'0' + n / 100) as char); + n %= 100; + } + buf.push((b'0' + n / 10) as char); n %= 10; - }; - result.push((b'0' + n) as char); - result + } + buf.push((b'0' + n) as char); + buf } } @@ -2247,31 +2247,21 @@ impl ToString for u8 { impl ToString for i8 { #[inline] fn to_string(&self) -> String { - let mut vec = vec![0; 4]; - let mut free = 0; + let mut buf = String::with_capacity(4); if self.is_negative() { - vec[free] = b'-'; - free += 1; + buf.push('-'); } let mut n = self.unsigned_abs(); if n >= 10 { if n >= 100 { + buf.push('1'); n -= 100; - vec[free] = b'1'; - free += 1; } - debug_assert!(n < 100); - vec[free] = b'0' + n / 10; - free += 1; + buf.push((b'0' + n / 10) as char); n %= 10; } - debug_assert!(n < 10); - vec[free] = b'0' + n; - free += 1; - vec.truncate(free); - - // SAFETY: Vec only contains ascii so valid utf8 - unsafe { String::from_utf8_unchecked(vec) } + buf.push((b'0' + n) as char); + buf } }