This page looks best with JavaScript enabled

Optimizing Go Applications in Containers

1. Memory Alignment

Arrange the fields inside a struct from largest to smallest.

This reduces memory footprint.

  • Install the fieldalignment tool
1
go install golang.org/x/tools/go/analysis/passes/fieldalignment/cmd/fieldalignment@latest
  • Analyze and fix memory alignment
1
2
3
4
5
6
7
8
9
fieldalignment -fix  ./...

/Users/shaowenchen/Code/app/config/config.go:136:14: struct with 32 pointer bytes could be 24
/Users/shaowenchen/Code/app/config/config.go:150:11: struct of size 96 could be 88
/Users/shaowenchen/Code/app/config/config.go:166:14: struct of size 152 could be 144
/Users/shaowenchen/Code/app/config/config.go:194:12: struct with 80 pointer bytes could be 72
/Users/shaowenchen/Code/app/config/config.go:209:12: struct with 56 pointer bytes could be 40
/Users/shaowenchen/Code/app/dao/gormx/gorm.go:12:13: struct with 16 pointer bytes could be 8
/Users/shaowenchen/Code/app/dao/gormx/entity/cluster.go:5:14: struct with 128 pointer bytes could be 104
  • Look at the optimizations fieldalignment performs

Before optimization, struct of size 96

1
2
3
4
5
6
7
8
type CORS struct {
	Enable           bool
	AllowOrigins     []string
	AllowMethods     []string
	AllowHeaders     []string
	AllowCredentials bool
	MaxAge           int
}

After optimization, struct of size 88

1
2
3
4
5
6
7
8
type CORS struct {
	AllowOrigins     []string
	AllowMethods     []string
	AllowHeaders     []string
	MaxAge           int
	Enable           bool
	AllowCredentials bool
}

fieldalignment automatically sorts the fields in a struct, from largest to smallest.

The reason is that during compilation the Go compiler, for the sake of CPU access efficiency, applies a certain memory alignment strategy to ensure that a complete field can be read in as close to a single atomic read as possible. But this strategy leaves holes in structs in memory, which increases memory usage.

2. Setting a Reasonable GOMAXPROCS

Write a program that gets the current CPU core count and shows the current GOMAXPROCS value.

1
2
3
4
5
6
func main() {
	cpu := runtime.NumCPU()
	fmt.Println("Current CPU COUNT =", cpu)
	maxProcs := runtime.GOMAXPROCS(-1)
	fmt.Println("Current GOMAXPROCS =", maxProcs)
}

Pod Request CPU is 10, Limit CPU is 20.

1
2
Current CPU COUNT = 32
Current GOMAXPROCS = 32

The value of GOMAXPROCS is the CPU core count of the physical machine, not the container’s CPU core count. An excessively large GOMAXPROCS leads to severe context switching and wasted CPU. In a container environment, a Go program cannot set GOMAXPROCS optimally on its own; you need to set it based on the container’s CPU core count.

  • The first way is to set the GOMAXPROCS environment variable, which specifies the maximum number of Ps
1
export GOMAXPROCS=8
1
2
Current CPU COUNT = 32
Current GOMAXPROCS = 8

The program automatically reads the value of GOMAXPROCS from the environment variable and applies it.

  • The second way is to use the automaxprocs package to set GOMAXPROCS automatically.
1
2
3
4
import _ "go.uber.org/automaxprocs"
func main() {
  // ...
}

After the package is imported, the init method runs automatically, gets the Pod’s CPU Limit value, and sets GOMAXPROCS.

1
2
3
4
 ./default
maxprocs: Updating GOMAXPROCS=20: determined from CPU quota
Current CPU COUNT = 32
Current GOMAXPROCS = 20

If you use both at once, the environment variable takes priority over the automaxprocs package.

1
export GOMAXPROCS=8
1
2
3
maxprocs: Honoring GOMAXPROCS="8" as set in environment
Current CPU COUNT = 32
Current GOMAXPROCS = 8

WeChat Official Account
WRITTEN BY
WeChat Official Account