golang unmarshal & marshal 导致 cup 内存占用率过高
现象
当任务过多时,导致中控 cup 100%。
排查问题发现:
当任务数小于 2000 时:
当任务数大于 7000 时:
分析原因:
func fn(){
res := initRes()
result := make(map[string]*TranscodeEntity)
for k, v := range res {
taskId := k
taskEntity := &TranscodeEntity{}
err := json.Unmarshal(v, taskEntity)
if err != nil {
continue
}
result[taskId] = taskEntity
}
}
每次分发任务时,都会查询所有任务,导致 Unmarshal 。结合线上内存分析工具 ppor 显示,当 cup 过高时,marshal & unmarshal 都会各自达到 30% 左右。
且分发任务执行次数频率较高,每分钟执行一次。
验证
生成初使数据
type TranscodeEntity struct {
Weight int `json:"weight"` // 任务权重
TaskId string `json:"taskId"` //任务id
...
Profile string `json:"perfile"`
}
var mark = `{
"weight": 100,
"taskId": "task001",
...
"profile": "high"
}`
func initRes() map[string][]byte {
res := make(map[string][]byte)
for i := 0; i < Max; i++ {
generateUUID, err := uuid.GenerateUUID()
if err != nil {
return nil
}
res[generateUUID] = []byte(mark)
}
return res
}
可能有问题的代码块
func willRefactor() {
res := initRes()
result := make(map[string]*TranscodeEntity)
for k, v := range res {
taskId := k
taskEntity := &TranscodeEntity{}
err := json.Unmarshal(v, taskEntity)
if err != nil {
continue
}
result[taskId] = taskEntity
}
}
验证执行时间和内存
func BenchmarkWillRefactor(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
willRefactor()
}
b.StopTimer()
var memStats runtime.MemStats
runtime.ReadMemStats(&memStats)
fmt.Printf("Memory used: %v bytes\n", memStats)
}
结果
goos: darwin
goarch: arm64
pkg: study_go/feature_test
BenchmarkWillRefactor
Memory Usage: 31029672 bytes/op
Memory Usage: 34238947 bytes/op
Memory Usage: 61603173 bytes/op
BenchmarkWillRefactor-8 10 109_089_525 ns/op
注:
61603173 bytes = 60 mb
109_089_525 ns = 0.1 s
使用更高效的 unmarshal 工具试试
func RefactorFn() {
res := initRes()
result := make(map[string]*TranscodeEntity)
for k, v := range res {
taskId := k
taskEntity := &TranscodeEntity{}
err := jsoniter.Unmarshal(v, taskEntity)
if err != nil {
continue
}
result[taskId] = taskEntity
}
}
验证运行时间和内存使用
func BenchmarkRefactorFn(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
RefactorFn()
}
b.StopTimer()
var memStats runtime.MemStats
runtime.ReadMemStats(&memStats)
fmt.Printf("Memory Usage: %v bytes/op\n", int64(memStats.TotalAlloc)/int64(b.N))
}
结果
goos: darwin
goarch: arm64
pkg: study_go/feature_test
BenchmarkRefactorFn
Memory Usage: 32632032 bytes/op
Memory Usage: 34434436 bytes/op
Memory Usage: 53584024 bytes/op
BenchmarkRefactorFn-8 26 45_508_535 ns/op
优化字段方案
type ShortTranscodeEntity struct {
Weight int `json:"weight"` // 任务权重
TaskId string `json:"taskId"` //任务id
TaskName string `json:"taskName"` //视频标题
TransType int `json:"transType"` //转码类型 0 轮播 1点播
}
var shortMark = `{
"weight": 100,
"taskId": "task001",
"taskName": "example video",
"streamId": "stream001",
}`
func initResShort() map[string][]byte {
res := make(map[string][]byte)
for i := 0; i < Max; i++ {
generateUUID, err := uuid.GenerateUUID()
if err != nil {
return nil
}
res[generateUUID] = []byte(shortMark)
}
return res
}
只减少字段数据,使用 json 解析
func willRefactorShort() {
res := initResShort()
result := make(map[string]*TranscodeEntity)
for k, v := range res {
taskId := k
taskEntity := &TranscodeEntity{}
err := json.Unmarshal(v, taskEntity)
if err != nil {
continue
}
result[taskId] = taskEntity
}
}
func BenchmarkWillRefactorShort(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
willRefactorShort()
}
b.StopTimer()
var memStats runtime.MemStats
runtime.ReadMemStats(&memStats)
fmt.Printf("Memory Usage: %v bytes/op\n", int64(memStats.TotalAlloc)/int64(b.N))
}
结果
goos: darwin
goarch: arm64
pkg: study_go/feature_test
BenchmarkWillRefactorShort
Memory Usage: 12021184 bytes/op
Memory Usage: 12005521 bytes/op
Memory Usage: 21302302 bytes/op
BenchmarkWillRefactorShort-8 81 14_186_038 ns/op
修改字段,改变 struct,使用 json 解析
func willRefactorShort() {
res := initResShort()
result := make(map[string]*ShortTranscodeEntity)
for k, v := range res {
taskId := k
taskEntity := &ShortTranscodeEntity{}
err := json.Unmarshal(v, taskEntity)
if err != nil {
continue
}
result[taskId] = taskEntity
}
}
func BenchmarkWillRefactorShort(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
willRefactorShort()
}
b.StopTimer()
var memStats runtime.MemStats
runtime.ReadMemStats(&memStats)
fmt.Printf("Memory Usage: %v bytes/op\n", int64(memStats.TotalAlloc)/int64(b.N))
}
结果
goos: darwin
goarch: arm64
pkg: study_go/feature_test
BenchmarkWillRefactorShort
Memory Usage: 7692352 bytes/op
Memory Usage: 7671502 bytes/op
Memory Usage: 11377431 bytes/op
BenchmarkWillRefactorShort-8 87 13_443_757 ns/op
改变解析工具,只减少数据
func RefactorFnShort() {
res := initResShort()
result := make(map[string]*TranscodeEntity)
for k, v := range res {
taskId := k
taskEntity := &TranscodeEntity{}
err := jsoniter.Unmarshal(v, taskEntity)
if err != nil {
continue
}
result[taskId] = taskEntity
}
}
func BenchmarkRefactorFnShort(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
RefactorFnShort()
}
b.StopTimer()
var memStats runtime.MemStats
runtime.ReadMemStats(&memStats)
fmt.Printf("Memory Usage: %v bytes/op\n", int64(memStats.TotalAlloc)/int64(b.N))
}
结果
goos: darwin
goarch: arm64
pkg: study_go/feature_test
BenchmarkRefactorFnShort
Memory Usage: 17038000 bytes/op
Memory Usage: 17168781 bytes/op
Memory Usage: 30109440 bytes/op
BenchmarkRefactorFnShort-8 58 19_926_978 ns/op
改变解析工具,改变 struct
func RefactorFnShort() {
res := initResShort()
result := make(map[string]*ShortTranscodeEntity)
for k, v := range res {
taskId := k
taskEntity := &ShortTranscodeEntity{}
err := jsoniter.Unmarshal(v, taskEntity)
if err != nil {
continue
}
result[taskId] = taskEntity
}
}
func BenchmarkRefactorFnShort(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
RefactorFnShort()
}
b.StopTimer()
var memStats runtime.MemStats
runtime.ReadMemStats(&memStats)
fmt.Printf("Memory Usage: %v bytes/op\n", int64(memStats.TotalAlloc)/int64(b.N))
}
goos: darwin
goarch: arm64
pkg: study_go/feature_test
BenchmarkRefactorFnShort
Memory Usage: 12674312 bytes/op
Memory Usage: 12746981 bytes/op
Memory Usage: 21715815 bytes/op
BenchmarkRefactorFnShort-8 62 18_712_932 ns/op
结论
使用 jsoniter.Unmarshal
有效的缩短了运行时长,但是内存使用情况,并没有优化。
所以要优化内存使用情况,需要修改查询手段:
-
使用中间价保存任务信息(redis 等内存数据库)
-
使用分页查询,限制查询数据大小
-
优化数据字段,保存较少数据
-
json 大小,较大时,执行效率 json <= jsoniter,内存使用基本无差距
-
json 大小,较小时,执行效率 json >= jsoniter,内存使用 json < jsoniter