From d4bea3797042dd8cc669ba01bd7ebe1403a9e077 Mon Sep 17 00:00:00 2001 From: Niklas Fiekas Date: Tue, 16 Jun 2026 23:08:17 +0200 Subject: [PATCH 1/2] Expand network address parser benchmark suite --- library/coretests/benches/net/addr_parser.rs | 176 ++++++++++++++++--- 1 file changed, 155 insertions(+), 21 deletions(-) diff --git a/library/coretests/benches/net/addr_parser.rs b/library/coretests/benches/net/addr_parser.rs index bbf2ea3eb9796..d44ea98bfcbec 100644 --- a/library/coretests/benches/net/addr_parser.rs +++ b/library/coretests/benches/net/addr_parser.rs @@ -3,76 +3,210 @@ use core::str::FromStr; use test::{Bencher, black_box}; -const IPV4_STR: &str = "192.168.0.1"; -const IPV4_STR_PORT: &str = "192.168.0.1:8080"; - -const IPV6_STR_FULL: &str = "2001:db8:0:0:0:0:c0a8:1"; -const IPV6_STR_COMPRESS: &str = "2001:db8::c0a8:1"; -const IPV6_STR_V4: &str = "2001:db8::192.168.0.1"; -const IPV6_STR_PORT: &str = "[2001:db8::c0a8:1]:8080"; -const IPV6_STR_PORT_SCOPE_ID: &str = "[2001:db8::c0a8:1%1337]:8080"; +const IPV4: &[&str] = &[ + "192.168.0.1", + "8.8.8.8", + "127.0.0.1", + "255.255.255.255", + "0.0.0.0", + "10.0.0.1", + "203.0.113.42", + "172.16.254.1", + "100.64.0.1", + "1.2.3.4", +]; + +const IPV4_PORT: &[&str] = &[ + "192.168.0.1:8080", + "8.8.8.8:53", + "127.0.0.1:65535", + "255.255.255.255:1", + "0.0.0.0:80", + "10.0.0.1:443", + "203.0.113.42:22", + "172.16.254.1:3306", + "100.64.0.1:8443", + "1.2.3.4:0", +]; + +const IPV6_FULL: &[&str] = &[ + "2001:db8:0:0:0:0:c0a8:1", + "2001:db8:85a3:8d3:1319:8a2e:370:7348", + "fe80:0:0:0:0:0:0:1", + "ff02:0:0:0:0:0:0:101", + "2001:4860:4860:0:0:0:0:8888", + "2606:4700:4700:0:0:0:0:1111", + "fd00:0:0:0:0:0:0:1", + "fec0:0:0:0:0:0:0:abcd", + "1:2:3:4:5:6:7:8", + "abcd:ef01:2345:6789:abcd:ef01:2345:6789", +]; + +const IPV6_COMPRESS: &[&str] = &[ + "2001:db8::c0a8:1", + "::1", + "fe80::1", + "2001:db8::", + "ff02::1:2", + "64:ff9b::", + "::", + "2001:4860:4860::8888", + "2606:4700:4700::1111", + "fe80::1ff:fe23:4567:890a", +]; + +const IPV6_V4: &[&str] = &[ + "2001:db8::192.168.0.1", + "::ffff:192.168.0.1", + "64:ff9b::192.0.2.33", + "::ffff:8.8.8.8", + "::192.168.0.1", + "::ffff:255.255.255.255", + "2001:db8:0:0:0:0:192.168.0.1", + "64:ff9b::10.0.0.1", + "::ffff:1.2.3.4", + "::ffff:127.0.0.1", +]; + +const IPV6_PORT: &[&str] = &[ + "[2001:db8::c0a8:1]:8080", + "[::1]:443", + "[fe80::1]:53", + "[2001:db8:85a3:8d3:1319:8a2e:370:7348]:22", + "[::]:80", + "[2001:4860:4860::8888]:443", + "[2606:4700:4700::1111]:53", + "[fe80::1ff:fe23:4567:890a]:8080", + "[64:ff9b::192.0.2.33]:443", + "[::ffff:8.8.8.8]:53", +]; + +const IPV6_PORT_SCOPE_ID: &[&str] = &[ + "[2001:db8::c0a8:1%1337]:8080", + "[fe80::1%1]:53", + "[fe80::1%999999]:443", + "[fe80::1%0]:80", + "[fe80::1ff:fe23:4567:890a%2]:8080", + "[::1%1]:443", + "[fe80::abcd%15]:22", + "[ff02::1%42]:5353", + "[fe80::1%4294967295]:443", + "[2001:db8::1%100]:8080", +]; #[bench] fn bench_parse_ipv4(b: &mut Bencher) { - b.iter(|| Ipv4Addr::from_str(black_box(IPV4_STR))); + b.iter(|| { + for s in IPV4 { + let _ = black_box(Ipv4Addr::from_str(black_box(s))); + } + }); } #[bench] fn bench_parse_ipv6_full(b: &mut Bencher) { - b.iter(|| Ipv6Addr::from_str(black_box(IPV6_STR_FULL))); + b.iter(|| { + for s in IPV6_FULL { + let _ = black_box(Ipv6Addr::from_str(black_box(s))); + } + }); } #[bench] fn bench_parse_ipv6_compress(b: &mut Bencher) { - b.iter(|| Ipv6Addr::from_str(black_box(IPV6_STR_COMPRESS))); + b.iter(|| { + for s in IPV6_COMPRESS { + let _ = black_box(Ipv6Addr::from_str(black_box(s))); + } + }); } #[bench] fn bench_parse_ipv6_v4(b: &mut Bencher) { - b.iter(|| Ipv6Addr::from_str(black_box(IPV6_STR_V4))); + b.iter(|| { + for s in IPV6_V4 { + let _ = black_box(Ipv6Addr::from_str(black_box(s))); + } + }); } #[bench] fn bench_parse_ipaddr_v4(b: &mut Bencher) { - b.iter(|| IpAddr::from_str(black_box(IPV4_STR))); + b.iter(|| { + for s in IPV4 { + let _ = black_box(IpAddr::from_str(black_box(s))); + } + }); } #[bench] fn bench_parse_ipaddr_v6_full(b: &mut Bencher) { - b.iter(|| IpAddr::from_str(black_box(IPV6_STR_FULL))); + b.iter(|| { + for s in IPV6_FULL { + let _ = black_box(IpAddr::from_str(black_box(s))); + } + }); } #[bench] fn bench_parse_ipaddr_v6_compress(b: &mut Bencher) { - b.iter(|| IpAddr::from_str(black_box(IPV6_STR_COMPRESS))); + b.iter(|| { + for s in IPV6_COMPRESS { + let _ = black_box(IpAddr::from_str(black_box(s))); + } + }); } #[bench] fn bench_parse_ipaddr_v6_v4(b: &mut Bencher) { - b.iter(|| IpAddr::from_str(black_box(IPV6_STR_V4))); + b.iter(|| { + for s in IPV6_V4 { + let _ = black_box(IpAddr::from_str(black_box(s))); + } + }); } #[bench] fn bench_parse_socket_v4(b: &mut Bencher) { - b.iter(|| SocketAddrV4::from_str(black_box(IPV4_STR_PORT))); + b.iter(|| { + for s in IPV4_PORT { + let _ = black_box(SocketAddrV4::from_str(black_box(s))); + } + }); } #[bench] fn bench_parse_socket_v6(b: &mut Bencher) { - b.iter(|| SocketAddrV6::from_str(black_box(IPV6_STR_PORT))); + b.iter(|| { + for s in IPV6_PORT { + let _ = black_box(SocketAddrV6::from_str(black_box(s))); + } + }); } #[bench] fn bench_parse_socket_v6_scope_id(b: &mut Bencher) { - b.iter(|| SocketAddrV6::from_str(black_box(IPV6_STR_PORT_SCOPE_ID))); + b.iter(|| { + for s in IPV6_PORT_SCOPE_ID { + let _ = black_box(SocketAddrV6::from_str(black_box(s))); + } + }); } #[bench] fn bench_parse_socketaddr_v4(b: &mut Bencher) { - b.iter(|| SocketAddr::from_str(black_box(IPV4_STR_PORT))); + b.iter(|| { + for s in IPV4_PORT { + let _ = black_box(SocketAddr::from_str(black_box(s))); + } + }); } #[bench] fn bench_parse_socketaddr_v6(b: &mut Bencher) { - b.iter(|| SocketAddr::from_str(black_box(IPV6_STR_PORT))); + b.iter(|| { + for s in IPV6_PORT { + let _ = black_box(SocketAddr::from_str(black_box(s))); + } + }); } From 1d5426573496501457364d1d20d443d7472a234f Mon Sep 17 00:00:00 2001 From: Niklas Fiekas Date: Wed, 17 Jun 2026 22:49:27 +0200 Subject: [PATCH 2/2] Optimize network address parser Split `read_number()` into seperate methods for the the `max_digits` `Some(_)` and `None` cases. Hoist reading the first digit which is always required. The path for unlimited digits then no longer has to count digits at all. --- library/core/src/net/parser.rs | 97 +++++++++++++++------------------- 1 file changed, 44 insertions(+), 53 deletions(-) diff --git a/library/core/src/net/parser.rs b/library/core/src/net/parser.rs index 3aab24a90d817..a6473735dd455 100644 --- a/library/core/src/net/parser.rs +++ b/library/core/src/net/parser.rs @@ -63,11 +63,6 @@ impl<'a> Parser<'a> { if self.state.is_empty() { result } else { None }.ok_or(AddrParseError(kind)) } - /// Peek the next character from the input - fn peek_char(&self) -> Option { - self.state.first().map(|&b| char::from(b)) - } - /// Reads the next character from the input fn read_char(&mut self) -> Option { self.state.split_first().map(|(&b, tail)| { @@ -100,60 +95,56 @@ impl<'a> Parser<'a> { }) } - // Read a number off the front of the input in the given radix, stopping - // at the first non-digit character or eof. Fails if the number has more - // digits than max_digits or if there is no number. - // - // INVARIANT: `max_digits` must be less than the number of digits that `u32` - // can represent. - fn read_number>( + /// Reads a number off the front of the input in the given radix, stopping at the first + /// non-digit character or eof. Fails if the number has more digits than `max_digits`, if there + /// is no number, if the number overflows `T`, or if there are leading zeros but + /// `allow_zero_prefix` is false. + /// + /// `max_digits` must be in 1..=6. + fn read_radix_max_digits>( &mut self, radix: u32, - max_digits: Option, + max_digits: u32, allow_zero_prefix: bool, ) -> Option { - self.read_atomically(move |p| { - let mut digit_count = 0; - let has_leading_zero = p.peek_char() == Some('0'); - - // If max_digits.is_some(), then we are parsing a `u8` or `u16` and - // don't need to use checked arithmetic since it fits within a `u32`. - let result = if let Some(max_digits) = max_digits { - // u32::MAX = 4_294_967_295u32, which is 10 digits long. - // `max_digits` must be less than 10 to not overflow a `u32`. - debug_assert!(max_digits < 10); - - let mut result = 0_u32; - while let Some(digit) = p.read_atomically(|p| p.read_char()?.to_digit(radix)) { - result *= radix; - result += digit; - digit_count += 1; - - if digit_count > max_digits { - return None; - } - } - - result.try_into().ok() - } else { - let mut result = T::ZERO; + debug_assert!(1 <= max_digits); + debug_assert!(max_digits <= 6); // Works for any radix in u32 + self.read_atomically(|p| { + let first = p.read_char()?.to_digit(radix)?; + let mut result = first; + let mut digit_count = 1; - while let Some(digit) = p.read_atomically(|p| p.read_char()?.to_digit(radix)) { - result = result.checked_mul(radix)?; - result = result.checked_add(digit)?; - digit_count += 1; + while let Some(digit) = p.read_atomically(|p| p.read_char()?.to_digit(radix)) { + if digit_count >= max_digits { + return None; } + result *= radix; + result += digit; + digit_count += 1; + } - Some(result) - }; - - if digit_count == 0 { - None - } else if !allow_zero_prefix && has_leading_zero && digit_count > 1 { + if !allow_zero_prefix && first == 0 && digit_count > 1 { None } else { - result + result.try_into().ok() + } + }) + } + + /// Reads a decimal number off the front of the input, stopping at the first non-digit character + /// or eof. Fails if there is no number, or if the number overflows `T`. Allows an arbitrary + /// amount of leading zeros. + fn read_decimal(&mut self) -> Option { + self.read_atomically(|p| { + let first = p.read_char()?.to_digit(10)?; + let mut result = T::ZERO.checked_add(first)?; + + while let Some(digit) = p.read_atomically(|p| p.read_char()?.to_digit(10)) { + result = result.checked_mul(10)?; + result = result.checked_add(digit)?; } + + Some(result) }) } @@ -166,7 +157,7 @@ impl<'a> Parser<'a> { *slot = p.read_separator('.', i, |p| { // Disallow octal number in IP string. // https://tools.ietf.org/html/rfc6943#section-3.1.1 - p.read_number(10, Some(3), false) + p.read_radix_max_digits(10, 3, false) })?; } @@ -198,7 +189,7 @@ impl<'a> Parser<'a> { } } - let group = p.read_separator(':', i, |p| p.read_number(16, Some(4), true)); + let group = p.read_separator(':', i, |p| p.read_radix_max_digits(16, 4, true)); match group { Some(g) => *slot = g, @@ -250,7 +241,7 @@ impl<'a> Parser<'a> { fn read_port(&mut self) -> Option { self.read_atomically(|p| { p.read_given_char(':')?; - p.read_number(10, None, true) + p.read_decimal() }) } @@ -258,7 +249,7 @@ impl<'a> Parser<'a> { fn read_scope_id(&mut self) -> Option { self.read_atomically(|p| { p.read_given_char('%')?; - p.read_number(10, None, true) + p.read_decimal() }) }