Websocket: زيادة استخدام الذاكرة عند تشغيل الضغط

تم إنشاؤها على ١١ يناير ٢٠١٧  ·  50تعليقات  ·  مصدر: gorilla/websocket

مرحبا،

لقد بدأت باستخدام Centrifugo في الأسبوع الماضي.
أنا أستخدم نقطة نهاية Websocket الخام التي تستخدم هذه المكتبة تحت الغطاء.

أواجه موقفًا يتم فيه تمكين تفريغ كل رسالة ، وهناك نمو هائل في الذاكرة حتى النقطة التي يتعطل فيها عامل الإرساء بسبب استخدام الكثير من الذاكرة.

أنا أعمل داخل حاوية عامل إرساء ، بمتوسط ​​150 ألف إلى 200 ألف مستخدم متزامن ، ومتوسط ​​معدل الرسائل بين 30 ألف إلى 50 ألف رسالة في الثانية ، بمتوسط ​​حجم رسالة 600 بايت.

بدون تفريغ كل رسالة ، لا توجد ذاكرة تنمو على الإطلاق والأداء رائع ، ولكن نقل البيانات مرتفع للغاية.

هل يمكن لأي شخص أن يساعدني في التحقيق فيه؟

شكرا لك.

bug help wanted question waiting on new maintainer

التعليق الأكثر فائدة

لقد قدمت golang / go # 32371

ال 50 كومينتر

من المحتمل أن تكون الخطوة الأولى هي الحصول على ملف تعريف كومة وتخصيص لتطبيقك باستخدام pprof.

يرجى إنشاء ملفات التعريف بأحدث إصدار من الحزمة. يجب أن يساعد التغيير الأخير في تجميع القراء والكتابات.

شكرا يا رفاق ،

أنا في انتظار الإصدار التالي من Centrifugo والذي يتضمن أحدث إصدار لديك ، ثم سأقوم بملف تعريف التطبيق وتحميله هنا.

يمكنك إغلاق هذه المشكلة حتى ذلك الحين ، لكنني آمل أن يكون الإصدار الجديد اليوم أو غدًا هو الأحدث ، لذا فالأمر متروك لك.

هنا هو تفريغ pprof

هذا هو ملف تعريف الكومة

kisielkgaryburd لقد تحديث جوهر يرجى التحقق من الآن

https://gist.github.com/joshdvir/091229e3d3e4ade8d73b8cffe86c602b

طلبت من joshdvir إرسال ملفات تعريف وحدة المعالجة المركزية والذاكرة من عقدة الإنتاج ، إليك ما لدينا:

وحدة المعالجة المركزية:

(pprof) top 20 --cum
28.99s of 62.03s total (46.74%)
Dropped 523 nodes (cum <= 0.31s)
Showing top 20 nodes out of 155 (cum >= 8.07s)
      flat  flat%   sum%        cum   cum%
         0     0%     0%     58.68s 94.60%  runtime.goexit
     0.05s 0.081% 0.081%     45.44s 73.25%  github.com/centrifugal/centrifugo/libcentrifugo/conns/clientconn.(*client).sendMessages
     0.16s  0.26%  0.34%     44.23s 71.30%  github.com/centrifugal/centrifugo/libcentrifugo/conns/clientconn.(*client).sendMessage
     0.16s  0.26%   0.6%     44.07s 71.05%  github.com/centrifugal/centrifugo/libcentrifugo/server/httpserver.(*wsSession).Send
     0.05s 0.081%  0.68%     43.82s 70.64%  github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket.(*Conn).WriteMessage
     0.01s 0.016%  0.69%     21.67s 34.93%  github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket.(*flateWriteWrapper).Close
     0.03s 0.048%  0.74%     20.19s 32.55%  github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket.(*Conn).NextWriter
     0.07s  0.11%  0.85%     19.79s 31.90%  github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket.compressNoContextTakeover
    17.56s 28.31% 29.16%     17.56s 28.31%  runtime.memclr
     0.04s 0.064% 29.23%     15.46s 24.92%  compress/flate.(*Writer).Reset
     0.03s 0.048% 29.28%     15.42s 24.86%  compress/flate.(*compressor).reset
         0     0% 29.28%     14.40s 23.21%  compress/flate.(*Writer).Flush
         0     0% 29.28%     14.40s 23.21%  compress/flate.(*compressor).syncFlush
     2.62s  4.22% 33.50%     14.01s 22.59%  compress/flate.(*compressor).deflate
     0.01s 0.016% 33.52%     11.05s 17.81%  compress/flate.(*compressor).writeBlock
     0.15s  0.24% 33.76%     11.04s 17.80%  compress/flate.(*huffmanBitWriter).writeBlock
     0.21s  0.34% 34.10%      9.05s 14.59%  runtime.systemstack
     0.06s 0.097% 34.19%      8.87s 14.30%  github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket.(*messageWriter).flushFrame
     0.07s  0.11% 34.31%      8.81s 14.20%  github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket.(*Conn).write

يقضي معظم وقت وحدة المعالجة المركزية في WriteMessage:

(pprof) list WriteMessage
Total: 1.03mins
ROUTINE ======================== github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket.(*Conn).WriteMessage in /Users/fz/go/src/github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket/conn.go
      50ms     43.82s (flat, cum) 70.64% of Total
         .          .    659:
         .          .    660:// WriteMessage is a helper method for getting a writer using NextWriter,
         .          .    661:// writing the message and closing the writer.
         .          .    662:func (c *Conn) WriteMessage(messageType int, data []byte) error {
         .          .    663:
      50ms       50ms    664:   if c.isServer && (c.newCompressionWriter == nil || !c.enableWriteCompression) {
         .          .    665:
         .          .    666:       // Fast path with no allocations and single frame.
         .          .    667:
         .       20ms    668:       if err := c.prepWrite(messageType); err != nil {
         .          .    669:           return err
         .          .    670:       }
         .          .    671:       mw := messageWriter{c: c, frameType: messageType, pos: maxFrameHeaderSize}
         .       10ms    672:       n := copy(c.writeBuf[mw.pos:], data)
         .          .    673:       mw.pos += n
         .          .    674:       data = data[n:]
         .      1.69s    675:       return mw.flushFrame(true, data)
         .          .    676:   }
         .          .    677:
         .     20.19s    678:   w, err := c.NextWriter(messageType)
         .          .    679:   if err != nil {
         .          .    680:       return err
         .          .    681:   }
         .      190ms    682:   if _, err = w.Write(data); err != nil {
         .          .    683:       return err
         .          .    684:   }
         .     21.67s    685:   return w.Close()
         .          .    686:}

التالي

(pprof) list NextWriter
Total: 1.03mins
ROUTINE ======================== github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket.(*Conn).NextWriter in /Users/fz/go/src/github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket/conn.go
      30ms     20.19s (flat, cum) 32.55% of Total
         .          .    437:// method flushes the complete message to the network.
         .          .    438://
         .          .    439:// There can be at most one open writer on a connection. NextWriter closes the
         .          .    440:// previous writer if the application has not already done so.
         .          .    441:func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) {
         .       90ms    442:   if err := c.prepWrite(messageType); err != nil {
         .          .    443:       return nil, err
         .          .    444:   }
         .          .    445:
         .          .    446:   mw := &messageWriter{
         .          .    447:       c:         c,
         .          .    448:       frameType: messageType,
      10ms      280ms    449:       pos:       maxFrameHeaderSize,
         .          .    450:   }
         .       10ms    451:   c.writer = mw
         .          .    452:   if c.newCompressionWriter != nil && c.enableWriteCompression && isData(messageType) {
      10ms     19.80s    453:       w := c.newCompressionWriter(c.writer)
         .          .    454:       mw.compress = true
      10ms       10ms    455:       c.writer = w
         .          .    456:   }
         .          .    457:   return c.writer, nil
         .          .    458:}
         .          .    459:
         .          .    460:type messageWriter struct {

CompressNoContextTakeover:

(pprof) list compressNoContextTakeover
Total: 1.03mins
ROUTINE ======================== github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket.compressNoContextTakeover in /Users/fz/go/src/github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket/compression.go
      70ms     19.79s (flat, cum) 31.90% of Total
         .          .     33:   fr.(flate.Resetter).Reset(io.MultiReader(r, strings.NewReader(tail)), nil)
         .          .     34:   return &flateReadWrapper{fr}
         .          .     35:}
         .          .     36:
         .          .     37:func compressNoContextTakeover(w io.WriteCloser) io.WriteCloser {
         .      130ms     38:   tw := &truncWriter{w: w}
      40ms      3.93s     39:   fw, _ := flateWriterPool.Get().(*flate.Writer)
      10ms     15.47s     40:   fw.Reset(tw)
      20ms      260ms     41:   return &flateWriteWrapper{fw: fw, tw: tw}
         .          .     42:}

والآن الملف الشخصي كومة:

(pprof) top 30 --cum
4794.23MB of 5414.45MB total (88.55%)
Dropped 238 nodes (cum <= 27.07MB)
Showing top 30 nodes out of 46 (cum >= 113.64MB)
      flat  flat%   sum%        cum   cum%
         0     0%     0%  5385.39MB 99.46%  runtime.goexit
         0     0%     0%  4277.82MB 79.01%  sync.(*Pool).Get
         0     0%     0%  4277.82MB 79.01%  sync.(*Pool).getSlow
         0     0%     0%  4182.80MB 77.25%  github.com/centrifugal/centrifugo/libcentrifugo/conns/clientconn.(*client).sendMessages
         0     0%     0%  4181.80MB 77.23%  github.com/centrifugal/centrifugo/libcentrifugo/conns/clientconn.(*client).sendMessage
         0     0%     0%  4181.80MB 77.23%  github.com/centrifugal/centrifugo/libcentrifugo/server/httpserver.(*wsSession).Send
         0     0%     0%  4181.80MB 77.23%  github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket.(*Conn).WriteMessage
       8MB  0.15%  0.15%  4168.27MB 76.98%  github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket.(*Conn).NextWriter
      12MB  0.22%  0.37%  4160.27MB 76.84%  github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket.compressNoContextTakeover
 3792.80MB 70.05% 70.42%  4148.27MB 76.61%  compress/flate.NewWriter
         0     0% 70.42%  4148.27MB 76.61%  github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket.glob..func1
    0.50MB 0.0092% 70.43%  1156.29MB 21.36%  net/http.(*conn).serve
         0     0% 70.43%   873.42MB 16.13%  github.com/centrifugal/centrifugo/libcentrifugo/server/httpserver.(*HTTPServer).Logged.func1
         0     0% 70.43%   873.42MB 16.13%  net/http.HandlerFunc.ServeHTTP
         0     0% 70.43%   872.92MB 16.12%  github.com/centrifugal/centrifugo/libcentrifugo/server/httpserver.(*HTTPServer).WrapShutdown.func1
         0     0% 70.43%   872.92MB 16.12%  net/http.(*ServeMux).ServeHTTP
         0     0% 70.43%   872.92MB 16.12%  net/http.serverHandler.ServeHTTP
         0     0% 70.43%   866.91MB 16.01%  github.com/centrifugal/centrifugo/libcentrifugo/server/httpserver.(*HTTPServer).RawWebsocketHandler
         0     0% 70.43%   866.91MB 16.01%  github.com/centrifugal/centrifugo/libcentrifugo/server/httpserver.(*HTTPServer).RawWebsocketHandler-fm
         0     0% 70.43%   404.78MB  7.48%  github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket.(*Conn).ReadMessage
  355.47MB  6.57% 76.99%   355.47MB  6.57%  compress/flate.(*compressor).init
         0     0% 76.99%   320.19MB  5.91%  github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket.(*Upgrader).Upgrade
    0.50MB 0.0092% 77.00%   292.64MB  5.40%  github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket.(*Conn).NextReader
    1.50MB 0.028% 77.03%   291.64MB  5.39%  github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket.decompressNoContextTakeover
  215.85MB  3.99% 81.02%   216.35MB  4.00%  github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket.newConn
  159.10MB  2.94% 83.96%   159.10MB  2.94%  compress/flate.(*decompressor).Reset
  129.04MB  2.38% 86.34%   129.04MB  2.38%  compress/flate.NewReader
         0     0% 86.34%   129.04MB  2.38%  github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket.glob..func2
  119.46MB  2.21% 88.55%   119.46MB  2.21%  net/http.newBufioWriterSize
         0     0% 88.55%   113.64MB  2.10%  io/ioutil.ReadAll

التالي

(pprof) list WriteMessage
Total: 5.29GB
ROUTINE ======================== github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket.(*Conn).WriteMessage in /Users/fz/go/src/github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket/conn.go
         0     4.08GB (flat, cum) 77.23% of Total
         .          .    673:       mw.pos += n
         .          .    674:       data = data[n:]
         .          .    675:       return mw.flushFrame(true, data)
         .          .    676:   }
         .          .    677:
         .     4.07GB    678:   w, err := c.NextWriter(messageType)
         .          .    679:   if err != nil {
         .          .    680:       return err
         .          .    681:   }
         .          .    682:   if _, err = w.Write(data); err != nil {
         .          .    683:       return err
         .          .    684:   }
         .    13.53MB    685:   return w.Close()
         .          .    686:}

CompressNoContextTakeover:

(pprof) list compressNoContextTakeover
Total: 5.29GB
ROUTINE ======================== github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket.compressNoContextTakeover in /Users/fz/go/src/github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket/compression.go
      12MB     4.06GB (flat, cum) 76.84% of Total
         .          .     33:   fr.(flate.Resetter).Reset(io.MultiReader(r, strings.NewReader(tail)), nil)
         .          .     34:   return &flateReadWrapper{fr}
         .          .     35:}
         .          .     36:
         .          .     37:func compressNoContextTakeover(w io.WriteCloser) io.WriteCloser {
      10MB       10MB     38:   tw := &truncWriter{w: w}
         .     4.05GB     39:   fw, _ := flateWriterPool.Get().(*flate.Writer)
         .          .     40:   fw.Reset(tw)
       2MB        2MB     41:   return &flateWriteWrapper{fw: fw, tw: tw}
         .          .     42:}

FZambia شكرًا لك على نشر معلومات الملف الشخصي.

يجب إضافة عدادات هنا وهنا لتحديد مدى فعالية المسبح. ربما يوجد مسار رمز حيث لا يتم إرجاع كاتب السطح إلى البركة.

يحتمل أن تكون ذات صلة: https://github.com/golang/go/issues/18625

@ y3llowcake شكرا للإشارة إلى هذه المسألة.

لقد كتبت حالة اختبار لـ Gorilla Websocket:

type testConn struct {
    conn     *Conn
    messages chan []byte
}

func newTestConn(c *Conn, bufferSize int) *testConn {
    return &testConn{
        conn:     c,
        messages: make(chan []byte, bufferSize),
    }
}

func printss() {
    m := runtime.MemStats{}
    runtime.ReadMemStats(&m)
    fmt.Printf("inuse: %d sys: %d\n", m.StackInuse, m.StackSys)
}

func TestWriteWithCompression(t *testing.T) {
    w := ioutil.Discard
    done := make(chan struct{})
    numConns := 1000
    numMessages := 1000
    conns := make([]*testConn, numConns)
    var wg sync.WaitGroup
    for i := 0; i < numConns; i++ {
        c := newConn(fakeNetConn{Reader: nil, Writer: w}, false, 1024, 1024)
        c.enableWriteCompression = true
        c.newCompressionWriter = compressNoContextTakeover
        conns[i] = newTestConn(c, 256)
        wg.Add(1)
        go func(c *testConn) {
            defer wg.Done()
            i := 0
            for i < numMessages {
                select {
                case <-done:
                    return
                case msg := <-c.messages:
                    c.conn.WriteMessage(TextMessage, msg)
                    i++
                }
            }
        }(conns[i])
    }
    messages := textMessages(100)
    for i := 0; i < numMessages; i++ {
        if i%100 == 0 {
            printss()
        }
        msg := messages[i%len(messages)]
        for _, c := range conns {
            c.messages <- msg
        }
    }
    wg.Wait()
}

func textMessages(num int) [][]byte {
    messages := make([][]byte, num)
    for i := 0; i < num; i++ {
        msg := fmt.Sprintf("planet: %d, country: %d, city: %d, street: %d", i, i, i, i)
        messages[i] = []byte(msg)
    }
    return messages
}

يقوم بإنشاء 1000 اتصال مع تمكين الضغط ، ولكل منها قناة رسائل مخزنة. ثم في حلقة نكتب رسالة في كل اتصال.

إليك كيف يتصرف مع go1.7.4

fz<strong i="7">@websocket</strong>: go test -test.run=TestWriteWithCompression
inuse: 4259840 sys: 4259840
inuse: 27394048 sys: 27394048
inuse: 246251520 sys: 246251520
inuse: 1048510464 sys: 1048510464
inuse: 1048510464 sys: 1048510464
inuse: 1049034752 sys: 1049034752
inuse: 1049034752 sys: 1049034752
inuse: 1049034752 sys: 1049034752
inuse: 1049034752 sys: 1049034752
inuse: 1049034752 sys: 1049034752
PASS
ok      github.com/gorilla/websocket    11.053s

استخدام Go مع الالتزام https://github.com/golang/go/commit/9c3630f578db1d4331b367c3c7d284db299be3a6

fz<strong i="12">@websocket</strong>: go1.8 test -test.run=TestWriteWithCompression
inuse: 4521984 sys: 4521984
inuse: 4521984 sys: 4521984
inuse: 4521984 sys: 4521984
inuse: 4521984 sys: 4521984
inuse: 4521984 sys: 4521984
inuse: 4521984 sys: 4521984
inuse: 4521984 sys: 4521984
inuse: 4521984 sys: 4521984
inuse: 4521984 sys: 4521984
inuse: 4521984 sys: 4521984
PASS
ok      github.com/gorilla/websocket    12.023s

على الرغم من صعوبة القول في الوقت الحالي ، هل سيحل هذا الإصلاح المشكلة الأصلية في هذه المشكلة أم لا.

لقد جربت أيضًا نفس الشيء مع flate من https://github.com/klauspost/compress بواسطة klauspost والذي يحتوي بالفعل على إصلاح نسخة المصفوفة هذه بشكل رئيسي:

fz<strong i="20">@websocket</strong>: go test -test.run=TestWriteWithCompression
inuse: 4358144 sys: 4358144
inuse: 4587520 sys: 4587520
inuse: 4587520 sys: 4587520
inuse: 4587520 sys: 4587520
inuse: 4587520 sys: 4587520
inuse: 4587520 sys: 4587520
inuse: 4587520 sys: 4587520
inuse: 4587520 sys: 4587520
inuse: 4587520 sys: 4587520
inuse: 4587520 sys: 4587520
PASS
ok      github.com/gorilla/websocket    3.426s

ولكن في الواقع حتى بدون هذا الإصلاح https://github.com/klauspost/compress Library تتصرف بدون ذاكرة تنمو ... لا يمكنني شرح ذلك.

هنا أيضًا نتيجة الاختبار باستخدام

BenchmarkWriteWithCompression-4       200000          5676 ns/op         149 B/op          3 allocs/op

إنها تسريع 4x مقارنة بنتائج lib compress/flate القياسية:

BenchmarkWriteWithCompression-4        50000         25362 ns/op         128 B/op          3 allocs/op

garyburd أفهم أن وجود حزمة lib غير قياسية في النواة قد يكون خطوة خاطئة ، ولكن ربما يمكننا التفكير في آلية للسماح بتوصيلها بطريقة ما برمز المستخدم؟

حتى بدون هذا الإصلاح يتصرف بدون أن تنمو الذاكرة ... لا يمكنني شرح ذلك.

AFAICT ، تستخدم هذه الحزمة ضغط "المستوى 3" (وهو اختيار جيد). في الحزمة الخاصة بي ، المستوى 1-4 متخصص ، ولا تستخدم الكود "العام" ، الذي يحتوي على المشكلة.

في Go 1.7 المستوى 1 (أفضل سرعة) له وظيفة متخصصة مماثلة. أعتقد أنه إذا استخدمت ذلك ، فلن تواجه المشكلة. قد يكون هذا حلاً يمكنك استخدامه ، لذلك لا يتعين عليك استيراد حزمة متخصصة (حتى لو كنت لا أمانع في منح المستخدمين الخيار). يجب أن يكون أداء المستوى 1 قريبًا جدًا من الحزمة الخاصة بي.

شكرًا klauspost على التوضيح ، فقط جربت ما قلته - نعم ، مع أداء مستوى الضغط 1 يمكن مقارنته بمكتبتك وليس لديه مشاكل في الذاكرة في go1.7 (في حالة الاختبار أعلاه)

garyburd ما رأيك في هذا؟ أرى حلين يمكنهما مساعدتنا - جعل مستوى الضغط مُصدَّرًا متغيرًا أو السماح بتوصيل تنفيذ مسطح مخصص. بالطبع يمكننا أيضًا انتظار go1.8 ولكن طريقة تحسين أداء الضغط لا تزال مهمة جدًا. هل تريد منا أن نحاول إنشاء بنية مخصصة مع النصائح التي قدمتها وخطأ نسخة مصفوفة ثابتة ونرى كيف يتصرف في الإنتاج؟

FZambia ماذا عن ضبط مستوى الضغط على واحد في الوقت الحالي؟ من المحتمل أن يكون الخيار الأفضل لمعظم التطبيقات في هذا الوقت ويتجنب تعريض المزيد من مساحة سطح API.

garyburd أتفق مع FZambia ،
عادة ، تكلف الخوادم أقل بكثير من حركة المرور عندما تكون حركة المرور عالية جدًا ، لذا فإن وجود هذا الخيار القابل للتكوين سيكون رائعًا
شكرا

حسنًا ، دعنا نضيف ما يلي:

type CompressionOptions {
    // Level specifies the compression level for the flate compressor.  Valid levels range from
    // -2 to 9.  Level -2 will use Huffman compression only. Level -1 uses the default compression 
    // level.  Level 0 does not attempt any compression.  Levels 1 through 9 range from best
    // speed to best compression. 
    // 
    // Applications should set this field.  The default value of 0 does not attempt any compression.
    Level int
}

type Dialer struct {
   // CompressionOptions specifies options the client should use for 
   // per message compression (RFC 7692).  If CompressionOptions is nil and
   // EnableCompression is nil, then the client does not attempt to negotiate
   // compression with the server.
   CompressionOptions *CompressionOptions

  // EnableCompression specifies if the client should attempt to negotiate
  // per message compression (RFC 7692). Setting this value to true does not
  // guarantee that compression will be supported. Currently only "no context
  // takeover" modes are supported.
  //
  // Deprecated: Set CompressionOptions to non-nil value to enable compression
  // negotiation.
  EnableCompression bool
}

تعديل مطور لتتناسب مع المسجل.

garyburd أوافق على أن المستوى 1 أفضل للقيمة الافتراضية في الوقت الحالي لأنه يعمل على إصلاح نمو الذاكرة على Go1.7 والضغط مكلف للغاية. ولكن يبدو أنه على مستوى الجماهير في تطبيقات كبيرة مثل

لقد صنعنا تصميمًا مخصصًا بمستوى الضغط 1 والعدادات التي اقترحتها ووضعها في الإنتاج. قيم العداد هي:

Node 1:
"gorilla_websocket_flate_writer_from_pool": 1453147,
"gorilla_websocket_new_flate_writer": 6702

Node 2:
"gorilla_websocket_flate_writer_from_pool": 1820919,
"gorilla_websocket_new_flate_writer": 3676,

Node 3:
"gorilla_websocket_flate_writer_from_pool": 574187,
"gorilla_websocket_new_flate_writer": 321

...

إنه تجميع أكثر من دقيقة واحدة. لذا يبدو المسبح فعالًا جدًا ولكن ...

... لا يزال الضغط هو الرائد في ملفات تخصيص التخصيص ووحدة المعالجة المركزية والحصول من المزامنة. Pool هو العملية الأكثر تكلفة للتخصيص لسبب ما.

هنا ملف تعريف وحدة المعالجة المركزية الآن:

(pprof) top 30 --cum
27.28s of 52.42s total (52.04%)
Dropped 414 nodes (cum <= 0.26s)
Showing top 30 nodes out of 137 (cum >= 1.89s)
      flat  flat%   sum%        cum   cum%
         0     0%     0%     50.21s 95.78%  runtime.goexit
     0.16s  0.31%  0.31%     43.93s 83.80%  github.com/centrifugal/centrifugo/libcentrifugo/conns/clientconn.(*client).sendMessages
     0.21s   0.4%  0.71%     42.52s 81.11%  github.com/centrifugal/centrifugo/libcentrifugo/conns/clientconn.(*client).sendMessage
     0.19s  0.36%  1.07%     42.31s 80.71%  github.com/centrifugal/centrifugo/libcentrifugo/server/httpserver.(*wsSession).Send
     0.21s   0.4%  1.47%     41.87s 79.87%  github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket.(*Conn).WriteMessage
     0.01s 0.019%  1.49%     35.43s 67.59%  github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket.(*flateWriteWrapper).Close
     0.03s 0.057%  1.55%     24.69s 47.10%  compress/flate.(*Writer).Flush
         0     0%  1.55%     24.66s 47.04%  compress/flate.(*compressor).syncFlush
     0.04s 0.076%  1.62%     24.03s 45.84%  compress/flate.(*compressor).encSpeed
     0.08s  0.15%  1.77%     18.16s 34.64%  compress/flate.(*huffmanBitWriter).writeBlockDynamic
     0.12s  0.23%  2.00%     15.03s 28.67%  github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket.(*messageWriter).flushFrame
     0.11s  0.21%  2.21%     14.90s 28.42%  github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket.(*Conn).write
     0.91s  1.74%  3.95%     13.21s 25.20%  compress/flate.(*huffmanEncoder).generate
         0     0%  3.95%     12.72s 24.27%  net.(*conn).Write
     0.06s  0.11%  4.06%     12.72s 24.27%  net.(*netFD).Write
    11.78s 22.47% 26.54%     12.16s 23.20%  syscall.Syscall
     0.05s 0.095% 26.63%     12.09s 23.06%  syscall.Write
     0.02s 0.038% 26.67%     12.04s 22.97%  syscall.write
     0.61s  1.16% 27.83%     11.98s 22.85%  compress/flate.(*huffmanBitWriter).indexTokens
         0     0% 27.83%     10.61s 20.24%  github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket.(*messageWriter).Close
     5.20s  9.92% 37.75%      6.62s 12.63%  compress/flate.(*huffmanEncoder).bitCounts
     4.77s  9.10% 46.85%      5.44s 10.38%  compress/flate.encodeBestSpeed

قائمة اكتب رسالة

(pprof) list WriteMessage
Total: 52.42s
ROUTINE ======================== github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket.(*Conn).WriteMessage in /Users/fz/go/src/github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket/conn.go
     210ms     41.87s (flat, cum) 79.87% of Total
         .          .    659:
         .          .    660:// WriteMessage is a helper method for getting a writer using NextWriter,
         .          .    661:// writing the message and closing the writer.
         .          .    662:func (c *Conn) WriteMessage(messageType int, data []byte) error {
         .          .    663:
     160ms      160ms    664:   if c.isServer && (c.newCompressionWriter == nil || !c.enableWriteCompression) {
         .          .    665:
         .          .    666:       // Fast path with no allocations and single frame.
         .          .    667:
      10ms       40ms    668:       if err := c.prepWrite(messageType); err != nil {
         .          .    669:           return err
         .          .    670:       }
         .          .    671:       mw := messageWriter{c: c, frameType: messageType, pos: maxFrameHeaderSize}
         .       80ms    672:       n := copy(c.writeBuf[mw.pos:], data)
         .          .    673:       mw.pos += n
         .          .    674:       data = data[n:]
      10ms      4.43s    675:       return mw.flushFrame(true, data)
         .          .    676:   }
         .          .    677:
         .      1.63s    678:   w, err := c.NextWriter(messageType)
         .          .    679:   if err != nil {
         .          .    680:       return err
         .          .    681:   }
      30ms      100ms    682:   if _, err = w.Write(data); err != nil {
         .          .    683:       return err
         .          .    684:   }
         .     35.43s    685:   return w.Close()

قائمة إغلاق

         .          .    104:func (w *flateWriteWrapper) Close() error {
         .          .    105:   if w.fw == nil {
         .          .    106:       return errWriteClosed
         .          .    107:   }
         .     24.69s    108:   err1 := w.fw.Flush()
      10ms      130ms    109:   flateWriterPool.Put(w.fw)
         .          .    110:   w.fw = nil
         .          .    111:   if w.tw.p != [4]byte{0, 0, 0xff, 0xff} {
         .          .    112:       return errors.New("websocket: internal error, unexpected bytes at end of flate stream")
         .          .    113:   }
         .     10.61s    114:   err2 := w.tw.w.Close()
         .          .    115:   if err1 != nil {
         .          .    116:       return err1
         .          .    117:   }
         .          .    118:   return err2
         .          .    119:}

قائمة دافق

         .          .    711:// In the terminology of the zlib library, Flush is equivalent to Z_SYNC_FLUSH.
         .          .    712:func (w *Writer) Flush() error {
         .          .    713:   // For more about flushing:
         .          .    714:   // http://www.bolet.org/~pornin/deflate-flush.html
      30ms     24.69s    715:   return w.d.syncFlush()
         .          .    716:}
         .          .    717:
         .          .    718:// Close flushes and closes the writer.
         .          .    719:func (w *Writer) Close() error {
         .          .    720:   return w.d.close()
ROUTINE ======================== compress/flate.(*compressor).syncFlush in /Users/fz/go1.7/src/compress/flate/deflate.go
         0     24.66s (flat, cum) 47.04% of Total
         .          .    555:func (d *compressor) syncFlush() error {
         .          .    556:   if d.err != nil {
         .          .    557:       return d.err
         .          .    558:   }
         .          .    559:   d.sync = true
         .     24.03s    560:   d.step(d)
         .          .    561:   if d.err == nil {
         .      490ms    562:       d.w.writeStoredHeader(0, false)
         .      140ms    563:       d.w.flush()
         .          .    564:       d.err = d.w.err
         .          .    565:   }
         .          .    566:   d.sync = false
         .          .    567:   return d.err
         .          .    568:}

أصبح استخدام الذاكرة أفضل بكثير الآن ولكنه لا يزال مرتفعًا جدًا ، ويخصص الضغط الكثير:

fz<strong i="9">@centrifugo</strong>: go tool pprof --alloc_space centrifugo heap_profile_extra 
Entering interactive mode (type "help" for commands)
(pprof) top 30 --cum
518.97GB of 541.65GB total (95.81%)
Dropped 314 nodes (cum <= 2.71GB)
Showing top 30 nodes out of 35 (cum >= 3.33GB)
      flat  flat%   sum%        cum   cum%
         0     0%     0%   541.53GB   100%  runtime.goexit
         0     0%     0%   505.54GB 93.33%  github.com/centrifugal/centrifugo/libcentrifugo/conns/clientconn.(*client).sendMessages
         0     0%     0%   504.45GB 93.13%  github.com/centrifugal/centrifugo/libcentrifugo/conns/clientconn.(*client).sendMessage
         0     0%     0%   504.45GB 93.13%  github.com/centrifugal/centrifugo/libcentrifugo/server/httpserver.(*wsSession).Send
         0     0%     0%   504.45GB 93.13%  github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket.(*Conn).WriteMessage
    6.63GB  1.22%  1.22%   501.75GB 92.63%  github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket.(*Conn).NextWriter
    6.56GB  1.21%  2.44%   495.11GB 91.41%  github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket.compressNoContextTakeover
         0     0%  2.44%   491.89GB 90.81%  sync.(*Pool).Get
         0     0%  2.44%   491.89GB 90.81%  sync.(*Pool).getSlow
  359.74GB 66.42% 68.85%   488.55GB 90.20%  compress/flate.NewWriter
         0     0% 68.85%   488.55GB 90.20%  github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket.glob..func1
  128.81GB 23.78% 92.63%   128.81GB 23.78%  compress/flate.(*compressor).init
    0.01GB 0.0019% 92.64%    28.97GB  5.35%  net/http.(*conn).serve
         0     0% 92.64%    25.18GB  4.65%  github.com/centrifugal/centrifugo/libcentrifugo/server/httpserver.(*HTTPServer).Logged.func1
         0     0% 92.64%    25.18GB  4.65%  net/http.(*ServeMux).ServeHTTP
         0     0% 92.64%    25.18GB  4.65%  net/http.HandlerFunc.ServeHTTP
         0     0% 92.64%    25.18GB  4.65%  net/http.serverHandler.ServeHTTP

قائمة NextWriter

(pprof) list NextWriter
Total: 541.65GB
ROUTINE ======================== github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket.(*Conn).NextWriter in /Users/fz/go/src/github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket/conn.go
    6.63GB   501.75GB (flat, cum) 92.63% of Total
         .          .    444:   }
         .          .    445:
         .          .    446:   mw := &messageWriter{
         .          .    447:       c:         c,
         .          .    448:       frameType: messageType,
    6.63GB     6.63GB    449:       pos:       maxFrameHeaderSize,
         .          .    450:   }
         .          .    451:   c.writer = mw
         .          .    452:   if c.newCompressionWriter != nil && c.enableWriteCompression && isData(messageType) {
         .   495.11GB    453:       w := c.newCompressionWriter(c.writer)
         .          .    454:       mw.compress = true
         .          .    455:       c.writer = w
         .          .    456:   }
         .          .    457:   return c.writer, nil
         .          .    458:}

قائمة CompressNoContextTakeover

(pprof) list compressNoContextTakeover
Total: 541.65GB
ROUTINE ======================== github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket.compressNoContextTakeover in /Users/fz/go/src/github.com/centrifugal/centrifugo/vendor/github.com/gorilla/websocket/compression.go
    6.56GB   495.11GB (flat, cum) 91.41% of Total
         .          .     44:   fr.(flate.Resetter).Reset(io.MultiReader(r, strings.NewReader(tail)), nil)
         .          .     45:   return &flateReadWrapper{fr}
         .          .     46:}
         .          .     47:
         .          .     48:func compressNoContextTakeover(w io.WriteCloser) io.WriteCloser {
    4.41GB     4.41GB     49:   tw := &truncWriter{w: w}
         .   488.55GB     50:   fw, _ := flateWriterPool.Get().(*flate.Writer)
         .          .     51:   plugin.Metrics.Counters.Inc("gorilla_websocket_flate_writer_from_pool")
         .          .     52:   fw.Reset(tw)
    2.15GB     2.15GB     53:   return &flateWriteWrapper{fw: fw, tw: tw}
         .          .     54:}

--inuse_space صورة مماثلة بقيمتين أقل من حيث الحجم (2.69 جيجابايت لخط flateWriterPool.Get (). (* flate.Writer) الذي هو الرائد).

من الصعب أن نقول ما الذي يمكننا فعله بمثل هذا الضغط الكبير ...

garyburd تقترح إضافة CompressionOptions - يمكنني سحب الطلب باستخدامه ، ولكن ربما مجرد متغير عالمي مُصدَّر مثل DefaultFlateCompressionLevel الذي يمكننا تعيينه عند بدء التطبيق أو طريقة الضبط ستقوم بهذا العمل؟ لا نحتاج إلى مستوى ضغط لكل اتصال - ويمكننا في النهاية القيام بما اقترحته إذا كانت هناك حاجة لاحقًا. ولن تكون هناك حاجة إلى إهمال بهذه الطريقة في الوقت الحالي.

FZambia شكرًا لك على اختبار فعالية التجمع والإبلاغ عن الملفات الشخصية.

لا أريد إضافة واجهة برمجة تطبيقات الآن قد يتم استبدالها بواجهة برمجة تطبيقات أخرى لاحقًا.

عندما أفكر في الأمر أكثر ، من الأفضل إضافة طريقة واحدة:

// SetCompressionLevel sets the flate compression level for the next message.
// Valid levels range from -2 to 9.  Level -2 will use Huffman compression only. 
// Level -1 uses the default compression  level.  Level 0 does not attempt any
// compression.  Levels 1 through 9 range from best speed to best compression. 
func (c *Conn) SetCompressionLevel(n int) error {
}

هذا أكثر مرونة من الخيارات الأخرى. سيحل التطبيق محل flateWriterPool مع flatWriterPools [12]. سيحتاج flateWriterWrapper لتخزين المستوى بحيث يمكن إرجاع الكاتب إلى التجمع الصحيح.

أعتقد أن وجود طريقة واحدة تعمل على تغيير مستوى الضغط الافتراضي لا يزال أمرًا منطقيًا:

var defaultCompressionLevel int = 1

// SetDefaultCompressionLevel sets the flate compression level which will be used by 
// default to compress messages when compression negotiated. This function must be 
// called once before application starts.
//
// Valid levels range from -2 to 9.  Level -2 will use Huffman compression only. 
// Level -1 uses the default compression  level.  Level 0 does not attempt any
// compression.  Levels 1 through 9 range from best speed to best compression. 
func (c *Conn) SetDefaultCompressionLevel(n int) error {
    defaultCompressionLevel = n
}

في معظم الحالات ، أفترض أن المستخدمين الذين يحتاجون إلى ضغط مخصص يحتاجون إلى تعيين هذه القيمة الافتراضية مرة واحدة.

ثم إذا احتاج شخص ما إلى مستوى ضغط لكل اتصال / رسالة ، فيمكننا إضافة SetCompressionLevel و [12] flateWriterPool:

// SetCompressionLevel sets the flate compression level for the next message.
// Valid levels range from -2 to 9.  Level -2 will use Huffman compression only. 
// Level -1 uses the default compression  level.  Level 0 does not attempt any
// compression.  Levels 1 through 9 range from best speed to best compression.
// If not set default compression level will be used.
func (c *Conn) SetCompressionLevel(n int) error {
}

إذا لم يتم استدعاؤه ولكن ضغط التفاوض سيتم استخدام defaultCompressionLevel . التحذير الوحيد الذي أراه هو أنه يجب استدعاء SetDefaultCompressionLevel مرة واحدة قبل أن يبدأ التطبيق في

نظرت للتو في حجم كل مثيل flate.Writer :

package main

import "unsafe"
import "compress/flate"

func main() {
    var w flate.Writer
    println(unsafe.Sizeof(w))
}

600 كيلو بايت! هذا الحجم مثير للدهشة بالنسبة لي - لم أفترض حتى أنه شيء كبير :)

نعم - هناك عدد كبير من الجداول اللازمة للحفاظ على الحالة ، وإنتاج جداول huffman ومخرجات المخزن المؤقت. ليس هناك الكثير الذي يمكن القيام به حيال ذلك ، باستثناء المستوى -2 (HuffmanOnly) ، 0 (بدون ضغط) و 1 (أفضل سرعة) في المكتبة القياسية.

بالنسبة إلى مكتبتي الخاصة ، كنت أبحث عن تقليل متطلبات الذاكرة لخيارات الترميز التي لا تتطلب العديد من المخازن المؤقتة ، راجع https://github.com/klauspost/compress/pull/70 (لا يزال هناك مشكلات ، كما يمكن رؤيته من خلال الانهيار قمت بتسجيل الدخول في المشكلة)

FZambia لا أريد استخدام متغيرات مستوى الحزمة للإعدادات. يجب تضمين الإعداد مع الإعدادات الأخرى في Dialer and Upgrader أو يجب أن يكون جزءًا من الاتصال حتى يمكن تغييره من رسالة إلى أخرى.

انظر b0dc45572b148296ddf01989da00a2923d213a56.

garyburd جزيل الشكر على وقتك لمساعدتنا في هذا. بالنظر إلى كل هذه الملفات الشخصية ، هل ترى أي طريقة لتقليل استخدام الذاكرة المضغوطة والتخصيصات؟

إذا أرسل التطبيق نفس الرسالة إلى عدة عملاء ، فإن # 182 هو الشيء التالي الذي يجب تجربته.

يمكن لـgaryburd # 182 أن يفعل السحر في حالة الاستخدام الخاصة بي ، حيث يتراوح معدل خروج المعجبين بين 50 ألفًا و 90 ألفًا في الثانية حيث تكون جميع الرسائل متشابهة.

شكرا لك على مساعدتك.

هل تم إصلاح هذه المشكلة عن طريق b0dc45572b148296ddf01989da00a2923d213a56 و 804cb600d06b10672f2fbc0a336a7bee507a428e؟

كومة garyburd @ قبل التغييرات:
screen shot 2017-03-06 at 10 09 57
بعد التغييرات (مستوى الضغط 1 واستخدام PreparedMessage ):
screen shot 2017-03-06 at 10 10 35

نفس الصورة لوحدة المعالجة المركزية. لذلك لم نعد نرى ضغطًا في ملفات تعريف الكومة ووحدة المعالجة المركزية في المقام الأول بعد الآن.

على الرغم من انخفاض استخدام الذاكرة ، إلا أنه لا يزال مرتفعًا جدًا عند استخدام الضغط - ولكن قد تكون هذه مشكلة في التطبيق ، سنحاول التحقيق

garyburd فقط inuse_space الملف الشخصي أعطانيjoshdvir .

القادة هم gorilla/websocket.newConnBRW (36٪) ، http.newBufioWriterSize (17٪) ، http.newBufioReader (16٪). اعتقدت أننا استخدمنا تحسينًا حديثًا أضفته في رقم 223 ولكن بالنظر إلى الملفات الشخصية لاحظت أن مخازن القراءة والكتابة من hijack لا يُعاد استخدامها لسبب ما. ثم وجدت خطأ في رمز Centrifugo - تم تعيين ReadBufferSize و WriteBufferSize على القيم الافتراضية SockJS-go (4096) في حالة ضبط هذه الأحجام على 0 في التكوين. سيبني مع الإصلاح ويعود بالنتائج.

هناك بعض الأشياء الأخرى في الملفات الشخصية التي قد تهمك - سأحاول تحميل تصورات الرسم البياني svg من الإنشاء مع الإصلاح.

فيما يلي بعض الرسوم البيانية باستخدام Centrifugo مع أحدث Gorilla Websocket. هذا من عقدة Centrifugo التي تديرهاjoshdvir مع العديد من الوصلات (حوالي 100 كيلو) وتمكين ضغط مقبس الويب (المستوى 1) ، go1.7.5

وحدة المعالجة المركزية:

screen shot 2017-03-31 at 23 54 19

هنا نرى أن معظم وحدات المعالجة المركزية تُنفَق على syscalls - القراءة والكتابة - لا أعتقد أنه يمكننا فعل الكثير هنا ، لأننا نرسل الكثير من الرسائل الجماعية إلى مآخذ مختلفة. لذلك هذا يبدو طبيعيا.

مساحة التخصيص:

screen shot 2017-03-31 at 23 52 49

هنا نرى تأثيرًا كبيرًا لـ compress/flate.(*decompressor).Reset - فكرت قليلاً في كيفية تحسين ذلك ، لكنني لم أجد طريقة ..

مساحة الاستخدام:

screen shot 2017-04-01 at 00 00 31

garyburd هل ترى طريقة لتحسين الأمور أكثر؟ إذا لم يكن الأمر كذلك ، فيمكننا التعايش مع هذا على ما أعتقد.

ملاحظة: سأقوم بإنشاء العديد من اتصالات ws على الجهاز المحلي مع تمكين / تعطيل الضغط ومقارنة ملفات التعريف.

لقد وجدت بعض التعديلات التي يمكن إجراؤها على وحدة فك التشفير لتجنب التخصيصات ، وتأجيل تخصيص حوالي 4 كيلوبايت إلى الوقت المطلوب بالفعل.

https://github.com/klauspost/compress/pull/76

لا أرى أي شيء واضح للتحسين في حزمة websocket.

جربت للتو على localhost - يبدو أن استهلاك الذاكرة يأتي من أماكن مختلفة بنسب متساوية إلى حد ما ، لذلك من الصعب تشخيص الاختناق للعمل عليه. أعتقد أننا انتهينا هنا في الوقت الحالي.

klauspost لقد جربت ضغط lib الخاص بك على الإعداد المحلي أيضًا (قم بتوصيل 1000 عميل بالعقدة) - في حالتي كان استهلاك الذاكرة 50 ميجابايت بدون ضغط ، و 90 ميجابايت مع ضغط lib القياسي ، و 90 ميجابايت مع lib بدون تصحيح و 85 ميجابايت مع lib الخاص بك مع التصحيح https: //github.com/klauspost/compress/pull/76 - لا أستطيع أن أقول كيف يمكن أن يؤثر ذلك على مثيل الإنتاج (حيث أنشأت للتو العديد من الاتصالات ، بدون بث الرسائل ، بدون رسائل واردة من العملاء باستثناء الحاجة إلى الاتصال والاشتراك على قناة) - ربما تكون فائدة الذاكرة في السيناريو الحقيقي أكثر أهمية (خاصة وأننا رأينا الكثير من الذاكرة المخصصة للقراءة مضغوطة على الرسم البياني المخصص للمساحة).

أدى التحديث إلى Go1.8.1 من Go 1.7.5 إلى تحسين استخدام الذاكرة بنحو 15٪ (90 ميجابايت -> 75 ميجابايت) على الإعداد المحلي الاصطناعي الخاص بي. لكن لا يمكنني القول بالضبط من أين جاء هذا المكسب.

في مثيل الإنتاج مع تمكين الضغط ، انخفض استخدام الذاكرة أيضًا بنسبة 15-20٪ نفسها مع Go 1.8

joshdvirFZambia (وغيرها)، لا نشعر أن هذه المسألة قد عولجت مع الإصلاحات في المكتبة القياسية العودة أو لا يزال هناك شيء لتعقب هنا؟

@ checkman مرحبًا ، ليس لدي أي أفكار حول كيفية تحسين أداء الضغط بشكل أكبر. لا أعتقد أن المشكلة قد تم حلها بالكامل لأن الضغط لا يزال يؤثر على الأداء كثيرًا. ولكن إذا كنت تخطط لإغلاق هذه المشكلة - فلا تتردد في فعل ذلك لأنه لا يوجد حل واضح في الأفق. اعتمادًا على الموقف ، يمكن أن يكون PreparedMessage منقذًا للحياة ، لكنني شخصياً لا أحب مفهومه كثيرًا لأنه محدد للغاية ويفصل دلالات كتابة البايت في الاتصال.

ربما إذا كتبت معيارًا مستقلًا إلى حد ما ، مما يعني فقط استخدام stdlib و deflate الذي يمثل سيناريوهات العالم الحقيقي ، يمكنني استخدام ذلك كمعيار للاختبار إذا كان برنامج التشفير الأقل حجمًا سيساعد بالفعل.

يجب إعادة كتابة العلاقات العامة نفسها ، ولكن وجود معيار جيد سيكون نقطة انطلاق.

يمكننا أيضا إضافة []byte -> []byte API، حيث يتم توفير إطار كامل من شأنها أن تقضي على ضرورة أن يكون مخزن مؤقت داخلي (وعقوبة من نسخ لذلك).

بالنظر إلى مشكلات الأداء هذه ، هل يستحق الضغط التمكين؟

يمكننا أيضًا إضافة [] بايت -> [] بايت API ، حيث يتم توفير الإطار الكامل الذي من شأنه أن يلغي الحاجة إلى وجود مخزن مؤقت داخلي (وعقوبة النسخ إليه).

كيف ستعمل واجهة برمجة التطبيقات هذه؟ هل نسمح بالكتابة مباشرة على net.Conn؟

على الخادم بدون ضغط ، يقوم برنامج WriteMessage API في الغالب بنسخ البيانات مباشرة من التطبيق الموفر [] بايت إلى اتصال الشبكة الأساسي. تقوم واجهة برمجة التطبيقات للرسالة المعدة أيضًا بنسخ [] بايت مباشرةً إلى اتصال الشبكة الأساسي.

أعتقد أن هذه المشكلة ناتجة عن ضغط / تسطيح لا يعرض طريقة لضبط النافذة المنزلقة. انظر https://golang.org/issue/3155

وبالتالي ، يخصص كل كاتب مسطح قدرًا كبيرًا من الذاكرة ، وعلى الرغم من إعادة استخدامها عبر التجمع ، فإن هذا ينتهي بالتسبب في استخدام الذاكرة الكبيرة مع الضغط على.

لا يبدو أن هناك أي شيء يمكن لهذه المكتبة أن تفعله أكثر حتى يتم إغلاق هذه المشكلة.

في الواقع ، وفقًا لهذا المعيار


func BenchmarkFlate(b *testing.B) {
    b.ReportAllocs()
    for i := 0; i < b.N; i++ {
        flate.NewWriter(nil, flate.BestSpeed)
    }
}
$ go test -bench=BenchmarkFlate -run=^$
goos: darwin
goarch: amd64
pkg: scratch
BenchmarkFlate-8       10000        131504 ns/op     1200008 B/op         15 allocs/op
PASS
ok      scratch 1.345s

flate.Newriter يخصص 1.2 ميجا بايت! هذه مساحة كبيرة من الذاكرة لتخصيصها للضغط عند كتابة رسائل WebSocket 600 بايت.

مع BestCompression أو DefaultCompress ، ينخفض ​​التخصيص 800 كيلو بايت ولكن هذا لا يزال هائلاً.

# DefaultCompression
$ go test -bench=BenchmarkFlate -run=^$
goos: darwin
goarch: amd64
pkg: nhooyr.io/websocket
BenchmarkFlate-8       20000         93332 ns/op      806784 B/op         13 allocs/op
PASS
ok      nhooyr.io/websocket 2.848s
# BestCompression
$ go test -bench=BenchmarkFlate -run=^$
goos: darwin
goarch: amd64
pkg: nhooyr.io/websocket
BenchmarkFlate-8       20000         95197 ns/op      806784 B/op         13 allocs/op
PASS
ok      nhooyr.io/websocket 2.879s

لقد قدمت golang / go # 32371

@ klauspost هذا رائع حقًا. ما نوع الذاكرة العلوية التي ستكون متضمنة لكل عملية كتابة؟

سوف يتطلب بضع مئات من الكيلوبايتات ، ولكن على عكس البدائل الأخرى بمجرد ضغطها ، سيتم تحرير الذاكرة.

بالنسبة لمقابس الويب ، هذا ما تحتاجه. بمعنى عدم وجود مخصصات حية للمآخذ غير النشطة. فقط تأكد من أن كل شيء مكتوب في كتابة واحدة.

klauspost شكرا ، سأحاول التحقق من ذلك بمجرد أن يكون لدي الوقت. لدي جوهر يسمح باستخدام مسطح مخصص مع مكتبة Gorilla WebSocket - أحتاج إلى إعادة النظر فيه وتجربة فرع الضغط عديم الحالة.

https://github.com/klauspost/compress/pull/185 يخفض تخصيص الكومة إلى بضع مئات من البايتات أثناء الكتابة عن طريق الحصول على sync.Pool داخلي.

أظهر اختباري لمكتبة

راجع https://github.com/klauspost/compress/pull/216#issuecomment -586660128

حوالي 9.5 كيلوبايت مخصصة لكل رسالة مكتوبة لرسالة 512 بايت بسيطة []byte(strings.Repeat("1234", 128)) .

تحرير: متاح الآن للاختبار الرئيسي في مكتبتي.

للتوضيح ، هذا مع قاموس في وضع الاستيلاء على السياق ، إنها رسالة 50 ب مخصصة إذا لم يكن هناك قاموس.

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات