Skip to content

chore: Avoid Box<dyn CongestionControl>#3451

Merged
larseggert merged 3 commits into
mozilla:mainfrom
larseggert:chore-cc-no-box
May 11, 2026
Merged

chore: Avoid Box<dyn CongestionControl>#3451
larseggert merged 3 commits into
mozilla:mainfrom
larseggert:chore-cc-no-box

Conversation

@larseggert
Copy link
Copy Markdown
Collaborator

For discussion - is this worth it?

(The extra dep could be avoided if we implement a dispatch macro ourselves.)

@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 13, 2026

Codecov Report

❌ Patch coverage is 85.55556% with 13 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.88%. Comparing base (143849d) to head (899fe67).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3451      +/-   ##
==========================================
- Coverage   95.03%   94.88%   -0.15%     
==========================================
  Files         109      115       +6     
  Lines       37633    38049     +416     
  Branches    37633    38049     +416     
==========================================
+ Hits        35764    36103     +339     
- Misses       1178     1243      +65     
- Partials      691      703      +12     
Flag Coverage Δ
freebsd 94.00% <85.55%> (-0.15%) ⬇️
linux 95.03% <85.55%> (-0.01%) ⬇️
macos 94.97% <85.55%> (-0.02%) ⬇️
windows 95.01% <85.55%> (-0.02%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
neqo-common 98.61% <ø> (ø)
neqo-http3 93.92% <ø> (ø)
neqo-qpack 95.14% <ø> (ø)
neqo-transport 95.65% <85.55%> (-0.01%) ⬇️
neqo-udp 84.90% <ø> (ø)
mtu 86.61% <ø> (ø)

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Mar 13, 2026

Merging this PR will not alter performance

✅ 24 untouched benchmarks
⏩ 27 skipped benchmarks1


Comparing larseggert:chore-cc-no-box (899fe67) with main (143849d)

Open in CodSpeed

Footnotes

  1. 27 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

Comment thread neqo-transport/src/cc/mod.rs Outdated
@larseggert larseggert marked this pull request as ready for review March 13, 2026 13:03
Copilot AI review requested due to automatic review settings March 13, 2026 13:03
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Refactors neqo-transport congestion control dispatch to avoid Box<dyn CongestionControl> in the packet sending hot path by using a concrete enum-based dispatcher.

Changes:

  • Replace PacketSender’s Box<dyn CongestionControl> with a concrete CongestionController enum and update construction accordingly.
  • Introduce enum_dispatch-based forwarding for the CongestionControl trait and add a CongestionController enum for NewReno/Cubic.
  • Update congestion-control tests to work with the new enum dispatch approach (including relocating cwnd_initial() test-only access).

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated no comments.

Show a summary per file
File Description
neqo-transport/src/sender.rs Switch sender congestion control from boxed trait object to CongestionController enum.
neqo-transport/src/cc/mod.rs Add enum_dispatch usage and define CongestionController enum implementing CongestionControl.
neqo-transport/src/cc/classic_cc.rs Adjust test helpers/coverage for enum dispatch; move cwnd_initial() to a test-only inherent method.
neqo-transport/Cargo.toml Add enum_dispatch as a crate dependency.
Cargo.toml Add enum_dispatch to workspace dependencies.
Cargo.lock Lockfile update for the new dependency.

You can also share your feedback on Copilot code review. Take the survey.

@larseggert
Copy link
Copy Markdown
Collaborator Author

@mxinden worth it?

@mxinden
Copy link
Copy Markdown
Member

mxinden commented Mar 13, 2026

I assume the goal is an improvement in performance? Unless we have some indicator that this patch actually does improve performance, I don't think this patch is worth the added complexity.

@larseggert
Copy link
Copy Markdown
Collaborator Author

Well, the transfer benches seem a bit faster. Let's see if the run after the rebase also shows +3%.

@github-actions
Copy link
Copy Markdown
Contributor

Client/server transfer results

Performance differences relative to 2fbd329.

Transfer of 33554432 bytes over loopback, min. 100 runs. All unit-less numbers are in milliseconds.

Client vs. server (params) Mean ± σ Min Max MiB/s ± σ Δ main Δ main
neqo-neqo-cubic 97.1 ± 4.1 89.8 106.8 329.7 ± 7.8 💔 2.2 2.4%
neqo-neqo-newreno-nopacing 96.2 ± 4.3 88.0 103.6 332.8 ± 7.4 💔 1.5 1.6%
neqo-s2n-cubic 218.8 ± 4.1 212.3 232.2 146.2 ± 7.8 💔 1.3 0.6%
quiche-neqo-cubic 180.2 ± 5.3 172.3 207.6 177.6 ± 6.0 💚 -2.3 -1.2%

Table above only shows statistically significant changes. See all results below.

All results

Transfer of 33554432 bytes over loopback, min. 100 runs. All unit-less numbers are in milliseconds.

Client vs. server (params) Mean ± σ Min Max MiB/s ± σ Δ main Δ main
google-google-nopacing 451.0 ± 3.7 445.0 460.7 71.0 ± 8.6
google-neqo-cubic 271.2 ± 4.7 262.8 282.7 118.0 ± 6.8 1.0 0.4%
msquic-msquic-nopacing 139.1 ± 54.3 112.0 548.5 230.1 ± 0.6
msquic-neqo-cubic 166.4 ± 38.8 127.5 355.2 192.3 ± 0.8 7.4 4.7%
neqo-google-cubic 750.5 ± 4.9 742.8 770.1 42.6 ± 6.5 -0.4 -0.0%
neqo-msquic-cubic 154.4 ± 4.1 147.7 163.3 207.3 ± 7.8 0.2 0.1%
neqo-neqo-cubic 97.1 ± 4.1 89.8 106.8 329.7 ± 7.8 💔 2.2 2.4%
neqo-neqo-cubic-nopacing 96.1 ± 4.2 89.3 103.4 333.0 ± 7.6 -0.3 -0.3%
neqo-neqo-newreno 97.4 ± 4.5 88.1 106.8 328.7 ± 7.1 0.7 0.7%
neqo-neqo-newreno-nopacing 96.2 ± 4.3 88.0 103.6 332.8 ± 7.4 💔 1.5 1.6%
neqo-quiche-cubic 191.2 ± 4.5 184.5 215.0 167.4 ± 7.1 1.1 0.6%
neqo-s2n-cubic 218.8 ± 4.1 212.3 232.2 146.2 ± 7.8 💔 1.3 0.6%
quiche-neqo-cubic 180.2 ± 5.3 172.3 207.6 177.6 ± 6.0 💚 -2.3 -1.2%
quiche-quiche-nopacing 144.0 ± 5.5 136.2 173.9 222.3 ± 5.8
s2n-neqo-cubic 220.1 ± 4.4 211.0 229.1 145.4 ± 7.3 -1.2 -0.5%
s2n-s2n-nopacing 295.8 ± 22.0 281.4 394.2 108.2 ± 1.5

Download data for profiler.firefox.com or download performance comparison data.

Copy link
Copy Markdown
Member

@martinthomson martinthomson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@larseggert do you want to push on this? I've taken a look at enum_dispatch and it's pretty complicated, but I see no warning flags. As it is only run at compile time, I think we're pretty safe, provided that someone spends a little more time than I did checking that the crate isn't poisoned somehow.

@larseggert
Copy link
Copy Markdown
Collaborator Author

@martinthomson should we just define our own dispatch macro and not depend on enum_dispatch? I looked into it before and it was like ten lines or so.

Copy link
Copy Markdown
Member

@martinthomson martinthomson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this is pretty reasonable. A simpler proc macro than enum_dispatch would have made this easier, but we don't need to go to those lengths for just one of these.

For discussion - is this worth it?

(The extra dep could be avoided if we implement a dispatch macro ourselves.)
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean approach. Replacing Box<dyn CongestionController> with a concrete enum + hand-rolled dispatch macro eliminates heap allocation and vtable indirection on the per-packet hot path, with no new dependencies (the earlier enum_dispatch crate has been removed in favor of the neqo_common::dispatch! macro, addressing the earlier review concern).

The trade-off is ~80 lines of manual trait forwarding that must be kept in sync with CongestionController. This is compiler-enforced (missing methods won't compile), so it's safe, just verbose. For a single enum this is reasonable — a proc macro would only pay off if more enums need the same treatment.

The strum::Display derive with #[strum(to_string = "{0}")] delegates formatting to the inner ClassicCongestionController, which already implements Display — consistent with the existing strum usage in this module.

Overall LGTM.

Comment thread neqo-common/src/lib.rs
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean refactoring. Replacing Box<dyn CongestionController> with a concrete enum eliminates heap allocation and vtable indirection on the per-packet hot path, and the hand-rolled neqo_common::dispatch! macro keeps the forwarding boilerplate manageable without adding a new dependency.

Moving cwnd_initial from the trait to a #[cfg(test)] inherent method on ClassicCongestionController is the right call — it avoids polluting the dispatch enum with test-only plumbing while keeping the tests unchanged (they already construct the concrete type directly).

One minor optional suggestion inline on the strum::Display derive.

Comment thread neqo-transport/src/cc/mod.rs
@github-actions
Copy link
Copy Markdown
Contributor

Benchmark results

No significant performance differences relative to 143849d.

All results
transfer/1-conn/1-100mb-resp (aka. Download): No change in performance detected.
       time:   [189.14 ms 189.56 ms 190.04 ms]
       thrpt:  [526.21 MiB/s 527.54 MiB/s 528.71 MiB/s]
change:
       time:   [-0.5748% -0.2850% +0.0299] (p = 0.06 > 0.05)
       thrpt:  [-0.0299% +0.2858% +0.5781]
       No change in performance detected.
Found 2 outliers among 100 measurements (2.00%)
1 (1.00%) high mild
1 (1.00%) high severe
transfer/1-conn/10_000-parallel-1b-resp (aka. RPS): No change in performance detected.
       time:   [286.93 ms 288.95 ms 291.00 ms]
       thrpt:  [34.365 Kelem/s 34.608 Kelem/s 34.851 Kelem/s]
change:
       time:   [-1.1392% -0.1968% +0.7458] (p = 0.69 > 0.05)
       thrpt:  [-0.7402% +0.1972% +1.1523]
       No change in performance detected.
transfer/1-conn/1-1b-resp (aka. HPS): No change in performance detected.
       time:   [38.688 ms 38.862 ms 39.056 ms]
       thrpt:  [25.604   B/s 25.732   B/s 25.848   B/s]
change:
       time:   [-0.5096% +0.1255% +0.8180] (p = 0.70 > 0.05)
       thrpt:  [-0.8113% -0.1254% +0.5122]
       No change in performance detected.
Found 8 outliers among 100 measurements (8.00%)
8 (8.00%) high severe
transfer/1-conn/1-100mb-req (aka. Upload): No change in performance detected.
       time:   [193.67 ms 193.97 ms 194.29 ms]
       thrpt:  [514.70 MiB/s 515.55 MiB/s 516.33 MiB/s]
change:
       time:   [-0.4244% +0.0114% +0.3456] (p = 0.96 > 0.05)
       thrpt:  [-0.3444% -0.0114% +0.4262]
       No change in performance detected.
Found 1 outliers among 100 measurements (1.00%)
1 (1.00%) high severe
streams/walltime/1-streams/each-1000-bytes: Change within noise threshold.
       time:   [584.43 µs 587.24 µs 590.44 µs]
       change: [-1.6781% -0.9874% -0.2193] (p = 0.01 < 0.05)
       Change within noise threshold.
Found 12 outliers among 100 measurements (12.00%)
12 (12.00%) high severe
streams/walltime/1000-streams/each-1-bytes: Change within noise threshold.
       time:   [12.086 ms 12.118 ms 12.166 ms]
       change: [-0.9397% -0.6312% -0.1855] (p = 0.00 < 0.05)
       Change within noise threshold.
Found 1 outliers among 100 measurements (1.00%)
1 (1.00%) high severe
streams/walltime/1000-streams/each-1000-bytes: No change in performance detected.
       time:   [43.509 ms 43.554 ms 43.601 ms]
       change: [-0.4112% -0.1374% +0.0755] (p = 0.30 > 0.05)
       No change in performance detected.
Found 1 outliers among 100 measurements (1.00%)
1 (1.00%) high mild
streams-flow-controlled/walltime/1-streams/each-4194304-bytes: Change within noise threshold.
       time:   [33.995 ms 34.041 ms 34.089 ms]
       change: [-1.3474% -1.1457% -0.9412] (p = 0.00 < 0.05)
       Change within noise threshold.
Found 2 outliers among 100 measurements (2.00%)
2 (2.00%) high mild
streams-flow-controlled/walltime/10-streams/each-1048576-bytes: No change in performance detected.
       time:   [97.472 ms 98.724 ms 100.01 ms]
       change: [-1.2945% +0.4917% +2.3795] (p = 0.60 > 0.05)
       No change in performance detected.
transfer/walltime/pacing-false/varying-seeds: Change within noise threshold.
       time:   [23.103 ms 23.131 ms 23.174 ms]
       change: [+2.6349% +2.9043% +3.1614] (p = 0.00 < 0.05)
       Change within noise threshold.
Found 2 outliers among 100 measurements (2.00%)
1 (1.00%) high mild
1 (1.00%) high severe
transfer/walltime/pacing-true/varying-seeds: Change within noise threshold.
       time:   [23.283 ms 23.312 ms 23.348 ms]
       change: [+2.5868% +2.7841% +2.9683] (p = 0.00 < 0.05)
       Change within noise threshold.
Found 2 outliers among 100 measurements (2.00%)
1 (1.00%) high mild
1 (1.00%) high severe
transfer/walltime/pacing-false/same-seed: Change within noise threshold.
       time:   [22.838 ms 22.859 ms 22.882 ms]
       change: [+0.2106% +0.3534% +0.4836] (p = 0.00 < 0.05)
       Change within noise threshold.
Found 1 outliers among 100 measurements (1.00%)
1 (1.00%) high severe
transfer/walltime/pacing-true/same-seed: Change within noise threshold.
       time:   [23.198 ms 23.228 ms 23.274 ms]
       change: [+0.2832% +0.4904% +0.7255] (p = 0.00 < 0.05)
       Change within noise threshold.
Found 3 outliers among 100 measurements (3.00%)
1 (1.00%) high mild
2 (2.00%) high severe

Download data for profiler.firefox.com or download performance comparison data.

@github-actions
Copy link
Copy Markdown
Contributor

Failed Interop Tests

QUIC Interop Runner, client vs. server, differences relative to main at 143849d.

neqo-pr as clientneqo-pr as server
neqo-pr vs. go-x-net: BP BA
neqo-pr vs. haproxy: BP BA
neqo-pr vs. kwik: 🚀L1 BP BA
neqo-pr vs. lsquic: L1 C1
neqo-pr vs. msquic: A L1 C1 🚀C2
neqo-pr vs. mvfst: A BP BA
neqo-pr vs. neqo: A
neqo-pr vs. nginx: BP BA
neqo-pr vs. ngtcp2: ⚠️C1 CM
neqo-pr vs. picoquic: A
neqo-pr vs. quic-go: A
neqo-pr vs. quiche: BP BA
neqo-pr vs. s2n-quic: BA CM
neqo-pr vs. tquic: S BP BA
neqo-pr vs. xquic: A 🚀L1
aioquic vs. neqo-pr: CM
go-x-net vs. neqo-pr: CM
kwik vs. neqo-pr: BP BA CM
msquic vs. neqo-pr: CM
mvfst vs. neqo-pr: Z A L1 C1 CM
neqo vs. neqo-pr: A
openssl vs. neqo-pr: LR M A CM
quic-go vs. neqo-pr: CM
quic-zig vs. neqo-pr: ⚠️C20
quiche vs. neqo-pr: L1 ⚠️C1 CM
quinn vs. neqo-pr: V2 CM
s2n-quic vs. neqo-pr: CM
tquic vs. neqo-pr: CM
xquic vs. neqo-pr: M CM
All results

Succeeded Interop Tests

QUIC Interop Runner, client vs. server

neqo-pr as client

neqo-pr as server

Unsupported Interop Tests

QUIC Interop Runner, client vs. server

neqo-pr as client

neqo-pr as server

@larseggert larseggert added this pull request to the merge queue May 11, 2026
Merged via the queue into mozilla:main with commit 3287939 May 11, 2026
185 of 187 checks passed
@larseggert larseggert deleted the chore-cc-no-box branch May 11, 2026 14:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants