diff --git a/btf/strings.go b/btf/strings.go index f8ae08cfb..114d25001 100644 --- a/btf/strings.go +++ b/btf/strings.go @@ -15,6 +15,7 @@ import ( type stringTable struct { base *stringTable offsets []uint32 + prevIdx int strings []string } @@ -61,7 +62,7 @@ func readStringTable(r sizedReader, base *stringTable) (*stringTable, error) { return nil, errors.New("first item in string table is non-empty") } - return &stringTable{base, offsets, strings}, nil + return &stringTable{base, offsets, 0, strings}, nil } func splitNull(data []byte, atEOF bool) (advance int, token []byte, err error) { @@ -84,15 +85,28 @@ func (st *stringTable) Lookup(offset uint32) (string, error) { } func (st *stringTable) lookup(offset uint32) (string, error) { + // Fast path: zero offset is the empty string, looked up frequently. if offset == 0 && st.base == nil { return "", nil } + // Accesses tend to be globally increasing, so check if the next string is + // the one we want. This skips the binary search in about 50% of cases. + if st.prevIdx+1 < len(st.offsets) && st.offsets[st.prevIdx+1] == offset { + st.prevIdx++ + return st.strings[st.prevIdx], nil + } + i, found := slices.BinarySearch(st.offsets, offset) if !found { return "", fmt.Errorf("offset %d isn't start of a string", offset) } + // Set the new increment index, but only if its greater than the current. + if i > st.prevIdx+1 { + st.prevIdx = i + } + return st.strings[i], nil } diff --git a/btf/strings_test.go b/btf/strings_test.go index d34726d49..e97c62672 100644 --- a/btf/strings_test.go +++ b/btf/strings_test.go @@ -112,5 +112,5 @@ func newStringTable(strings ...string) *stringTable { offset += uint32(len(str)) + 1 // account for NUL } - return &stringTable{nil, offsets, strings} + return &stringTable{nil, offsets, 0, strings} }