Repository: cilium/ebpf
Pull request #427 (2021-09-18 17:09:19)

Directly handle int32 and int64 types in unmarshalBytes()
The common int32 and int64 types are directly handled to avoid
unnecessary heap allocations as happening in the default case.


Patch:
diff --git a/cmd/bpf2go/main.go b/cmd/bpf2go/main.go
index ec79dd2..f827f1b 100644
--- a/cmd/bpf2go/main.go
+++ b/cmd/bpf2go/main.go
@@ -7,7 +7,6 @@ import (
 	"fmt"
 	"go/token"
 	"io"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"sort"
@@ -289,7 +288,7 @@ func (b2g *bpf2go) convert(tgt target, arches []string) (err error) {
 	}
 
 	depFileName := goFileName + ".d"
-	if err := ioutil.WriteFile(depFileName, depFile, 0666); err != nil {
+	if err := os.WriteFile(depFileName, depFile, 0666); err != nil {
 		return fmt.Errorf("can't write dependency file: %s", err)
 	}
 
diff --git a/cmd/bpf2go/output.go b/cmd/bpf2go/output.go
index bd3032c..8c199f6 100644
--- a/cmd/bpf2go/output.go
+++ b/cmd/bpf2go/output.go
@@ -8,7 +8,7 @@ import (
 	"go/scanner"
 	"go/token"
 	"io"
-	"io/ioutil"
+	"os"
 	"path/filepath"
 	"strings"
 	"text/template"
@@ -220,7 +220,7 @@ type writeArgs struct {
 }
 
 func writeCommon(args writeArgs) error {
-	obj, err := ioutil.ReadFile(args.obj)
+	obj, err := os.ReadFile(args.obj)
 	if err != nil {
 		return fmt.Errorf("read object file contents: %s", err)
 	}
diff --git a/elf_reader.go b/elf_reader.go
index 17f60e5..42010f4 100644
--- a/elf_reader.go
+++ b/elf_reader.go
@@ -8,7 +8,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"math"
 	"os"
 	"strings"
@@ -521,7 +520,7 @@ func (ec *elfCode) loadMaps(maps map[string]*MapSpec) error {
 				return fmt.Errorf("map %s: missing flags", mapName)
 			}
 
-			extra, err := ioutil.ReadAll(lr)
+			extra, err := io.ReadAll(lr)
 			if err != nil {
 				return fmt.Errorf("map %s: reading map tail: %w", mapName, err)
 			}
@@ -606,7 +605,7 @@ func (ec *elfCode) loadBTFMaps(maps map[string]*MapSpec) error {
 
 		// Drain the ELF section reader to make sure all bytes are accounted for
 		// with BTF metadata.
-		i, err := io.Copy(ioutil.Discard, rs)
+		i, err := io.Copy(io.Discard, rs)
 		if err != nil {
 			return fmt.Errorf("section %v: unexpected error reading remainder of ELF section: %w", sec.Name, err)
 		}
diff --git a/examples/README.md b/examples/README.md
index 7047e40..3dfd28b 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -1,15 +1,20 @@
 # eBPF Examples
 
-- [kprobe](kprobe/) - Attach a program to the entry or exit of an arbitrary kernel symbol (function).
-- [kprobepin](kprobepin/) - Reuse a pinned map for the kprobe example. It assumes the BPF FS is mounted at `/sys/fs/bpf`.
-- [uretprobe](uretprobe/) - Like a kprobe, but for symbols in userspace binaries (e.g. `bash`).
-- [tracepoint](tracepoint/) - Attach a program to predetermined kernel tracepoints.
-- [ringbuffer](ringbuffer/) - Like the kprobe example, but using a RINGBUF map type.
-- Add your use case(s) here!
+* Kprobe - Attach a program to the entry or exit of an arbitrary kernel symbol (function).
+  * [kprobe](kprobe/) - Kprobe using bpf2go.
+  * [kprobepin](kprobepin/) - Reuse a pinned map for the kprobe example. It assumes the BPF FS is mounted at `/sys/fs/bpf`.
+  * [kprobe_percpu](kprobe_percpu/) - Use a `BPF_MAP_TYPE_PERCPU_ARRAY` map.
+  * [ringbuffer](ringbuffer/) - Use a `BPF_MAP_TYPE_RINGBUF` map.
+* Uprobe - Attach a program to the entry or exit of an arbitrary userspace binary symbol (function).
+  * [uretprobe](uretprobe/) - Uretprobe using bpf2go.
+* Tracepoint - Attach a program to predetermined kernel tracepoints.
+  * [tracepoint_in_c](tracepoint_in_c/) - Tracepoint using bpf2go.
+  * [tracepoint_in_go](tracepoint_in_go/) - Tracepoint using the `ebpf.NewProgram` API and Go eBPF assembler.
+* Add your use case(s) here!
 
 ## How to run
 
 ```bash
 cd ebpf/examples/
-go run -exec sudo [./kprobe, ./uretprobe, ./tracepoint, ...]
+go run -exec sudo [./kprobe, ./uretprobe, ./ringbuffer, ...]
 ```
diff --git a/examples/ringbuffer/main.go b/examples/ringbuffer/main.go
index 3085111..71b7d1f 100644
--- a/examples/ringbuffer/main.go
+++ b/examples/ringbuffer/main.go
@@ -1,3 +1,6 @@
+//go:build linux
+// +build linux
+
 package main
 
 import (
diff --git a/features/map.go b/features/map.go
index 82426c0..d404dfe 100644
--- a/features/map.go
+++ b/features/map.go
@@ -139,7 +139,7 @@ func haveMapType(mt ebpf.MapType) error {
 		return err
 	}
 
-	_, err = internal.BPFMapCreate(createMapTypeAttr(mt))
+	fd, err := internal.BPFMapCreate(createMapTypeAttr(mt))
 
 	switch {
 	// For nested and storage map types we accept EBADF as indicator that these maps are supported
@@ -162,6 +162,9 @@ func haveMapType(mt ebpf.MapType) error {
 	// Wrap unexpected errors.
 	case err != nil:
 		err = fmt.Errorf("unexpected error during feature probe: %w", err)
+
+	default:
+		fd.Close()
 	}
 
 	mc.mapTypes[mt] = err
diff --git a/features/prog.go b/features/prog.go
index f5e2b9a..47cee9e 100644
--- a/features/prog.go
+++ b/features/prog.go
@@ -123,7 +123,7 @@ func haveProgType(pt ebpf.ProgramType) error {
 		return fmt.Errorf("couldn't create the program load attribute: %w", err)
 	}
 
-	_, err = internal.BPFProgLoad(attr)
+	fd, err := internal.BPFProgLoad(attr)
 
 	switch {
 	// EINVAL occurs when attempting to create a program with an unknown type.
@@ -140,6 +140,9 @@ func haveProgType(pt ebpf.ProgramType) error {
 	// Wrap unexpected errors.
 	case err != nil:
 		err = fmt.Errorf("unexpected error during feature probe: %w", err)
+
+	default:
+		fd.Close()
 	}
 
 	pc.progTypes[pt] = err
diff --git a/internal/btf/btf.go b/internal/btf/btf.go
index 0c2524b..2b5f6d2 100644
--- a/internal/btf/btf.go
+++ b/internal/btf/btf.go
@@ -7,7 +7,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"math"
 	"os"
 	"reflect"
@@ -242,7 +241,7 @@ func loadKernelSpec() (*Spec, error) {
 }
 
 func parseBTF(btf io.Reader, bo binary.ByteOrder) ([]rawType, stringTable, error) {
-	rawBTF, err := ioutil.ReadAll(btf)
+	rawBTF, err := io.ReadAll(btf)
 	if err != nil {
 		return nil, nil, fmt.Errorf("can't read BTF: %v", err)
 	}
diff --git a/internal/btf/ext_info.go b/internal/btf/ext_info.go
index 8a5384d..cdae2ec 100644
--- a/internal/btf/ext_info.go
+++ b/internal/btf/ext_info.go
@@ -7,7 +7,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 
 	"github.com/cilium/ebpf/asm"
 	"github.com/cilium/ebpf/internal"
@@ -64,7 +63,7 @@ func parseExtInfos(r io.ReadSeeker, bo binary.ByteOrder, strings stringTable) (f
 
 	// Of course, the .BTF.ext header has different semantics than the
 	// .BTF ext header. We need to ignore non-null values.
-	_, err = io.CopyN(ioutil.Discard, r, remainder)
+	_, err = io.CopyN(io.Discard, r, remainder)
 	if err != nil {
 		return nil, nil, nil, fmt.Errorf("header padding: %v", err)
 	}
diff --git a/internal/btf/strings.go b/internal/btf/strings.go
index 7414f85..9876aa2 100644
--- a/internal/btf/strings.go
+++ b/internal/btf/strings.go
@@ -5,13 +5,12 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 )
 
 type stringTable []byte
 
 func readStringTable(r io.Reader) (stringTable, error) {
-	contents, err := ioutil.ReadAll(r)
+	contents, err := io.ReadAll(r)
 	if err != nil {
 		return nil, fmt.Errorf("can't read string table: %v", err)
 	}
diff --git a/internal/cpu.go b/internal/cpu.go
index d3424ba..3affa1e 100644
--- a/internal/cpu.go
+++ b/internal/cpu.go
@@ -2,7 +2,7 @@ package internal
 
 import (
 	"fmt"
-	"io/ioutil"
+	"os"
 	"strings"
 	"sync"
 )
@@ -24,7 +24,7 @@ func PossibleCPUs() (int, error) {
 }
 
 func parseCPUsFromFile(path string) (int, error) {
-	spec, err := ioutil.ReadFile(path)
+	spec, err := os.ReadFile(path)
 	if err != nil {
 		return 0, err
 	}
diff --git a/internal/version.go b/internal/version.go
index 1a678bf..4915e58 100644
--- a/internal/version.go
+++ b/internal/version.go
@@ -2,7 +2,7 @@ package internal
 
 import (
 	"fmt"
-	"io/ioutil"
+	"os"
 	"regexp"
 	"sync"
 
@@ -109,7 +109,7 @@ func detectKernelVersion() (Version, error) {
 	// Example format: Ubuntu 4.15.0-91.92-generic 4.15.18
 	// This method exists in the kernel itself, see d18acd15c
 	// ("perf tools: Fix kernel version error in ubuntu").
-	if pvs, err := ioutil.ReadFile("/proc/version_signature"); err == nil {
+	if pvs, err := os.ReadFile("/proc/version_signature"); err == nil {
 		// If /proc/version_signature exists, failing to parse it is an error.
 		// It only exists on Ubuntu, where the real patch level is not obtainable
 		// through any other method.
diff --git a/link/kprobe.go b/link/kprobe.go
index 37ee316..b6577b5 100644
--- a/link/kprobe.go
+++ b/link/kprobe.go
@@ -5,7 +5,6 @@ import (
 	"crypto/rand"
 	"errors"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"runtime"
@@ -420,7 +419,7 @@ func probePrefix(ret bool) string {
 func determineRetprobeBit(typ probeType) (uint64, error) {
 	p := filepath.Join("/sys/bus/event_source/devices/", typ.String(), "/format/retprobe")
 
-	data, err := ioutil.ReadFile(p)
+	data, err := os.ReadFile(p)
 	if err != nil {
 		return 0, err
 	}
diff --git a/link/perf_event.go b/link/perf_event.go
index 110da98..7e0443a 100644
--- a/link/perf_event.go
+++ b/link/perf_event.go
@@ -4,7 +4,6 @@ import (
 	"bytes"
 	"errors"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"regexp"
@@ -263,7 +262,7 @@ func uint64FromFile(base string, path ...string) (uint64, error) {
 		return 0, fmt.Errorf("path '%s' attempts to escape base path '%s': %w", l, base, errInvalidInput)
 	}
 
-	data, err := ioutil.ReadFile(p)
+	data, err := os.ReadFile(p)
 	if err != nil {
 		return 0, fmt.Errorf("reading file %s: %w", p, err)
 	}
diff --git a/link/uprobe.go b/link/uprobe.go
index 50299c8..59170ce 100644
--- a/link/uprobe.go
+++ b/link/uprobe.go
@@ -25,6 +25,10 @@ var (
 		value uint64
 		err   error
 	}{}
+
+	// ErrNoSymbol indicates that the given symbol was not found
+	// in the ELF symbols table.
+	ErrNoSymbol = errors.New("not found")
 )
 
 // Executable defines an executable program on the filesystem.
@@ -144,7 +148,7 @@ func (ex *Executable) offset(symbol string) (uint64, error) {
 		}
 		return off, nil
 	}
-	return 0, fmt.Errorf("symbol %s not found", symbol)
+	return 0, fmt.Errorf("symbol %s: %w", symbol, ErrNoSymbol)
 }
 
 // Uprobe attaches the given eBPF program to a perf event that fires when the
diff --git a/marshalers.go b/marshalers.go
index fd6c679..e461d67 100644
--- a/marshalers.go
+++ b/marshalers.go
@@ -92,6 +92,9 @@ var bytesReaderPool = sync.Pool{
 //
 // Prefer using Map.unmarshalKey and Map.unmarshalValue if possible, since
 // those have special cases that allow more types to be encoded.
+//
+// The common int32 and int64 types are directly handled to avoid
+// unnecessary heap allocations as happening in the default case.
 func unmarshalBytes(data interface{}, buf []byte) error {
 	switch value := data.(type) {
 	case unsafe.Pointer:
@@ -116,6 +119,30 @@ func unmarshalBytes(data interface{}, buf []byte) error {
 	case *[]byte:
 		*value = buf
 		return nil
+	case *int32:
+		if len(buf) < 4 {
+			return errors.New("int32 requires 4 bytes")
+		}
+		*value = int32(internal.NativeEndian.Uint32(buf))
+		return nil
+	case *uint32:
+		if len(buf) < 4 {
+			return errors.New("uint32 requires 4 bytes")
+		}
+		*value = internal.NativeEndian.Uint32(buf)
+		return nil
+	case *int64:
+		if len(buf) < 8 {
+			return errors.New("int64 requires 8 bytes")
+		}
+		*value = int64(internal.NativeEndian.Uint64(buf))
+		return nil
+	case *uint64:
+		if len(buf) < 8 {
+			return errors.New("uint64 requires 8 bytes")
+		}
+		*value = internal.NativeEndian.Uint64(buf)
+		return nil
 	case string:
 		return errors.New("require pointer to string")
 	case []byte:
diff --git a/prog.go b/prog.go
index 90016a8..09d906e 100644
--- a/prog.go
+++ b/prog.go
@@ -307,7 +307,10 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions, handles *hand
 		attr.LogSize = uint32(len(logBuf))
 		attr.LogBuf = internal.NewSlicePointer(logBuf)
 
-		_, logErr = internal.BPFProgLoad(attr)
+		fd, logErr = internal.BPFProgLoad(attr)
+		if logErr == nil {
+			fd.Close()
+		}
 	}
 
 	if errors.Is(logErr, unix.EPERM) && logBuf[0] == 0 {


Test patch:
diff --git a/asm/instruction_test.go b/asm/instruction_test.go
index ba723b8..d97dd79 100644
--- a/asm/instruction_test.go
+++ b/asm/instruction_test.go
@@ -5,7 +5,7 @@ import (
 	"encoding/binary"
 	"encoding/hex"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"math"
 	"testing"
 
@@ -55,7 +55,7 @@ func TestSignedJump(t *testing.T) {
 
 	insns[0].Offset = 1
 
-	err := insns.Marshal(ioutil.Discard, binary.LittleEndian)
+	err := insns.Marshal(io.Discard, binary.LittleEndian)
 	if err != nil {
 		t.Error("Can't marshal signed jump:", err)
 	}
diff --git a/cmd/bpf2go/compile_test.go b/cmd/bpf2go/compile_test.go
index 26aaf10..1f925d7 100644
--- a/cmd/bpf2go/compile_test.go
+++ b/cmd/bpf2go/compile_test.go
@@ -2,7 +2,6 @@ package main
 
 import (
 	"bytes"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"reflect"
@@ -68,12 +67,12 @@ func TestReproducibleCompile(t *testing.T) {
 		t.Fatal("Can't compile:", err)
 	}
 
-	aBytes, err := ioutil.ReadFile(filepath.Join(dir, "a.o"))
+	aBytes, err := os.ReadFile(filepath.Join(dir, "a.o"))
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	bBytes, err := ioutil.ReadFile(filepath.Join(dir, "b.o"))
+	bBytes, err := os.ReadFile(filepath.Join(dir, "b.o"))
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -151,14 +150,14 @@ nothing:
 func mustWriteTempFile(t *testing.T, name, contents string) string {
 	t.Helper()
 
-	tmp, err := ioutil.TempDir("", "bpf2go")
+	tmp, err := os.MkdirTemp("", "bpf2go")
 	if err != nil {
 		t.Fatal(err)
 	}
 	t.Cleanup(func() { os.RemoveAll(tmp) })
 
 	tmpFile := filepath.Join(tmp, name)
-	if err := ioutil.WriteFile(tmpFile, []byte(contents), 0660); err != nil {
+	if err := os.WriteFile(tmpFile, []byte(contents), 0660); err != nil {
 		t.Fatal(err)
 	}
 
diff --git a/cmd/bpf2go/main_test.go b/cmd/bpf2go/main_test.go
index ec0fc6e..d198955 100644
--- a/cmd/bpf2go/main_test.go
+++ b/cmd/bpf2go/main_test.go
@@ -3,7 +3,7 @@ package main
 import (
 	"bytes"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"os"
 	"os/exec"
 	"path/filepath"
@@ -31,7 +31,7 @@ func TestRun(t *testing.T) {
 		t.Fatal("No go.mod file in", modRoot)
 	}
 
-	tmpDir, err := ioutil.TempDir("", "bpf2go-module-*")
+	tmpDir, err := os.MkdirTemp("", "bpf2go-module-*")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -60,7 +60,7 @@ func TestRun(t *testing.T) {
 		fmt.Sprintf("-replace=%s=%s", ebpfModule, modRoot),
 	)
 
-	err = run(ioutil.Discard, "foo", tmpDir, []string{
+	err = run(io.Discard, "foo", tmpDir, []string{
 		"-cc", "clang-9",
 		"bar",
 		filepath.Join(dir, "test.c"),
@@ -198,7 +198,7 @@ func TestConvertGOARCH(t *testing.T) {
 
 	b2g := bpf2go{
 		pkg:        "test",
-		stdout:     ioutil.Discard,
+		stdout:     io.Discard,
 		ident:      "test",
 		cc:         "clang-9",
 		sourceFile: tmp + "/test.c",
diff --git a/internal/testutils/bpffs.go b/internal/testutils/bpffs.go
index 5ed9e06..7df15a3 100644
--- a/internal/testutils/bpffs.go
+++ b/internal/testutils/bpffs.go
@@ -1,7 +1,6 @@
 package testutils
 
 import (
-	"io/ioutil"
 	"os"
 	"testing"
 )
@@ -12,7 +11,7 @@ import (
 func TempBPFFS(tb testing.TB) string {
 	tb.Helper()
 
-	tmp, err := ioutil.TempDir("/sys/fs/bpf", "ebpf-test")
+	tmp, err := os.MkdirTemp("/sys/fs/bpf", "ebpf-test")
 	if err != nil {
 		tb.Fatal("Create temporary directory on BPFFS:", err)
 	}
diff --git a/internal/testutils/cgroup.go b/internal/testutils/cgroup.go
index 6c3f33f..9b2af5f 100644
--- a/internal/testutils/cgroup.go
+++ b/internal/testutils/cgroup.go
@@ -2,7 +2,6 @@ package testutils
 
 import (
 	"errors"
-	"io/ioutil"
 	"os"
 	"strings"
 	"sync"
@@ -19,7 +18,7 @@ var cgroup2 = struct {
 
 func cgroup2Path() (string, error) {
 	cgroup2.once.Do(func() {
-		mounts, err := ioutil.ReadFile("/proc/mounts")
+		mounts, err := os.ReadFile("/proc/mounts")
 		if err != nil {
 			cgroup2.err = err
 			return
@@ -53,7 +52,7 @@ func CreateCgroup(tb testing.TB) *os.File {
 		tb.Fatal("Can't locate cgroup2 mount:", err)
 	}
 
-	cgdir, err := ioutil.TempDir(cg2, "ebpf-link")
+	cgdir, err := os.MkdirTemp(cg2, "ebpf-link")
 	if err != nil {
 		tb.Fatal("Can't create cgroupv2:", err)
 	}
diff --git a/link/iter_test.go b/link/iter_test.go
index 8be8fe2..04615eb 100644
--- a/link/iter_test.go
+++ b/link/iter_test.go
@@ -1,7 +1,7 @@
 package link
 
 import (
-	"io/ioutil"
+	"io"
 	"testing"
 
 	"github.com/cilium/ebpf"
@@ -39,7 +39,7 @@ func TestIter(t *testing.T) {
 	}
 	defer file.Close()
 
-	contents, err := ioutil.ReadAll(file)
+	contents, err := io.ReadAll(file)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -99,7 +99,7 @@ func TestIterMapElements(t *testing.T) {
 	}
 	defer file.Close()
 
-	contents, err := ioutil.ReadAll(file)
+	contents, err := io.ReadAll(file)
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/link/link_test.go b/link/link_test.go
index 48b0e00..f09530a 100644
--- a/link/link_test.go
+++ b/link/link_test.go
@@ -2,7 +2,6 @@ package link
 
 import (
 	"errors"
-	"io/ioutil"
 	"math"
 	"os"
 	"path/filepath"
@@ -124,7 +123,7 @@ type testLinkOptions struct {
 func testLink(t *testing.T, link Link, opts testLinkOptions) {
 	t.Helper()
 
-	tmp, err := ioutil.TempDir("/sys/fs/bpf", "ebpf-test")
+	tmp, err := os.MkdirTemp("/sys/fs/bpf", "ebpf-test")
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/link/uprobe_test.go b/link/uprobe_test.go
index 8159bc3..cf0ea07 100644
--- a/link/uprobe_test.go
+++ b/link/uprobe_test.go
@@ -349,6 +349,12 @@ func TestUprobeProgramCall(t *testing.T) {
 			// Open Uprobe on the executable for the given symbol
 			// and attach it to the ebpf program created above.
 			u, err := ex.Uprobe(tt.sym, p, nil)
+			if errors.Is(err, ErrNoSymbol) {
+				// Assume bash::main and go::main.main always exists
+				// and skip the test if the symbol can't be found as
+				// certain OS (eg. Debian) strip binaries.
+				t.Skipf("executable %s appear to be stripped, skipping", tt.elf)
+			}
 			if err != nil {
 				t.Fatal(err)
 			}
diff --git a/map_test.go b/map_test.go
index 069e759..f011363 100644
--- a/map_test.go
+++ b/map_test.go
@@ -3,7 +3,6 @@ package ebpf
 import (
 	"errors"
 	"fmt"
-	"io/ioutil"
 	"math"
 	"os"
 	"path/filepath"
@@ -344,7 +343,7 @@ func TestNestedMapPin(t *testing.T) {
 	}
 	defer m.Close()
 
-	tmp, err := ioutil.TempDir("/sys/fs/bpf", "ebpf-test")
+	tmp, err := os.MkdirTemp("/sys/fs/bpf", "ebpf-test")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -1498,6 +1497,43 @@ func BenchmarkMarshalling(b *testing.B) {
 	})
 }
 
+func BenchmarkPerCPUMarshalling(b *testing.B) {
+	newMap := func(valueSize uint32) *Map {
+		m, err := NewMap(&MapSpec{
+			Type:       PerCPUHash,
+			KeySize:    8,
+			ValueSize:  valueSize,
+			MaxEntries: 1,
+		})
+		if err != nil {
+			b.Fatal(err)
+		}
+		return m
+	}
+
+	key := uint64(1)
+	val := []uint64{1, 2, 3, 4, 5, 6, 7, 8}
+
+	m := newMap(8)
+	if err := m.Put(key, val[0:]); err != nil {
+		b.Fatal(err)
+	}
+
+	b.Run("reflection", func(b *testing.B) {
+		b.ReportAllocs()
+		b.ResetTimer()
+
+		var value []uint64
+
+		for i := 0; i < b.N; i++ {
+			err := m.Lookup(unsafe.Pointer(&key), &value)
+			if err != nil {
+				b.Fatal("Can't get key:", err)
+			}
+		}
+	})
+}
+
 func BenchmarkMap(b *testing.B) {
 	m, err := NewMap(&MapSpec{
 		Type:       Hash,