Skip to content

refactor: complete rewrite of the frame type#39

Merged
mccutchen merged 6 commits intomainfrom
experimental-rewrite
Feb 6, 2025
Merged

refactor: complete rewrite of the frame type#39
mccutchen merged 6 commits intomainfrom
experimental-rewrite

Conversation

@mccutchen
Copy link
Owner

With this experimental refactor, a decoded websocket frame is represented in memory as a single header byte and a payload byte slice.

The single header byte gives us all the info we need at the application level (opcode, fin bit, RSV bits).

Because we unmask the payload at read time and already require callers to specify a mask when writing a frame, we do not need to store the masked bit or the mask key in the frame itself. (This does, however, require passing a mode into ReadFrame so that we can still reject unmasked client frames.)

Because correctly constructing the header byte for a frame is tricky, a new NewFrame constructor is now the only way to create a frame.

With this experimental refactor, a decoded websocket frame is
represented in memory as a single header byte and a payload byte slice.

The single header byte gives us all the info we need at the
application level (opcode, fin bit, RSV bits).

Because we unmask the payload at read time and already require callers
to specify a mask when writing a frame, we do not need to store the
masked bit or the mask key in the frame itself. (This does, however,
require passing a mode into ReadFrame so that we can still reject
unmasked client frames.)

Because correctly constructing the header byte for a frame is tricky, a
new `NewFrame` constructor is now the only way to create a frame.
@github-actions
Copy link

github-actions bot commented Feb 5, 2025

🔥 Run benchmarks comparing df776d4 against main:

gh workflow run bench.yaml -f pr_number=39

Note: this comment will update with each new commit.

@codecov
Copy link

codecov bot commented Feb 5, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 92.65%. Comparing base (ef31006) to head (df776d4).
Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #39      +/-   ##
==========================================
+ Coverage   92.44%   92.65%   +0.20%     
==========================================
  Files           3        3              
  Lines         437      449      +12     
==========================================
+ Hits          404      416      +12     
  Misses         26       26              
  Partials        7        7              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@github-actions
Copy link

github-actions bot commented Feb 5, 2025

benchstats: ef31006...40c87b9

View full benchmark output on the workflow summary.

goos: linux
goarch: amd64
pkg: github.com/mccutchen/websocket
cpu: AMD EPYC 7763 64-Core Processor                
                       │ ./baseline/bench-results.txt │      ./head/bench-results.txt      │
                       │            sec/op            │   sec/op     vs base               │
ReadFrame/1KiB-4                         1.042µ ±  1%   1.006µ ± 2%  -3.45% (p=0.001 n=10)
ReadFrame/1MiB-4                         944.3µ ±  0%   928.5µ ± 1%  -1.67% (p=0.001 n=10)
ReadFrame/8MiB-4                         7.297m ±  1%   7.023m ± 1%  -3.76% (p=0.000 n=10)
ReadFrame/16MiB-4                        14.58m ±  1%   13.92m ± 1%  -4.50% (p=0.000 n=10)
ReadMessage/1KiB/1-4                     1.768µ ±  0%   1.706µ ± 0%  -3.54% (p=0.000 n=10)
ReadMessage/8MiB/1-4                     8.116m ±  1%   7.614m ± 0%  -6.18% (p=0.000 n=10)
ReadMessage/16MiB/1-4                    16.09m ±  1%   15.22m ± 1%  -5.41% (p=0.000 n=10)
ReadMessage/1KiB/4-4                     3.006µ ±  0%   2.923µ ± 1%  -2.74% (p=0.000 n=10)
ReadMessage/8MiB/4-4                     11.27m ±  5%   10.95m ± 5%  -2.85% (p=0.002 n=10)
ReadMessage/16MiB/4-4                    20.92m ±  1%   21.29m ± 3%  +1.75% (p=0.023 n=10)
ReadMessage/1KiB/16-4                    4.905µ ±  0%   4.811µ ± 0%  -1.93% (p=0.000 n=10)
ReadMessage/8MiB/16-4                    16.82m ±  4%   15.62m ± 4%  -7.15% (p=0.000 n=10)
ReadMessage/16MiB/16-4                   24.42m ± 11%   23.35m ± 3%  -4.39% (p=0.003 n=10)
geomean                                  771.9µ         744.5µ       -3.55%

                       │ ./baseline/bench-results.txt │      ./head/bench-results.txt       │
                       │             B/op             │     B/op      vs base               │
ReadFrame/1KiB-4                         1.055Ki ± 0%   1.039Ki ± 0%  -1.48% (p=0.000 n=10)
ReadFrame/1MiB-4                         1.000Mi ± 0%   1.000Mi ± 0%  -0.00% (p=0.000 n=10)
ReadFrame/8MiB-4                         8.000Mi ± 0%   8.000Mi ± 0%  -0.00% (p=0.000 n=10)
ReadFrame/16MiB-4                        16.00Mi ± 0%   16.00Mi ± 0%  -0.00% (p=0.000 n=10)
ReadMessage/1KiB/1-4                     1.086Ki ± 0%   1.070Ki ± 0%  -1.44% (p=0.000 n=10)
ReadMessage/8MiB/1-4                     8.000Mi ± 0%   8.000Mi ± 0%  -0.00% (p=0.000 n=10)
ReadMessage/16MiB/1-4                    16.00Mi ± 0%   16.00Mi ± 0%  -0.00% (p=0.000 n=10)
ReadMessage/1KiB/4-4                     4.000Ki ± 0%   3.938Ki ± 0%  -1.56% (p=0.000 n=10)
ReadMessage/8MiB/4-4                     28.57Mi ± 0%   28.57Mi ± 0%  -0.00% (p=0.000 n=10)
ReadMessage/16MiB/4-4                    57.09Mi ± 0%   57.09Mi ± 0%  -0.00% (p=0.000 n=10)
ReadMessage/1KiB/16-4                    5.031Ki ± 0%   4.781Ki ± 0%  -4.97% (p=0.000 n=10)
ReadMessage/8MiB/16-4                    47.22Mi ± 0%   47.22Mi ± 0%  -0.00% (p=0.000 n=10)
ReadMessage/16MiB/16-4                   93.81Mi ± 0%   93.81Mi ± 0%  -0.00% (p=0.000 n=10)
geomean                                  1.072Mi        1.065Mi       -0.74%

                       │ ./baseline/bench-results.txt │      ./head/bench-results.txt       │
                       │          allocs/op           │ allocs/op   vs base                 │
ReadFrame/1KiB-4                           5.000 ± 0%   5.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadFrame/1MiB-4                           5.000 ± 0%   5.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadFrame/8MiB-4                           5.000 ± 0%   5.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadFrame/16MiB-4                          5.000 ± 0%   5.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadMessage/1KiB/1-4                       6.000 ± 0%   6.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadMessage/8MiB/1-4                       6.000 ± 0%   6.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadMessage/16MiB/1-4                      6.000 ± 0%   6.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadMessage/1KiB/4-4                       24.00 ± 0%   24.00 ± 0%       ~ (p=1.000 n=10) ¹
ReadMessage/8MiB/4-4                       24.00 ± 0%   24.00 ± 0%       ~ (p=1.000 n=10) ¹
ReadMessage/16MiB/4-4                      24.00 ± 0%   24.00 ± 0%       ~ (p=1.000 n=10) ¹
ReadMessage/1KiB/16-4                      70.00 ± 0%   70.00 ± 0%       ~ (p=1.000 n=10) ¹
ReadMessage/8MiB/16-4                      90.00 ± 0%   90.00 ± 0%       ~ (p=1.000 n=10) ¹
ReadMessage/16MiB/16-4                     90.00 ± 0%   90.00 ± 0%       ~ (p=1.000 n=10) ¹
geomean                                    14.31        14.31       +0.00%
¹ all samples are equal

Comment on lines -39 to +44
OpcodeContinuation Opcode = 0x0
OpcodeText Opcode = 0x1
OpcodeBinary Opcode = 0x2
OpcodeClose Opcode = 0x8
OpcodePing Opcode = 0x9
OpcodePong Opcode = 0xA
OpcodeContinuation Opcode = 0b0000_0000 // 0x0
OpcodeText Opcode = 0b0000_0001 // 0x1
OpcodeBinary Opcode = 0b0000_0010 // 0x2
OpcodeClose Opcode = 0b0000_1000 // 0x8
OpcodePing Opcode = 0b0000_1001 // 0x9
OpcodePong Opcode = 0b0000_1010 // 0xA
Copy link
Owner Author

Choose a reason for hiding this comment

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

Tbh this extra verbose binary representation makes it easier for me to understand all the bitwise operations happening here.

@mccutchen
Copy link
Owner Author

Interesting that the ReadMessage and ReadFrame benchmarks got slower, consistent across a sample size of two runs. Probably need to try some profiling here.

@mccutchen mccutchen merged commit 0dbe072 into main Feb 6, 2025
8 checks passed
@mccutchen mccutchen deleted the experimental-rewrite branch February 6, 2025 03:24
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.

1 participant