Golang Manually managing Memory

Source: Internet
Author: User

Author: John graham-cumming. Click here for the original text. Translation: Lubia Yang (expired)

I introduced our use of Lua a few days ago, implement our new Web application Firewall.

Another language that has become very popular in CloudFlare (the author's company) is Golang. In the past, I wrote a how-to use go to introduce a Web service like Railgun.

There is a big challenge in writing long-running Web services in a GC-Golang language, which is memory management.

In order to understand Golang memory management, it is necessary to dig deep into run-time source code. There are two processes that differentiate the memory that the application is no longer using, and when they do not seem to be used again, return them to the operating system (called scavenging in the Golang source code).

Here is a simple program that creates a lot of garbage (garbage), creating an array of 5,000,000 to 10,000,000 bytes per second. The program maintains 20 such arrays and the others are discarded. The program is designed to simulate a very common situation: over time, different parts of the program request memory, some are retained, but most are no longer reused. In Go language network programming, when using Goroutines to process network connections and network requests (networks connections or requests), Usually goroutines will apply for a piece of memory (such as slice to store the received data) and then no longer use them. Over time, there will be a large amount of memory being used by the network connections, which connects the accumulated garbage come and gone.

123456789101112131415161718192021222324252627282930313233343536373839 package mainimport (      "fmt"    "math/rand"     "runtime"    "time"func makeBuffer() []byte {      returnmake([]byte, rand.Intn(5000000)+5000000)  }func main() {      pool := make([][]byte, 20)    var m runtime.MemStats      makes := 0      for        b := makeBuffer()        makes += 1        i := rand.Intn(len(pool))        pool[i] = b        time.Sleep(time.Second)        bytes := 0        for i := 0; i < len(pool); i++ {            ifpool[i] != nil {                bytes += len(pool[i])            }        }        runtime.ReadMemStats(&m)        fmt.Printf("%d,%d,%d,%d,%d,%d\n", m.HeapSys, bytes, m.HeapAlloc,            m.HeapIdle, m.HeapReleased, makes)    }}

The program uses runtime. The Readmemstats function to get the usage information for the heap. It prints four values,

Heapsys: Memory requested by the program to the application

HeapAlloc: The current allocated memory on the heap

Heapidle: Memory currently not in use on the heap

heapreleased: Recovering memory to the operating system

The GC runs very frequently in Golang (see GOGC environment variable (GOGC environment variable) to understand how to control garbage collection operations), so in run because some memory is marked as "unused", The memory size on the heap changes: This causes HeapAlloc and heapidle to change. Scavenger in Golang frees up memory that is still unused for more than 5 minutes, so heapreleased does not change very often.

The following figure is the case of the above program running for 10 minutes:

(In this and subsequent diagrams, the left axis is the memory size in bytes, and the right axis is the number of program executions)

The red line shows the number of byte buffers in the pool. 20 buffers soon reached 150,000,000 bytes. The top blue line represents the memory that the program requested from the operating system. Stable in 375,000,000 bytes. so the program applied 2.5 times times as much space as it needed!

When the GC occurs, Heapidle and HeapAlloc jump. The Orange Line is the number of times the Makebuffer () is sent.

This excessive memory application is a common problem with GC programs, see this article paper

Quantifying the performance of garbage Collection vs. Explicit Memory Management

The program executes continuously, and the idle memory (i.e. Heapidle) is reused but rarely returned to the operating system.

One way to resolve this problem is to manually manage the memory in the program. For example

Programs can be rewritten like this:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354 package mainimport (    "fmt"    "math/rand"    "runtime"    "time")func makeBuffer() []byte {    returnmake([]byte, rand.Intn(5000000)+5000000)}func main() {    pool := make([][]byte, 20)    buffer := make(chan []byte, 5)    var m runtime.MemStats    makes := 0    for{        var b []byte        select {        caseb = <-buffer:        default:            makes += 1            b = makeBuffer()        }        i := rand.Intn(len(pool))        ifpool[i] != nil {            select {            casebuffer <- pool[i]:                pool[i] = nil            default:            }        }        pool[i] = b        time.Sleep(time.Second)        bytes := 0        for i := 0; i < len(pool); i++ {            ifpool[i] != nil {                bytes += len(pool[i])            }        }        runtime.ReadMemStats(&m)        fmt.Printf("%d,%d,%d,%d,%d,%d\n", m.HeapSys, bytes, m.HeapAlloc,            m.HeapIdle, m.HeapReleased, makes)    }}

The following figure is the case of the above program running for 10 minutes:

This picture shows a completely different situation. The actual buffer used is almost equal to the memory requested from the operating system. At the same time, the GC has little work to do. Only a few heapidle on the heap eventually need to be returned to the operating system.

The key operation of the memory recovery mechanism in this program is a buffered Channel--buffer, in which buffer is a container that can store 5 []byte Slice. When the program requires space, it is first read from buffer using select:

Select {

Case B = <-buffer:

Default:

Makes + = 1

b = Makebuffer ()

}

This will never block because if there is data in the channel, it will be read out, and if the channel is empty (meaning that the receive will block), one will be created.

Use a similar non-blocking mechanism to recycle the slice to buffer:

Select {

Case Buffer <-Pool[i]:

Pool[i] = Nil

Default

}

If the channel of buffer is full, the above write process will block, in which case the default trigger. This simple mechanism can be used to create a shared pool securely, even through channel delivery to achieve the perfect, secure sharing between multiple goroutines.

Similar techniques are used in our actual projects, and the actual in-use (simple version) of the Collector (recycler) is shown below, with a goroutine that handles buffers and is shared among multiple goroutine. Get (get a new buffer) and give (reclaim a buffer to pool) These two channel are used by all goroutines.

The collector maintains a connection to the retrieved buffer and periodically discards any buffer that is too old to be reused (in the example code, this period is one minute). This allows the program to automatically respond to explosive buffers requirements.

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465 66676869707172737475767778798081828384858687888990919293 package mainimport (    "container/list"    "fmt"    "math/rand"    "runtime"    "time")var makes intvar frees intfunc makeBuffer() []byte {    makes += 1    returnmake([]byte, rand.Intn(5000000)+5000000)}type queued struct{    when time.Time    slice []byte}func makeRecycler() (get, give chan []byte) {    get = make(chan []byte)    give = make(chan []byte)     go func() {        q := new(list.List)        for{            ifq.Len() == 0 {                q.PushFront(queued{when: time.Now(), slice: makeBuffer()})            }            e := q.Front()            timeout := time.NewTimer(time.Minute)            select {            caseb := <-give:                timeout.Stop()                q.PushFront(queued{when: time.Now(), slice: b})            caseget <- e.Value.(queued).slice:               timeout.Stop()               q.Remove(e)           case<-timeout.C:               e := q.Front()               fore != nil {                   n := e.Next()                   iftime.Since(e.Value.(queued).when) > time.Minute {                       q.Remove(e)                       e.Value = nil                   }                   e = n               }           }       }    }()    return}func main() {    pool := make([][]byte, 20)    get, give := makeRecycler()    var m runtime.MemStats    for{        b := <-get        i := rand.Intn(len(pool))        ifpool[i] != nil {            give <- pool[i]        }        pool[i] = b         time.Sleep(time.Second)        bytes := 0        fori := 0; i < len(pool); i++ {            ifpool[i] != nil {                bytes += len(pool[i])            }        }        runtime.ReadMemStats(&m)        fmt.Printf("%d,%d,%d,%d,%d,%d,%d\n", m.HeapSys, bytes, m.HeapAlloc             m.HeapIdle, m.HeapReleased, makes, frees)    }}

To execute the program for 10 minutes, the image will look similar to the second picture:

These techniques can be used by programmers to know that some memory can be reused without using GC, which can significantly reduce the memory usage of the program, and can be used in other data types rather than just []byte slice, any type of Go type (user-defined or not (user-defined or not) can be recycled in a similar way.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.