fork of https://github.com/sourcegraph/zoekt
0

Configure Feed

Select the types of activity you want to include in your feed.

build: faster newLinesIndices via bytes.IndexByte and buffer re-use (#680)

Firstly we use bytes.IndexByte for faster newLinesIndices. On my machine this
reduces wall clock time of BenchmarkTagsToSections by 38%. This is faster
since bytes.IndexByte relies on CPU specific optimizations to find the next
new line (eg uses AVX2 if available).

Secondly we reuse nls slice between calls to tagsToSections. I noticed in the
profiler a nonsignificant chunk in the garbage collector. The slice built by
newLinesIndices is allocated and thrown away for each call to tagsToSections.
This means we can re-use it which this commit implements by introducing a
struct storing the buffer. We now use this buffer per shard of symbols we
analyse.

old time/op new time/op delta
188µs ± 7% 101µs ± 3% -46.10% (p=0.000 n=10+10)

old alloc/op new alloc/op delta
79.3kB ± 0% 36.3kB ± 0% -54.24% (p=0.000 n=9+10)

old allocs/op new allocs/op delta
443 ± 0% 441 ± 0% -0.45% (p=0.000 n=10+10)

Test Plan: go test -bench BenchmarkTagsToSections

+50 -17
+42 -11
build/ctags.go
··· 46 46 monitor := newMonitor() 47 47 defer monitor.Stop() 48 48 49 + var tagsToSections tagsToSections 50 + 49 51 for _, doc := range todo { 50 52 if doc.Symbols != nil { 51 53 continue ··· 78 80 continue 79 81 } 80 82 81 - symOffsets, symMetaData, err := tagsToSections(doc.Content, es) 83 + symOffsets, symMetaData, err := tagsToSections.Convert(doc.Content, es) 82 84 if err != nil { 83 85 return fmt.Errorf("%s: %v", doc.Name, err) 84 86 } ··· 109 111 return i + 1 110 112 } 111 113 112 - // tagsToSections converts ctags entries to byte ranges (zoekt.DocumentSection) 113 - // with corresponding metadata (zoekt.Symbol). 114 - func tagsToSections(content []byte, tags []*ctags.Entry) ([]zoekt.DocumentSection, []*zoekt.Symbol, error) { 115 - nls := newLinesIndices(content) 116 - nls = append(nls, uint32(len(content))) 114 + // tagsToSections contains buffers to be reused between conversions of bytes 115 + // ranges to metadata. This is done to reduce pressure on the garbage 116 + // collector. 117 + type tagsToSections struct { 118 + nlsBuf []uint32 119 + } 120 + 121 + // Convert ctags entries to byte ranges (zoekt.DocumentSection) with 122 + // corresponding metadata (zoekt.Symbol). 123 + // 124 + // This can not be called concurrently. 125 + func (t *tagsToSections) Convert(content []byte, tags []*ctags.Entry) ([]zoekt.DocumentSection, []*zoekt.Symbol, error) { 126 + nls := t.newLinesIndices(content) 117 127 symOffsets := make([]zoekt.DocumentSection, 0, len(tags)) 118 128 symMetaData := make([]*zoekt.Symbol, 0, len(tags)) 119 129 ··· 168 178 return symOffsets, symMetaData, nil 169 179 } 170 180 171 - func newLinesIndices(in []byte) []uint32 { 172 - out := make([]uint32, 0, len(in)/30) 173 - for i, c := range in { 174 - if c == '\n' { 175 - out = append(out, uint32(i)) 181 + // newLinesIndices returns an array of all indexes of '\n' aswell as a final 182 + // value for the length of the document. 183 + func (t *tagsToSections) newLinesIndices(in []byte) []uint32 { 184 + // reuse nlsBuf between calls to tagsToSections.Convert 185 + out := t.nlsBuf 186 + if out == nil { 187 + out = make([]uint32, 0, len(in)/30) 188 + } 189 + 190 + finalEntry := uint32(len(in)) 191 + off := uint32(0) 192 + for len(in) > 0 { 193 + i := bytes.IndexByte(in, '\n') 194 + if i < 0 { 195 + out = append(out, finalEntry) 196 + break 176 197 } 198 + 199 + off += uint32(i) 200 + out = append(out, off) 201 + 202 + in = in[i+1:] 203 + off++ 177 204 } 205 + 206 + // save buffer for reuse 207 + t.nlsBuf = out[:0] 208 + 178 209 return out 179 210 } 180 211
+8 -6
build/ctags_test.go
··· 34 34 }, 35 35 } 36 36 37 - secs, _, err := tagsToSections(c, tags) 37 + secs, _, err := (&tagsToSections{}).Convert(c, tags) 38 38 if err != nil { 39 39 t.Fatal("tagsToSections", err) 40 40 } ··· 59 59 }, 60 60 } 61 61 62 - got, _, err := tagsToSections(c, tags) 62 + got, _, err := (&tagsToSections{}).Convert(c, tags) 63 63 if err != nil { 64 64 t.Fatal("tagsToSections", err) 65 65 } ··· 92 92 }, 93 93 } 94 94 95 - got, _, err := tagsToSections(c, tags) 95 + got, _, err := (&tagsToSections{}).Convert(c, tags) 96 96 if err != nil { 97 97 t.Fatal("tagsToSections", err) 98 98 } ··· 118 118 }, 119 119 } 120 120 121 - secs, _, err := tagsToSections(c, tags) 121 + secs, _, err := (&tagsToSections{}).Convert(c, tags) 122 122 if err != nil { 123 123 t.Fatal("tagsToSections", err) 124 124 } ··· 242 242 b.Fatal(err) 243 243 } 244 244 245 + var tagsToSections tagsToSections 246 + 245 247 entries, err := parser.Parse("./testdata/large_file.cc", file) 246 248 if err != nil { 247 249 b.Fatal(err) 248 250 } 249 251 250 - secs, _, err := tagsToSections(file, entries) 252 + secs, _, err := tagsToSections.Convert(file, entries) 251 253 if err != nil { 252 254 b.Fatal(err) 253 255 } ··· 260 262 b.ReportAllocs() 261 263 262 264 for n := 0; n < b.N; n++ { 263 - _, _, err := tagsToSections(file, entries) 265 + _, _, err := tagsToSections.Convert(file, entries) 264 266 if err != nil { 265 267 b.Fatal(err) 266 268 }