This is a creation in Article, where the information may have evolved or changed.
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 every 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 the Go Language network programming, goroutines is used to process network connections and network requests (network 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 lot of memory being used by the network connection (network connections) to connect the accumulated garbage come and gone.
Package Mainimport ( "FMT" "Math/rand" " Runtime" "Time") func makebuffer () []byte { return Make ([]byte, Rand. INTN (5000000) +5000000) }func Main () { Pool: = make ([][]byte) 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++ { if pool[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:
Package Mainimport ("FMT" "Math/rand" "Runtime" "Time") Func-Makebuffer () []byte {return make ([]byte, Rand. INTN (5000000) +5000000)}func main () {pool: = make ([][]byte,] buffer: = Make (chan []byte, 5) var m runtime. Memstatsmakes: = 0for {var b []byteselect {case b = <-buffer:default:makes + = 1b = Makebuffer ()}i: = Rand. INTN (len (pool)) if pool[i]! = Nil {Select {case buffer <-Pool[i]:p ool[i] = nildefault:}}pool[i] = btime. Sleep (time. Second) Bytes: = 0for I: = 0; I < Len (pool); i++ {if pool[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.
Package Mainimport ("Container/list" "FMT" "Math/rand" "Runtime" "Time") var makes intvar frees IntFunc Mak Ebuffer () []byte {makes + = 1 return make ([]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 {if q.len () = = 0 {q.pushfront (queued{when:time). Now (), Slice:makebuffer ()})} e: = Q.front () Timeout: = time. Newtimer (time. Minute) Select {case b: = <-give:timeout. Stop () Q.pushfront (queued{when:time. Now (), slice:b}) case get <-e.value. (queued). Slice:timeout. Stop () Q.remove (e) Case <-timeout. C:e: = Q.front () for E! = nil {n: = E.next () if TIme. Since (E.value. ( Queued). When) > time. Minute {q.remove (e) E.value = nil} e = n }}}} () Return}func main () {pool: = make ([][]byte,] get, give: = Makerecy Cler () var m runtime. Memstats for {b: = <-get I: = Rand. INTN (len (pool)) if pool[i]! = nil {give <-pool[i]} Pool[i] = b time. Sleep (time. Second) Bytes: = 0 for I: = 0; I < Len (pool); i++ {if pool[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.