Является ли использование памяти канала Голанга динамическим? - PullRequest
0 голосов
/ 21 декабря 2018

Я протестировал использование памяти канала go и обнаружил, что она отличается от входной частоты канала, в то время как количество процедур одинаково.

Как и в приведенном ниже коде, я создаю тысячи процедур, которые выдают данные для егособственный канал и использование данных из одного и того же канала.

Изменяя только переменную «интервал» производителя, я вижу, что виртуальная память и резидентная память тоже меняются при мониторинге с помощью команды «top».

И чем короче интервал, тем больше использование памяти.

Кто-нибудь знает, что происходит?

package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
    "time"
)

type Session struct {
    KeepAlive chan bool
}

var count = 1024 * 8 * 4

var interval = 250 * time.Millisecond //3718.0m 3.587g   1.2m S 224.0 23.1

// var interval = 500 * time.Millisecond //2011.2m 1.923g   1.2m S 118.8 12.4

// var interval = 1 * time.Second   //1124.0m 1.059g   1.1m S  73.0  6.8

func main() {

    var gracefulStop = make(chan os.Signal, 1)
    signal.Notify(gracefulStop, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL)

    for i := 0; i < count; i++ {
        go Loop()
    }

    <-gracefulStop
    fmt.Println("gracefulStop")
}

func Loop() (err error) {

    var se *Session
    se = NewSession()

    se.Serve()

    return
}

func NewSession() (s *Session) {

    fmt.Println("NewSession")

    s = &Session{

        KeepAlive: make(chan bool, 1),
    }

    return
}

func (s *Session) Serve() {

    fmt.Println("Serve")

    go s.sendLoop()

    s.readLoop()

    s.Close()

    return
}

func (s *Session) Close() {

    close(s.KeepAlive)
    fmt.Println("Close")
}

// local-------------------------------------------------------

func (s *Session) readLoop() {
    fmt.Println("readLoop")

    sec := time.Duration(1 * time.Minute)

ServerHandlerLoop:
    for {
        select {

        case alive := <-s.KeepAlive:
            if alive == false {
                break ServerHandlerLoop
            }

        case <-time.After(sec):
            fmt.Println("Timeout")
            break ServerHandlerLoop

        }
    }

    fmt.Println("readLoop EXIT")
}

func (s *Session) sendLoop() {

    for {

        s.KeepAlive <- true

        time.Sleep(interval)

    }

    s.KeepAlive <- false

    fmt.Println("ReadMessage EXIT")
}

1 Ответ

0 голосов
/ 21 декабря 2018

pprof может сказать вам, где вы тратите память.Просто добавьте оператор импорта для пакета net / http / pprof и запустите HTTP-сервер с http.DefaultServeMux:

import _ "net/http/pprof"

func main() {
    go func() { log.Fatal(http.ListenAndServe(":4000", nil)) }()

    //...
}

Во время работы программы запустите инструмент pprof, чтобыпосмотрите на различные статистические данные о вашей программе.Поскольку вас беспокоит использование памяти, вероятно, наиболее актуален профиль кучи (используемая память).

$ go tool pprof -top 10 http://localhost:4000/debug/pprof/heap
Fetching profile over HTTP from http://localhost:4000/debug/pprof/heap
File: foo
Build ID: 10
Type: inuse_space
Time: Dec 21, 2018 at 12:52pm (CET)
Showing nodes accounting for 827.57MB, 99.62% of 830.73MB total
Dropped 9 nodes (cum <= 4.15MB)
      flat  flat%   sum%        cum   cum%
  778.56MB 93.72% 93.72%   796.31MB 95.86%  time.NewTimer
   18.25MB  2.20% 95.92%    18.25MB  2.20%  time.Sleep
   17.75MB  2.14% 98.05%    17.75MB  2.14%  time.startTimer
      11MB  1.32% 99.38%       11MB  1.32%  runtime.malg
       2MB  0.24% 99.62%   798.31MB 96.10%  main.(*Session).readLoop
         0     0% 99.62%   798.31MB 96.10%  main.(*Session).Serve
         0     0% 99.62%    18.25MB  2.20%  main.(*Session).sendLoop
         0     0% 99.62%   800.81MB 96.40%  main.Loop
         0     0% 99.62%    11.67MB  1.40%  runtime.mstart
         0     0% 99.62%    11.67MB  1.40%  runtime.newproc.func1
         0     0% 99.62%    11.67MB  1.40%  runtime.newproc1
         0     0% 99.62%    11.67MB  1.40%  runtime.systemstack
         0     0% 99.62%   796.31MB 95.86%  time.After

Неудивительно, что огромное количество time.Timer s, которые вы создаете с помощью time.After составляет почти всю используемую память.

Подумайте об этом: с интервалом 250 мс вы создаете таймеры в 4 раза быстрее, чем с интервалом 1 с.Однако время жизни таймеров не пропорционально интервалу - оно постоянное и составляет 60 секунд.Таким образом, в любой данный момент у вас в 4 * 60 = 240 раз больше живых таймеров.

Из документов времени. After:

After ожидает истечения времени действия и затем отправляеттекущее время на возвращаемом канале.Это эквивалентно NewTimer (d) .C.Базовый таймер не восстанавливается сборщиком мусора, пока не сработает таймер.Если важна эффективность, вместо этого используйте NewTimer и вызовите Timer.Stop, если таймер больше не нужен.

Так что создайте один таймер для readLoop и используйте его повторно.Вы можете дополнительно уменьшить использование памяти, используя канал с пустыми значениями структуры вместо канала с логическими значениями:

type Session struct {
    KeepAlive chan struct{}
}

func (s *Session) readLoop() {
    fmt.Println("readLoop")

    d := 1 * time.Minute
    t := time.NewTimer(d)

loop:
    for {
        select {
        case _, ok := <-s.KeepAlive:
            if !ok {
                break loop
            }

            if !t.Stop() {
                <-t.C
            }
            t.Reset(d)

        case <-t.C:
            fmt.Println("Timeout")
            break loop
        }
    }

    fmt.Println("readLoop EXIT")
}

func (s *Session) sendLoop() {
    defer close(s.KeepAlive)

    for {
        s.KeepAlive <- struct{}{}
        time.Sleep(interval)
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...