grafana 提供一个读取 jfr 文件的库: github.com/grafana/jfr…
以组件库维度查看,该库分配了大部分的内存,遂做一些优化:
初始性能:
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 开销。