Skip to content

Commit

Permalink
fix: improve memory&cpu consumption (#22)
Browse files Browse the repository at this point in the history
Enhancement #22

improve memory&cpu consumption  (#22)



* parser_test: ReportAllocs

* reader: avoid memory escape and unsafe byte to string

* types: do not malloc base type

* types: batch allocate StackFrame

* batch allocate constants

* cache typeFn in ClassMetadata

* checkpoint: preallocate class

* preallocate StackTrace field Frames

* preallocate StackFrame

* stream chunk reader and cache event

* classes store pointer to ClassMetadata

* types: enhancement range class.Fields

* types: remove useless resolve

* checkpoint: fix preallocate StackTrace field Frames

* checkpoint: fix preallocate StackTrace field Frames

* let unsafeByteToString configurable

* move parseBaseTypeAndDrops global and add test

* fix conflict

* add async-profiler test

* fix example_parsed.json.gz

* fix getPointerToStackFrames

* move unsafeByteToString into ChunkParseOptions

* modify README.md
  • Loading branch information
zdyj3170101136 authored Aug 11, 2023
1 parent 222d698 commit e11aad6
Show file tree
Hide file tree
Showing 16 changed files with 1,110 additions and 778 deletions.
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,21 @@ The parser API is pretty straightforward:
func Parse(r io.Reader) ([]Chunk, error)
```

Parser returns a slice of chunks, each containing a slice of events. It should be used like this:
Parser returns a slice of chunks, for each chunk call chunk.Next and then read chunk.Event.

It should be used like this:

```go
chunks, err := parser.Parse(reader)
for _, chunk := range chunks {
for chunk.Next() {
chunk.Event // it may be reused, copy it if you need the event after another call to Next
}
err = chunk.Err()
if err != nil {
panic(err)
}
}
```

Check the [main](./main.go) package for further details. It can also be used to validate the parser works with your data and get some basic stats.
Expand Down
127 changes: 120 additions & 7 deletions parser/checkpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,27 +39,140 @@ func (c *CheckpointEvent) Parse(r reader.Reader, classes ClassMap, cpools PoolMa
if err != nil {
return fmt.Errorf("unable to parse constant pool class: %w", err)
}
m, err := r.VarInt()
if err != nil {
return fmt.Errorf("unable to parse constant pool's number of constants: %w", err)
}
cm, ok := cpools[int(classID)]
if !ok {
cpools[int(classID)] = &CPool{Pool: make(map[int]ParseResolvable)}
cpools[int(classID)] = &CPool{Pool: make(map[int]ParseResolvable, m)}
cm = cpools[int(classID)]
}
m, err := r.VarInt()
if err != nil {
return fmt.Errorf("unable to parse constant pool's number of constants: %w", err)
class, ok := classes[int(classID)]
if !ok {
return fmt.Errorf("unexpected class %d", classID)
}
var (
results []ParseResolvable
preAllocateResults = true
contantsSlice = getConstantsSlice(int(m), class.numConstants)
)
// preallocate common class in async-profiler
results = make([]ParseResolvable, m)
switch class.Name {
case "java.lang.Thread":
threads := make([]Thread, m)
for i := range threads {
threads[i].constants = contantsSlice[i]
results[i] = &threads[i]
}
case "jdk.types.StackTrace":
var classStackFrames *ClassMetadata
for _, class := range classes {
if class.Name == "jdk.types.StackFrame" {
classStackFrames = class
break
}
}
var (
stackFrames []StackFrame
indexStackFrames int
)
createStackFrames := func() ParseResolvable {
if indexStackFrames >= len(stackFrames) {
stackFrames = make([]StackFrame, m)
contantsSlice = getConstantsSlice(int(m), classStackFrames.numConstants)
for i := range stackFrames {
stackFrames[i].constants = contantsSlice[i]
}
indexStackFrames = 0
}
result := &stackFrames[indexStackFrames]
indexStackFrames++
return result
}
for i, class := range classes {
if class.Name == "jdk.types.StackFrame" {
class.typeFn = createStackFrames
classes[i] = class
break
}
}
var pointerToStackFrames []*StackFrame
indexPointerToStackFrames := 0
getPointerToStackFrames := func(n int) []*StackFrame {
if n > int(m) {
return make([]*StackFrame, n)[:0]
}
if indexPointerToStackFrames+n > len(pointerToStackFrames) {
pointerToStackFrames = make([]*StackFrame, m)
indexPointerToStackFrames = 0
}
result := pointerToStackFrames[indexPointerToStackFrames : indexPointerToStackFrames+n]
indexPointerToStackFrames += n
return result[:0]
}
stackTraces := make([]StackTrace, m)
for i := range stackTraces {
stackTraces[i].getPointerToStackFrames = getPointerToStackFrames
stackTraces[i].constants = contantsSlice[i]
results[i] = &stackTraces[i]
}
case "jdk.types.Method":
methods := make([]Method, m)
for i := range methods {
methods[i].constants = contantsSlice[i]
results[i] = &methods[i]
}
case "java.lang.Class":
classes := make([]Class, m)
for i := range classes {
classes[i].constants = contantsSlice[i]
results[i] = &classes[i]
}
case "jdk.types.Package":
packages := make([]Package, m)
for i := range packages {
packages[i].constants = contantsSlice[i]
results[i] = &packages[i]
}
case "jdk.types.Symbol":
symbols := make([]Symbol, m)
for i := range symbols {
symbols[i].constants = contantsSlice[i]
results[i] = &symbols[i]
}
default:
preAllocateResults = false
}
// TODO: assert m is small enough
for j := 0; j < int(m); j++ {
idx, err := r.VarLong()
if err != nil {
return fmt.Errorf("unable to parse contant's index: %w", err)
}
v, err := ParseClass(r, classes, cpools, classID)
if err != nil {
return fmt.Errorf("unable to parse constant type %d: %w", classID, err)

var v ParseResolvable
if preAllocateResults {
v = results[j]
err = v.Parse(r, classes, cpools, class)
} else {
v, err = ParseClass(r, classes, cpools, classID)
if err != nil {
return fmt.Errorf("unable to parse constant type %d: %w", classID, err)
}
}
cm.Pool[int(idx)] = v
}
}
return nil
}

func getConstantsSlice(size int, num int) [][]constant {
constants := make([]constant, size*num)
contantsSlice := make([][]constant, size)
for i := range contantsSlice {
contantsSlice[i] = constants[i*num : (i+1)*num][:0]
}
return contantsSlice
}
Loading

0 comments on commit e11aad6

Please sign in to comment.