From d7eefcbdda400c1559f6e5862f1ce56df5e10162 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Thu, 19 Oct 2023 00:24:06 -0700 Subject: [PATCH] compress.go: Rewrite compression docs --- compress.go | 50 +++++++++++++++++++++++++----------------------- compress_test.go | 27 ++++++++++++++++++++++++++ write.go | 2 +- 3 files changed, 54 insertions(+), 25 deletions(-) diff --git a/compress.go b/compress.go index 81de751b..d7a40d3b 100644 --- a/compress.go +++ b/compress.go @@ -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 { diff --git a/compress_test.go b/compress_test.go index 7b0e3a68..667e1408 100644 --- a/compress_test.go +++ b/compress_test.go @@ -4,6 +4,9 @@ package websocket import ( + "bytes" + "compress/flate" + "io" "strings" "testing" @@ -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) + } +} diff --git a/write.go b/write.go index 708d5a6a..a6a137d1 100644 --- a/write.go +++ b/write.go @@ -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)