Skip to content

Commit

Permalink
sysenc: cache size of structs when finding size of slice of structs
Browse files Browse the repository at this point in the history
In the current Go binary.Size() implementation, the size of struct types
are cached to prevent subsequent reflect based encoding for the same types
over and over again but aren't cached when encoding slices of structs. There
is an allocation everytime when finding the size of structs. See golang/go#2320.

goos: darwin
goarch: arm64
pkg: github.com/cilium/ebpf/internal/sysenc

                                    │ unmarshal_old.txt │          unmarshal_new.txt           │
                                    │      sec/op       │    sec/op     vs base                │
Unmarshal/[]sysenc.explicitPad-8            59.04n ± 0%   39.86n ±  0%  -32.49% (p=0.000 n=10)
Unmarshal/[]sysenc.explicitPad#01-8         59.93n ± 1%   40.83n ±  8%  -31.88% (p=0.000 n=10)
Unmarshal/[]sysenc.explicitPad#02-8         59.70n ± 0%   40.25n ±  1%  -32.58% (p=0.000 n=10)
Unmarshal/[]sysenc.struc-8                  77.95n ± 0%   41.12n ±  0%  -47.25% (p=0.000 n=10)
Unmarshal/[]sysenc.struc#01-8              169.70n ± 0%   87.14n ±  1%  -48.65% (p=0.000 n=10)
Unmarshal/[]sysenc.struc#02-8               197.8n ± 0%   116.5n ±  0%  -41.08% (p=0.000 n=10)

                                    │ unmarshal_old.txt │            unmarshal_new.txt            │
                                    │       B/op        │    B/op     vs base                     │
Unmarshal/[]sysenc.explicitPad-8           8.000 ± 0%     0.000 ± 0%  -100.00% (p=0.000 n=10)
Unmarshal/[]sysenc.explicitPad#01-8        8.000 ± 0%     0.000 ± 0%  -100.00% (p=0.000 n=10)
Unmarshal/[]sysenc.explicitPad#02-8        8.000 ± 0%     0.000 ± 0%  -100.00% (p=0.000 n=10)
Unmarshal/[]sysenc.struc-8                 16.00 ± 0%      0.00 ± 0%  -100.00% (p=0.000 n=10)
Unmarshal/[]sysenc.struc#01-8              48.00 ± 0%     16.00 ± 0%   -66.67% (p=0.000 n=10)
Unmarshal/[]sysenc.struc#02-8              56.00 ± 0%     24.00 ± 0%   -57.14% (p=0.000 n=10)

                                    │ unmarshal_old.txt │            unmarshal_new.txt            │
                                    │     allocs/op     │ allocs/op   vs base                     │
Unmarshal/[]sysenc.explicitPad-8           1.000 ± 0%     0.000 ± 0%  -100.00% (p=0.000 n=10)
Unmarshal/[]sysenc.explicitPad#01-8        1.000 ± 0%     0.000 ± 0%  -100.00% (p=0.000 n=10)
Unmarshal/[]sysenc.explicitPad#02-8        1.000 ± 0%     0.000 ± 0%  -100.00% (p=0.000 n=10)
Unmarshal/[]sysenc.struc-8                 2.000 ± 0%     0.000 ± 0%  -100.00% (p=0.000 n=10)
Unmarshal/[]sysenc.struc#01-8              5.000 ± 0%     1.000 ± 0%   -80.00% (p=0.000 n=10)
Unmarshal/[]sysenc.struc#02-8              5.000 ± 0%     1.000 ± 0%   -80.00% (p=0.000 n=10)

goos: darwin
goarch: arm64
pkg: github.com/cilium/ebpf/internal/sysenc
                                  │ marshal_old.txt │           marshal_new.txt           │
                                  │     sec/op      │   sec/op     vs base                │
Marshal/[]sysenc.explicitPad-8         59.46n ±  0%   39.74n ± 0%  -33.16% (p=0.000 n=10)
Marshal/[]sysenc.explicitPad#01-8      59.85n ± 46%   40.15n ± 1%  -32.92% (p=0.000 n=10)
Marshal/[]sysenc.explicitPad#02-8      59.47n ±  2%   40.10n ± 1%  -32.57% (p=0.000 n=10)
Marshal/[]sysenc.struc-8               78.43n ±  1%   40.93n ± 0%  -47.81% (p=0.000 n=10)
Marshal/[]sysenc.struc#01-8           176.25n ±  1%   95.80n ± 0%  -45.64% (p=0.000 n=10)
Marshal/[]sysenc.struc#02-8            205.8n ±  0%   123.2n ± 2%  -40.14% (p=0.000 n=10)

                                  │ marshal_old.txt │             marshal_new.txt             │
                                  │      B/op       │    B/op     vs base                     │
Marshal/[]sysenc.explicitPad-8         8.000 ± 0%     0.000 ± 0%  -100.00% (p=0.000 n=10)
Marshal/[]sysenc.explicitPad#01-8      8.000 ± 0%     0.000 ± 0%  -100.00% (p=0.000 n=10)
Marshal/[]sysenc.explicitPad#02-8      8.000 ± 0%     0.000 ± 0%  -100.00% (p=0.000 n=10)
Marshal/[]sysenc.struc-8               16.00 ± 0%      0.00 ± 0%  -100.00% (p=0.000 n=10)
Marshal/[]sysenc.struc#01-8            64.00 ± 0%     32.00 ± 0%   -50.00% (p=0.000 n=10)
Marshal/[]sysenc.struc#02-8            80.00 ± 0%     48.00 ± 0%   -40.00% (p=0.000 n=10)

                                  │ marshal_old.txt │             marshal_new.txt             │
                                  │    allocs/op    │ allocs/op   vs base                     │
Marshal/[]sysenc.explicitPad-8         1.000 ± 0%     0.000 ± 0%  -100.00% (p=0.000 n=10)
Marshal/[]sysenc.explicitPad#01-8      1.000 ± 0%     0.000 ± 0%  -100.00% (p=0.000 n=10)
Marshal/[]sysenc.explicitPad#02-8      1.000 ± 0%     0.000 ± 0%  -100.00% (p=0.000 n=10)
Marshal/[]sysenc.struc-8               2.000 ± 0%     0.000 ± 0%  -100.00% (p=0.000 n=10)
Marshal/[]sysenc.struc#01-8            6.000 ± 0%     2.000 ± 0%   -66.67% (p=0.000 n=10)
Marshal/[]sysenc.struc#02-8            6.000 ± 0%     2.000 ± 0%   -66.67% (p=0.000 n=10)

Signed-off-by: kwakubiney <kebiney@hotmail.com>
  • Loading branch information
kwakubiney committed Mar 6, 2024
1 parent 4ad27ae commit 78d5e09
Show file tree
Hide file tree
Showing 9 changed files with 2,264 additions and 4 deletions.
838 changes: 838 additions & 0 deletions internal/sysenc/binary/binary.go

Large diffs are not rendered by default.

902 changes: 902 additions & 0 deletions internal/sysenc/binary/binary_test.go

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions internal/sysenc/binary/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright 2024 Authors of Cilium
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package binary is a fork of the upstream golang library. The modifications
// made from the upstream source code are related with the binary.read and binary.write
// functions to remove certain unwanted allocations
package binary
29 changes: 29 additions & 0 deletions internal/sysenc/binary/native_endian_big.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright 2024 Authors of Cilium
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build armbe || arm64be || m68k || mips || mips64 || mips64p32 || ppc || ppc64 || s390 || s390x || shbe || sparc || sparc64

package binary

type nativeEndian struct {
bigEndian
}

// NativeEndian is the native-endian implementation of [ByteOrder] and [AppendByteOrder].
var NativeEndian nativeEndian
29 changes: 29 additions & 0 deletions internal/sysenc/binary/native_endian_little.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright 2024 Authors of Cilium
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build 386 || amd64 || amd64p32 || alpha || arm || arm64 || loong64 || mipsle || mips64le || mips64p32le || nios2 || ppc64le || riscv || riscv64 || sh || wasm

package binary

type nativeEndian struct {
littleEndian
}

// NativeEndian is the native-endian implementation of [ByteOrder] and [AppendByteOrder].
var NativeEndian nativeEndian
181 changes: 181 additions & 0 deletions internal/sysenc/binary/varint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// Copyright 2024 Authors of Cilium
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package binary

// This file implements "varint" encoding of 64-bit integers.
// The encoding is:
// - unsigned integers are serialized 7 bits at a time, starting with the
// least significant bits
// - the most significant bit (msb) in each output byte indicates if there
// is a continuation byte (msb = 1)
// - signed integers are mapped to unsigned integers using "zig-zag"
// encoding: Positive values x are written as 2*x + 0, negative values
// are written as 2*(^x) + 1; that is, negative numbers are complemented
// and whether to complement is encoded in bit 0.
//
// Design note:
// At most 10 bytes are needed for 64-bit values. The encoding could
// be more dense: a full 64-bit value needs an extra byte just to hold bit 63.
// Instead, the msb of the previous byte could be used to hold bit 63 since we
// know there can't be more than 64 bits. This is a trivial improvement and
// would reduce the maximum encoding length to 9 bytes. However, it breaks the
// invariant that the msb is always the "continuation bit" and thus makes the
// format incompatible with a varint encoding for larger numbers (say 128-bit).

import (
"errors"
"io"
)

// MaxVarintLenN is the maximum length of a varint-encoded N-bit integer.
const (
MaxVarintLen16 = 3
MaxVarintLen32 = 5
MaxVarintLen64 = 10
)

// AppendUvarint appends the varint-encoded form of x,
// as generated by [PutUvarint], to buf and returns the extended buffer.
func AppendUvarint(buf []byte, x uint64) []byte {
for x >= 0x80 {
buf = append(buf, byte(x)|0x80)
x >>= 7
}
return append(buf, byte(x))
}

// PutUvarint encodes a uint64 into buf and returns the number of bytes written.
// If the buffer is too small, PutUvarint will panic.
func PutUvarint(buf []byte, x uint64) int {
i := 0
for x >= 0x80 {
buf[i] = byte(x) | 0x80
x >>= 7
i++
}
buf[i] = byte(x)
return i + 1
}

// Uvarint decodes a uint64 from buf and returns that value and the
// number of bytes read (> 0). If an error occurred, the value is 0
// and the number of bytes n is <= 0 meaning:
//
// n == 0: buf too small
// n < 0: value larger than 64 bits (overflow)
// and -n is the number of bytes read
func Uvarint(buf []byte) (uint64, int) {
var x uint64
var s uint
for i, b := range buf {
if i == MaxVarintLen64 {
// Catch byte reads past MaxVarintLen64.
// See issue https://golang.org/issues/41185
return 0, -(i + 1) // overflow
}
if b < 0x80 {
if i == MaxVarintLen64-1 && b > 1 {
return 0, -(i + 1) // overflow
}
return x | uint64(b)<<s, i + 1
}
x |= uint64(b&0x7f) << s
s += 7
}
return 0, 0
}

// AppendVarint appends the varint-encoded form of x,
// as generated by [PutVarint], to buf and returns the extended buffer.
func AppendVarint(buf []byte, x int64) []byte {
ux := uint64(x) << 1
if x < 0 {
ux = ^ux
}
return AppendUvarint(buf, ux)
}

// PutVarint encodes an int64 into buf and returns the number of bytes written.
// If the buffer is too small, PutVarint will panic.
func PutVarint(buf []byte, x int64) int {
ux := uint64(x) << 1
if x < 0 {
ux = ^ux
}
return PutUvarint(buf, ux)
}

// Varint decodes an int64 from buf and returns that value and the
// number of bytes read (> 0). If an error occurred, the value is 0
// and the number of bytes n is <= 0 with the following meaning:
//
// n == 0: buf too small
// n < 0: value larger than 64 bits (overflow)
// and -n is the number of bytes read
func Varint(buf []byte) (int64, int) {
ux, n := Uvarint(buf) // ok to continue in presence of error
x := int64(ux >> 1)
if ux&1 != 0 {
x = ^x
}
return x, n
}

var errOverflow = errors.New("binary: varint overflows a 64-bit integer")

// ReadUvarint reads an encoded unsigned integer from r and returns it as a uint64.
// The error is [io.EOF] only if no bytes were read.
// If an [io.EOF] happens after reading some but not all the bytes,
// ReadUvarint returns [io.ErrUnexpectedEOF].
func ReadUvarint(r io.ByteReader) (uint64, error) {
var x uint64
var s uint
for i := 0; i < MaxVarintLen64; i++ {
b, err := r.ReadByte()
if err != nil {
if i > 0 && err == io.EOF {
err = io.ErrUnexpectedEOF
}
return x, err
}
if b < 0x80 {
if i == MaxVarintLen64-1 && b > 1 {
return x, errOverflow
}
return x | uint64(b)<<s, nil
}
x |= uint64(b&0x7f) << s
s += 7
}
return x, errOverflow
}

// ReadVarint reads an encoded signed integer from r and returns it as an int64.
// The error is [io.EOF] only if no bytes were read.
// If an [io.EOF] happens after reading some but not all the bytes,
// ReadVarint returns [io.ErrUnexpectedEOF].
func ReadVarint(r io.ByteReader) (int64, error) {
ux, err := ReadUvarint(r) // ok to continue in presence of error
x := int64(ux >> 1)
if ux&1 != 0 {
x = ^x
}
return x, err
}

0 comments on commit 78d5e09

Please sign in to comment.