Skip to content

cmd/compile: slices.BinarySearchFunc is not inlined, making it slower than sort.Search #81887

Closed

Description

@adonovan

The generic slices.BinarySearchFunc function added in Go 1.21 is significantly slower than sort.Search in some cases, such as the program below. The difference appears to be due to inlining of the Search call, permitting the calls to the lambda to be made static; by contrast BinarySearchFunc, which has a slightly more complex comparison expression (that appears twice), seems to exceed the inliner budget.

    goos: darwin
    goarch: arm64
    pkg: binsearch
    cpu: Apple M1 Pro
                         │ sort.Search  │      slices.BinarySearchFunc       │
                         │    sec/op    │   sec/op     vs base               │
    Search/C-8             18.56n ± 15%   24.57n ± 3%  +32.41% (p=0.002 n=6)
    Search/WriteString-8   20.30n ±  5%   25.48n ± 2%  +25.54% (p=0.002 n=6)
    Search/p.c-8           19.06n ±  1%   24.71n ± 1%  +29.64% (p=0.002 n=6)
    Search/q.grow-8        19.43n ± 21%   23.11n ± 1%        ~ (p=0.065 n=6)
    Search/missing-8       18.91n ±  1%   26.51n ± 3%  +40.19% (p=0.002 n=6)
    geomean                19.24n         24.85n       +29.15%
package binsearch

import (
	"fmt"
	"slices"
	"sort"
	"strings"
	"testing"
)

type entry struct {
	id  string
	val *int
}

var list = func() []entry {
	var list []entry
	for _, id := range []string{
		"A", "B", "C", "D", "Grow", "Len", "Read", "Reset",
		"String", "Write", "WriteString", "p.a", "p.b", "p.c", "p.d", "q.grow",
	} {
		list = append(list, entry{id, new(int)})
	}
	if !slices.IsSortedFunc(list, func(x, y entry) int { return strings.Compare(x.id, y.id) }) {
		panic("unsorted")
	}
	return list
}()

//go:noinline
func lookupSort(list []entry, key string) *int {
	i := sort.Search(len(list), func(i int) bool {
		return strings.Compare(list[i].id, key) >= 0
	})
	if i < len(list) && list[i].id == key {
		return list[i].val
	}
	return nil
}

//go:noinline
func lookupSlices(list []entry, key string) *int {
	i, found := slices.BinarySearchFunc(list, key, func(e entry, key string) int {
		return strings.Compare(e.id, key)
	})
	if found {
		return list[i].val
	}
	return nil
}

var keys = []string{"C", "WriteString", "p.c", "q.grow", "missing"}

func TestAgree(t *testing.T) {
	for _, e := range list {
		if lookupSort(list, e.id) != e.val || lookupSlices(list, e.id) != e.val {
			t.Errorf("lookup(%q) failed", e.id)
		}
	}
	if lookupSort(list, "missing") != nil || lookupSlices(list, "missing") != nil {
		t.Errorf("lookup(missing) succeeded")
	}
}

var sink *int

func BenchmarkSortSearch(b *testing.B) {
	for _, key := range keys {
		b.Run(fmt.Sprint(key), func(b *testing.B) {
			for b.Loop() {
				sink = lookupSort(list, key)
			}
		})
	}
}

func BenchmarkSlicesBinarySearchFunc(b *testing.B) {
	for _, key := range keys {
		b.Run(fmt.Sprint(key), func(b *testing.B) {
			for b.Loop() {
				sink = lookupSlices(list, key)
			}
		})
	}
}

Activity

  1. gopherbot commented on Oct 1, 2026

    @gopherbot
    Contributor

    Change https://go.dev/cl/842885 mentions this issue: slices: make BinarySearchFunc inlinable

  2. gopherbot commented on Oct 1, 2026

    @gopherbot
    Contributor

    Change https://go.dev/cl/842886 mentions this issue: slices: make BinarySearchFunc inlinable

  3. added a commit that references this issue on Oct 5, 2026
    0adda8a
  4. added
    FixPendingIssues that have a fix which has not yet been reviewed or submitted.
    on Oct 5, 2026
  5. added this to the Go1.28 milestone on Oct 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    FixPendingIssues that have a fix which has not yet been reviewed or submitted.Performancecompiler/runtimeIssues related to the Go compiler and/or runtime.

    Type

    No type

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions