Skip to content

Commit

Permalink
proc_maps: Parse address and device without allocating (prometheus#572)
Browse files Browse the repository at this point in the history
In our project we need to parse proc maps pretty frequently and we've
noticed that there are lots of small allocations coming from parsing the
device and addresses from procfs' maps file.

The rough split of memory allocated is:
- bufio.(*Scanner).Text: 25%
- strings.Split: 50%
- string.Fields: 25%

The two callers of strings.Split are the two parsing functions that we
are optimising here. I've added some benchmarks to show the
improvements.

Before
======

```
$ go test -benchmem -run=^$ -bench ^BenchmarkParse.*$ github.com/prometheus/procfs
goos: linux
goarch: amd64
pkg: github.com/prometheus/procfs
cpu: Intel(R) Core(TM) i7-8700K CPU @ 3.70GHz
BenchmarkParseAddress-12        12218004               123.0 ns/op            32 B/op          1 allocs/op
BenchmarkParseDevice-12         15074881                85.11 ns/op           32 B/op          1 allocs/op
PASS
ok      github.com/prometheus/procfs    2.978s
```

After
=====

```
$ go test -benchmem -run=^$ -bench ^BenchmarkParse.*$ github.com/prometheus/procfs
goos: linux
goarch: amd64
pkg: github.com/prometheus/procfs
cpu: Intel(R) Core(TM) i7-8700K CPU @ 3.70GHz
BenchmarkParseAddress-12        28619314                50.45 ns/op            0 B/op          0 allocs/op
BenchmarkParseDevice-12         49721935                29.66 ns/op            0 B/op          0 allocs/op
PASS
ok      github.com/prometheus/procfs    2.991s
```

Signed-off-by: Francisco Javier Honduvilla Coto <javierhonduco@gmail.com>
Co-authored-by: Ben Kochie <superq@gmail.com>
  • Loading branch information
2 people authored and jritter committed Jul 15, 2024
1 parent ae3259d commit 5c138b7
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 10 deletions.
20 changes: 10 additions & 10 deletions proc_maps.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,17 @@ type ProcMap struct {
// parseDevice parses the device token of a line and converts it to a dev_t
// (mkdev) like structure.
func parseDevice(s string) (uint64, error) {
toks := strings.Split(s, ":")
if len(toks) < 2 {
return 0, fmt.Errorf("%w: unexpected number of fields, expected: 2, got: %q", ErrFileParse, len(toks))
i := strings.Index(s, ":")
if i == -1 {
return 0, fmt.Errorf("%w: expected separator `:` in %s", ErrFileParse, s)
}

major, err := strconv.ParseUint(toks[0], 16, 0)
major, err := strconv.ParseUint(s[0:i], 16, 0)
if err != nil {
return 0, err
}

minor, err := strconv.ParseUint(toks[1], 16, 0)
minor, err := strconv.ParseUint(s[i+1:], 16, 0)
if err != nil {
return 0, err
}
Expand All @@ -93,17 +93,17 @@ func parseAddress(s string) (uintptr, error) {

// parseAddresses parses the start-end address.
func parseAddresses(s string) (uintptr, uintptr, error) {
toks := strings.Split(s, "-")
if len(toks) < 2 {
return 0, 0, fmt.Errorf("%w: invalid address", ErrFileParse)
idx := strings.Index(s, "-")
if idx == -1 {
return 0, 0, fmt.Errorf("%w: expected separator `-` in %s", ErrFileParse, s)
}

saddr, err := parseAddress(toks[0])
saddr, err := parseAddress(s[0:idx])
if err != nil {
return 0, 0, err
}

eaddr, err := parseAddress(toks[1])
eaddr, err := parseAddress(s[idx+1:])
if err != nil {
return 0, 0, err
}
Expand Down
37 changes: 37 additions & 0 deletions proc_maps64_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,40 @@ func TestProcMaps(t *testing.T) {
}

}

var start, end uintptr

func BenchmarkParseAddress(b *testing.B) {
b.ReportAllocs()
var (
s, e uintptr
err error
)
for i := 0; i < b.N; i++ {
s, e, err = parseAddresses("7f7d7469e000-7f7d746a0000")
if err != nil {
b.Fatal(err)
}
}
// Prevent the compiler from optimizing away benchmark code.
start = s
end = e
}

var device uint64

func BenchmarkParseDevice(b *testing.B) {
b.ReportAllocs()
var (
d uint64
err error
)
for i := 0; i < b.N; i++ {
d, err = parseDevice("00:22")
if err != nil {
b.Fatal(err)
}
}
// Prevent the compiler from optimizing away benchmark code.
device = d
}

0 comments on commit 5c138b7

Please sign in to comment.