This is a creation in Article, where the information may have evolved or changed.
Golang is a very interesting language, the first time to see it introduced, it is very like. Six months ago to join the American Regiment, have the opportunity to write a few online programs. One of the programs router to forward tens of millions of requests per day. Since the route path needs to be determined based on the requested content, it needs to load hundreds of thousands of deal (US-order) information into memory for querying. The problem is, using the hundreds of thousands of data from map makes the GC very hard.
Deal data
// Deal的定义typeDealTinystruct{Dealid int32Classid int32Mttypeid int32Bizacctidint32Isonline boolGeocnt int32}
GC pauses
Write a simple Web program with go, set GOGCTRACE the environment variable to start the program after 1, use the WRK stress test, observe the GC pause time played by the console.
GOGCTRACE=1 go run gc.go # 设置环境变量,go gc时会打印详细信息wrk http://localhost:8080/ -d 10s # 压力测试,发送大量请求,让程序“忙”起来,触发gc
Test program main part code:
func Main() {Const SIZE = 500000 //500,000m := Make(Map[Int32]Dealtiny, SIZE) for I := 0; I < SIZE; I++ { //Put data into memorym[Rand.Int31()] = Dealtiny{}}http.Handlefunc("/", func(W http.Responsewriter, R *http.Request) {//Simulate memory allocations, do some calculationsN := Rand.INTN(4096) + 1024x768Buffer := Make([]int, N) for I := 0; I < N; I++ {Buffer[I] = Rand.INTN(1024x768)}C := 0 for I := 0; I < N; I++ {if Buffer[I] > + {C += 1}}FMT.fprintf(W, "N:%d, more than count:%d", N, C)})Log.Fatal(http.Listenandserve(": 8080", Nil))}
Partial output of the program in the console
# Go 1.1.1; Linux 3.2.0; CPU Intel (R) Core (TM) i7-2600 CPU 3.40GHzGc83(1): 8+0+0 MS, 19455-3211 MB(1291202-1287991)objects, 0(0)Handoff, 0(0)Steal, 0/0/0 yieldsgc84(1): 8+0+0 MS, 19087-3213 MB(1307079-1303866)objects, 0(0)Handoff, 0(0)Steal, 0/0/0 yieldsgc85(1): 8+0+0 MS, 18935-3212 MB(1322802-1319590)objects, 0(0)Handoff, 0(0)Steal, 0/0/0 yields
The GC pause time is 8ms, and the online CPU is lower than the test machine, and is a virtual machine, the pause time is longer than 8ms. Such a long pause is clearly unacceptable. Need to find ways to optimize.
Check out the code for Go src/pkg/runtime/mgc0.c#985 found that GC requires a scan of a map key and value, which is naturally quite expensive.
The
Go does not have as many parameters as the JVM can adjust, and it is not generational recycling. The way to optimize GC is only by optimizing the program. But go has one advantage: there is a real array (and just an array of referece). Go's GC algorithm is Mark and Sweep,array is friendly to this: The entire array is processed at once. You can use an array to implement map with open addressing, which optimizes the GC (which also reduces memory usage, as you can see later)
// DealMap 为array backend hash tabledm:=NewDealMap(SIZE)fori:=0;i<SIZE;i++{ dm.Put(DealTiny{Dealid:rand.Int31()})}
This time, the GC log is
gc80(1)(507340-506537) objects, 0(0) handoff, 0(0) steal, 0/0/0 yieldsgc81(1)(513722-512919) objects, 0(0) handoff, 0(0) steal, 0/0/0 yieldsgc82(1)(520260-519457) objects, 0(0) handoff, 0(0) steal, 0/0/0 yields
As you can see, GC recycling is very fast (0MS), and memory usage is reduced from 31M to 12M after the original GC. The optimization effect is very obvious.
The realization of Dealmap
type Dealmap struct { Table []Dealtiny Buckets int size int}//round to a multiple of the nearest 2func minbuckets(v int) int { v-- v |= v >> 1 v |= v >> 2 v |= v >> 4 v |= v >> 8 v |= v >> - v++ return v}func HashInt32(x int) int { x = ((x >> -) ^ x) * 0x45d9f3b x = ((x >> -) ^ x) * 0x45d9f3b x = ((x >> -) ^ x) return x}func Newdealmap(maxsize int) *Dealmap { Buckets := minbuckets(maxsize) return &Dealmap{size: 0, Buckets: Buckets, Table: Make([]Dealtiny, Buckets)}}//TODO Rehash policyfunc (m *Dealmap) Put(D Dealtiny) { Num_probes, Bucket_count_minus_one := 0, m.Buckets-1 Bucknum := HashInt32(int(D.Dealid)) & Bucket_count_minus_one for { if m.Table[Bucknum].Dealid == 0 { //INSERT, does not support putting deal with ID 0 m.size += 1 m.Table[Bucknum] = D return } if m.Table[Bucknum].Dealid == D.Dealid { //Update m.Table[Bucknum] = D return } Num_probes += 1 //Open addressing with Linear probing Bucknum = (Bucknum + Num_probes) & Bucket_count_minus_one }}func (m *Dealmap) Get(ID Int32) (Dealtiny, BOOL) { Num_probes, Bucket_count_minus_one := 0, m.Buckets-1 Bucknum := HashInt32(int(ID)) & Bucket_count_minus_one for { if m.Table[Bucknum].Dealid == ID { return m.Table[Bucknum], true } if m.Table[Bucknum].Dealid == 0 { return m.Table[Bucknum], false } Num_probes += 1 Bucknum = (Bucknum + Num_probes) & Bucket_count_minus_one }}