Skip to content
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

Merged
merged 2 commits into from
Mar 5, 2024

Conversation

okaneco
Copy link
Contributor

@okaneco okaneco commented Feb 22, 2024

Add a branch to Parser::read_number that determines whether checked or regular arithmetic is used.

  • 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 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

@rustbot
Copy link
Collaborator

rustbot commented Feb 22, 2024

r? @cuviper

rustbot has assigned @cuviper.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Feb 22, 2024
@okaneco
Copy link
Contributor Author

okaneco commented Feb 22, 2024

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

@@ -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
Copy link
Member

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.

Copy link
Contributor Author

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
@cuviper
Copy link
Member

cuviper commented Mar 4, 2024

Thanks!

@bors r+

@bors
Copy link
Contributor

bors commented Mar 4, 2024

📌 Commit 69637c9 has been approved by cuviper

It is now in the queue for this repository.

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Mar 4, 2024
@jhpratt
Copy link
Member

jhpratt commented Mar 5, 2024

@bors rollup=never

(due to perf implications)

@bors
Copy link
Contributor

bors commented Mar 5, 2024

⌛ Testing commit 69637c9 with merge 96561a8...

@bors
Copy link
Contributor

bors commented Mar 5, 2024

☀️ Test successful - checks-actions
Approved by: cuviper
Pushing 96561a8 to master...

@bors bors added the merged-by-bors This PR was explicitly merged by bors. label Mar 5, 2024
@bors bors merged commit 96561a8 into rust-lang:master Mar 5, 2024
12 checks passed
@rustbot rustbot added this to the 1.78.0 milestone Mar 5, 2024
@okaneco okaneco deleted the ipaddr_parse branch March 5, 2024 18:15
@rust-timer
Copy link
Collaborator

Finished benchmarking commit (96561a8): comparison URL.

Overall result: no relevant changes - no action needed

@rustbot label: -perf-regression

Instruction count

This 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.

Cycles

Results

This 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.

mean range count
Regressions ❌
(primary)
- - 0
Regressions ❌
(secondary)
2.1% [2.1%, 2.1%] 1
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
-2.9% [-2.9%, -2.9%] 1
All ❌✅ (primary) - - 0

Binary size

This benchmark run did not return any relevant results for this metric.

Bootstrap: 644.654s -> 644.518s (-0.02%)
Artifact size: 175.03 MiB -> 175.01 MiB (-0.01%)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
merged-by-bors This PR was explicitly merged by bors. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-libs Relevant to the library team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants