Skip to content

Commit

Permalink
compress.go: Rewrite compression docs
Browse files Browse the repository at this point in the history
  • Loading branch information
nhooyr committed Oct 19, 2023
1 parent d22d1f3 commit 50952d7
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 25 deletions.
50 changes: 26 additions & 24 deletions compress.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,43 +9,45 @@ import (
"sync"
)

// CompressionMode represents the modes available to the deflate extension.
// CompressionMode represents the modes available to the permessage-deflate extension.
// See https://tools.ietf.org/html/rfc7692
//
// Works in all browsers except Safari which does not implement the deflate extension.
// Works in all modern browsers except Safari which does not implement the permessage-deflate extension.
//
// Compression is only used if the peer supports the mode selected.
type CompressionMode int

const (
// CompressionDisabled disables the deflate extension.
//
// Use this if you are using a predominantly binary protocol with very
// little duplication in between messages or CPU and memory are more
// important than bandwidth.
// CompressionDisabled disables the negotiation of the permessage-deflate extension.
//
// This is the default.
// This is the default. Do not enable compression without benchmarking for your particular use case first.
CompressionDisabled CompressionMode = iota

// CompressionContextTakeover uses a 32 kB sliding window and flate.Writer per connection.
// It reuses the sliding window from previous messages.
// As most WebSocket protocols are repetitive, this can be very efficient.
// It carries an overhead of 32 kB + 1.2 MB for every connection compared to CompressionNoContextTakeover.
// CompressionNoContextTakeover compresses each message greater than 512 bytes. Each message is compressed with
// a new 1.2 MB flate.Writer pulled from a sync.Pool. Each message is read with a 40 KB flate.Reader pulled from
// a sync.Pool.
//
// Sometime in the future it will carry 65 kB overhead instead once https://github.com/golang/go/issues/36919
// is fixed.
// This means less efficient compression as the sliding window from previous messages will not be used but the
// memory overhead will be lower as there will be no fixed cost for the flate.Writer nor the 32 KB sliding window.
// Especially if the connections are long lived and seldom written to.
//
// If the peer negotiates NoContextTakeover on the client or server side, it will be
// used instead as this is required by the RFC.
CompressionContextTakeover
// Thus, it uses less memory than CompressionContextTakeover but compresses less efficiently.
//
// If the peer does not support CompressionNoContextTakeover then we will fall back to CompressionDisabled.
CompressionNoContextTakeover

// CompressionNoContextTakeover grabs a new flate.Reader and flate.Writer as needed
// for every message. This applies to both server and client side.
// CompressionContextTakeover compresses each message greater than 128 bytes reusing the 32 KB sliding window from
// previous messages. i.e compression context across messages is preserved.
//
// This means less efficient compression as the sliding window from previous messages
// will not be used but the memory overhead will be lower if the connections
// are long lived and seldom used.
// As most WebSocket protocols are text based and repetitive, this compression mode can be very efficient.
//
// The message will only be compressed if greater than 512 bytes.
CompressionNoContextTakeover
// The memory overhead is a fixed 32 KB sliding window, a fixed 1.2 MB flate.Writer and a sync.Pool of 40 KB flate.Reader's
// that are used when reading and then returned.
//
// Thus, it uses more memory than CompressionNoContextTakeover but compresses more efficiently.
//
// If the peer does not support CompressionContextTakeover then we will fall back to CompressionNoContextTakeover.
CompressionContextTakeover
)

func (m CompressionMode) opts() *compressionOptions {
Expand Down
27 changes: 27 additions & 0 deletions compress_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
package websocket

import (
"bytes"
"compress/flate"
"io"
"strings"
"testing"

Expand Down Expand Up @@ -33,3 +36,27 @@ func Test_slidingWindow(t *testing.T) {
})
}
}

func BenchmarkFlateWriter(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
w, _ := flate.NewWriter(io.Discard, flate.BestSpeed)
// We have to write a byte to get the writer to allocate to its full extent.
w.Write([]byte{'a'})
w.Flush()
}
}

func BenchmarkFlateReader(b *testing.B) {
b.ReportAllocs()

var buf bytes.Buffer
w, _ := flate.NewWriter(&buf, flate.BestSpeed)
w.Write([]byte{'a'})
w.Flush()

for i := 0; i < b.N; i++ {
r := flate.NewReader(bytes.NewReader(buf.Bytes()))
io.ReadAll(r)
}
}
2 changes: 1 addition & 1 deletion write.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func (c *Conn) Writer(ctx context.Context, typ MessageType) (io.WriteCloser, err
//
// See the Writer method if you want to stream a message.
//
// If compression is disabled or the threshold is not met, then it
// If compression is disabled or the compression threshold is not met, then it
// will write the message in a single frame.
func (c *Conn) Write(ctx context.Context, typ MessageType, p []byte) error {
_, err := c.write(ctx, typ, p)
Expand Down

0 comments on commit 50952d7

Please sign in to comment.