I have test go channel memory usage and found that it differs from channel input frequency while the amount of goroutines are the same.
As the code below, I create thousands of goroutines, which produce data to its own channel and consume data from same channel.
By changing only the variable "interval" of producer, I can see the virtual memory and resident memory varies too by monitoring with command "top".
And the shorter is the interval, the usage of memory is larger.
Does anyone know what happen?
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")
}
pprof can tell you where you spend memory. Simply add an import statement for the net/http/pprof package and start an HTTP server with the http.DefaultServeMux:
import _ "net/http/pprof"
func main() {
go func() { log.Fatal(http.ListenAndServe(":4000", nil)) }()
//...
}
While the program is running, run the pprof tool to look at various statistics about your program. Since you are concerned about memory usage, the heap profile (memory-in-use) is probably most relevant.
$ 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
Unsurprisingly, the huge amount of time.Timer
s you are creating with time.After
accounts for pretty much all of the memory in use.
Think about it: With an interval of 250ms you are creating timers 4 times faster than with an interval of 1s. However, the lifetime of the timers is not proportional to the interval -- it is constant at 60 seconds. So at any given point you have 4*60=240 times more timers alive.
From the docs of time.After:
After waits for the duration to elapse and then sends the current time on the returned channel. It is equivalent to NewTimer(d).C. The underlying Timer is not recovered by the garbage collector until the timer fires. If efficiency is a concern, use NewTimer instead and call Timer.Stop if the timer is no longer needed.
So create a single timer per readLoop
and re-use it. You can further reduce memory usage by using a channel of empty struct values instead of a channel of booleans:
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)
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With