jfr 读取优化

264 阅读2分钟

grafana 提供一个读取 jfr 文件的库: github.com/grafana/jfr…

以组件库维度查看,该库分配了大部分的内存,遂做一些优化:

截屏2023-08-01 下午2.58.56.png

初始性能:

BenchmarkParse-16              7         156254563 ns/op        43622763 B/op     384333 allocs/op

reader 避免使用 binary.Read

reader 提供 []byte 到基础类型,比如 int, string 等类型的转换。

原先使用了 binary.Read, 导致内存都逃逸到堆上去了。

func (r reader) Boolean() (bool, error) {  
var n int8  
err := binary.Read(r, binary.BigEndian, &n)  
if n == 0 {  
return false, err  
}  
return true, err  
}

我们去除直接使用 binary.Read 的方式:

type coder struct { 
    order binary.ByteOrder  
    buf []byte  
    offset int  
}  
  
// decoder implements binary.Read() utility but avoid memory escape  
type decoder coder  
  
func (d *decoder) bool() bool {  
    x := d.buf[d.offset]  
    d.offset++  
    return x != 0  
}

基础类型不分配在堆上

在 jfr 解析中,为了通用,所有的结构体都会实现一个 ParseResolvable 类型,这会导致基础类型的数据也都分配在堆上。

var types = map[string]func() ParseResolvable{  
 "boolean": func() ParseResolvable { return new(Boolean) },  
 "byte": func() ParseResolvable { return new(Byte) },  
 // TODO: char  
 "double": func() ParseResolvable { return new(Double) },  
 "float": func() ParseResolvable { return new(Float) },  
}

func toByte(p Parseable) (int8, error) {  
x, ok := p.(*Byte)  
if !ok {  
return 0, errors.New("not a Byte")  
}  
return int8(*x), nil  
}

对基础类型的数据做判断,避免分配在堆上:

func toByte(r reader.Reader) (int8, error) {  
return r.Byte()  
}

批量分配 class

在 jfr 文件中,有一类称为 cpool,会给每一种类型分配一个唯一的 id,然后其他类型通过 id 的方式去引用它们。

之前的方式是对于每个 id,我们分配一个新的结构体:

"java.lang.Thread": func() ParseResolvable { return new(Thread) },

现在改为批量分配:

threads := make([]Thread, m)  
for i := range threads {  
threads[i].constants = contantsSlice[i]  
results[i] = &threads[i]  
}

stream reader

原先,jfr 读取器会一次将所有 events 读取并放于内存中。

在改造成 stream reader 形式之后:

for chunk.Next() {
  chunk.Event // 
}
chunk.Err()

我们可以缓存最常见的 event type, 复用它们:

type Chunk struct {  

// Cache for common record types  
executionSample ExecutionSample  
threadPark ThreadPark  
objectAllocationInNewTLAB ObjectAllocationInNewTLAB  
cpuLoad CPULoad  
activeSetting ActiveSetting  
initialSystemProperty InitialSystemProperty  
nativeLibrary NativeLibrary  
}

总结

优化后:

BenchmarkParse-16             22          52101846 ns/op        14973117 B/op       1177 allocs/op

我们减少了 99% 的内存分配以及 65 % 的 cpu 开销。

pr: github.com/grafana/jfr…