-
Notifications
You must be signed in to change notification settings - Fork 12.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
net: Don't use checked arithmetic when parsing numbers with known max digits #121428
Conversation
I wasn't sure whether to add the functionality to the current parsing function or create a new function. I chose this way to reduce call-site churn. For the benchmarks, I took the values from the tests folder. If there are better suggestions, I have no problem using those instead. I saw a ~20% improvement on benchmarks on my laptop. Before benchmarks:
net::addr_parser::bench_parse_ipaddr_v4 62.00ns/iter +/- 2.00ns
net::addr_parser::bench_parse_ipaddr_v6_compress 211.00ns/iter +/- 9.00ns
net::addr_parser::bench_parse_ipaddr_v6_full 290.00ns/iter +/- 6.00ns
net::addr_parser::bench_parse_ipaddr_v6_v4 194.00ns/iter +/- 6.00ns
net::addr_parser::bench_parse_ipv4 61.00ns/iter +/- 3.00ns
net::addr_parser::bench_parse_ipv6_compress 193.00ns/iter +/- 29.00ns
net::addr_parser::bench_parse_ipv6_full 284.00ns/iter +/- 15.00ns
net::addr_parser::bench_parse_ipv6_v4 183.00ns/iter +/- 6.00ns
net::addr_parser::bench_parse_socket_v4 85.00ns/iter +/- 2.00ns
net::addr_parser::bench_parse_socket_v6 235.00ns/iter +/- 13.00ns
net::addr_parser::bench_parse_socket_v6_scope_id 248.00ns/iter +/- 31.00ns
net::addr_parser::bench_parse_socketaddr_v4 87.00ns/iter +/- 4.00ns
net::addr_parser::bench_parse_socketaddr_v6 254.00ns/iter +/- 10.00ns After benchmarks:
net::addr_parser::bench_parse_ipaddr_v4 48.00ns/iter +/- 1.00ns
net::addr_parser::bench_parse_ipaddr_v6_compress 170.00ns/iter +/- 4.00ns
net::addr_parser::bench_parse_ipaddr_v6_full 227.00ns/iter +/- 4.00ns
net::addr_parser::bench_parse_ipaddr_v6_v4 158.00ns/iter +/- 3.00ns
net::addr_parser::bench_parse_ipv4 45.00ns/iter +/- 2.00ns
net::addr_parser::bench_parse_ipv6_compress 160.00ns/iter +/- 12.00ns
net::addr_parser::bench_parse_ipv6_full 225.00ns/iter +/- 7.00ns
net::addr_parser::bench_parse_ipv6_v4 146.00ns/iter +/- 3.00ns
net::addr_parser::bench_parse_socket_v4 69.00ns/iter +/- 6.00ns
net::addr_parser::bench_parse_socket_v6 210.00ns/iter +/- 11.00ns
net::addr_parser::bench_parse_socket_v6_scope_id 218.00ns/iter +/- 24.00ns
net::addr_parser::bench_parse_socketaddr_v4 71.00ns/iter +/- 3.00ns
net::addr_parser::bench_parse_socketaddr_v6 216.00ns/iter +/- 5.00ns |
library/core/src/net/parser.rs
Outdated
@@ -104,36 +104,62 @@ 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. | |||
fn read_number<T: ReadNumberHelper>( | |||
// | |||
// INVARIANT: `max_digits` must be less than or equal to the number of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It must be strictly less, because you can overflow u32
with the same number of digits as u32::MAX
.
Maybe we should also add a debug_assert!
for this? Just for future proofing to be checked in CI, without affecting release builds.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed the invariant comment and added a debug_assert!
checking that max_digits
is below 10.
I could have used max_digits <= u32::MAX.ilog10()
but that seems less clear for future readers as that rounds down to 9.
arithmetic If `max_digits.is_some()`, then we know we are parsing a `u8` or `u16` because `read_number` is only called with `Some(3)` or `Some(4)`. Both types fit well within a `u32` without risk of overflow. Thus, we can use plain arithmetic to avoid extra instructions from `checked_mul` and `checked_add`.
Add benches for IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, and SocketAddrV6 parsing
Thanks! @bors r+ |
@bors rollup=never (due to perf implications) |
☀️ Test successful - checks-actions |
Finished benchmarking commit (96561a8): comparison URL. Overall result: no relevant changes - no action needed@rustbot label: -perf-regression Instruction countThis benchmark run did not return any relevant results for this metric. Max RSS (memory usage)This benchmark run did not return any relevant results for this metric. CyclesResultsThis is a less reliable metric that may be of interest but was not used to determine the overall result at the top of this comment.
Binary sizeThis benchmark run did not return any relevant results for this metric. Bootstrap: 644.654s -> 644.518s (-0.02%) |
Add a branch to
Parser::read_number
that determines whether checked or regular arithmetic is used.max_digits.is_some()
, then we know we are parsing au8
oru16
becauseread_number
is only called withSome(3)
orSome(4)
. Both types fit within au32
without risk of overflow. Thus, we can use plain arithmetic to avoid extra instructions fromchecked_mul
andchecked_add
.Add benches for
IpAddr
,Ipv4Addr
,Ipv6Addr
,SocketAddr
,SocketAddrV4
, andSocketAddrV6
parsing