From cbc466be8236d0d9c4034a95dca18ef8867aa976 Mon Sep 17 00:00:00 2001 From: Dylan Reimerink Date: Sun, 5 Nov 2023 16:16:03 +0100 Subject: [PATCH] btf: Optimize string table for globally increasing offsets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After doing some observations while parsing VMLinux, it seems that the lookup access pattern for strings is mostly globally increasing with with some patches of seemingly unpridicable offsets. This is a sample of the access pattern, numbers are the index into the string table, not the offset: ``` 1112 1113 1114 348 78 1115 1116 372 1117 1118 1119 ``` This commit adds logic to track this globally increasing offset and checks if the if the offset at the next expected index matches the offset we are looking for. If it does, we can skip the binary search. In VMLinux this fast path is taken about 50% of the time, resulting in about a 3% speedup. ``` goos: linux goarch: amd64 pkg: github.com/cilium/ebpf/btf cpu: 11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz │ before.txt │ after.txt │ │ sec/op │ sec/op vs base │ ParseVmlinux-16 47.53m ± 1% 45.97m ± 1% -3.27% (p=0.000 n=100) │ before.txt │ after.txt │ │ B/op │ B/op vs base │ ParseVmlinux-16 31.45Mi ± 0% 31.45Mi ± 0% ~ (p=0.704 n=100) │ before.txt │ after.txt │ │ allocs/op │ allocs/op vs base │ ParseVmlinux-16 534.1k ± 0% 534.1k ± 0% ~ (p=0.677 n=100) ``` Signed-off-by: Dylan Reimerink --- btf/strings.go | 16 +++++++++++++++- btf/strings_test.go | 2 +- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/btf/strings.go b/btf/strings.go index f8ae08cfb..5f9158bf7 100644 --- a/btf/strings.go +++ b/btf/strings.go @@ -15,6 +15,7 @@ import ( type stringTable struct { base *stringTable offsets []uint32 + incrIdx 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.incrIdx+1 < len(st.offsets) && st.offsets[st.incrIdx+1] == offset { + st.incrIdx++ + return st.strings[st.incrIdx], 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.incrIdx+1 { + st.incrIdx = 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} }