--- /dev/null
+// 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.
+
+package http
+
+// A mapping is a collection of key-value pairs where the keys are unique.
+// A zero mapping is empty and ready to use.
+// A mapping tries to pick a representation that makes [mapping.find] most efficient.
+type mapping[K comparable, V any] struct {
+ s []entry[K, V] // for few pairs
+ m map[K]V // for many pairs
+}
+
+type entry[K comparable, V any] struct {
+ key K
+ value V
+}
+
+// maxSlice is the maximum number of pairs for which a slice is used.
+// It is a variable for benchmarking.
+var maxSlice int = 8
+
+// add adds a key-value pair to the mapping.
+func (h *mapping[K, V]) add(k K, v V) {
+ if h.m == nil && len(h.s) < maxSlice {
+ h.s = append(h.s, entry[K, V]{k, v})
+ } else {
+ if h.m == nil {
+ h.m = map[K]V{}
+ for _, e := range h.s {
+ h.m[e.key] = e.value
+ }
+ h.s = nil
+ }
+ h.m[k] = v
+ }
+}
+
+// find returns the value corresponding to the given key.
+// The second return value is false if there is no value
+// with that key.
+func (h *mapping[K, V]) find(k K) (v V, found bool) {
+ if h == nil {
+ return v, false
+ }
+ if h.m != nil {
+ v, found = h.m[k]
+ return v, found
+ }
+ for _, e := range h.s {
+ if e.key == k {
+ return e.value, true
+ }
+ }
+ return v, false
+}
+
+// eachPair calls f for each pair in the mapping.
+// If f returns false, pairs returns immediately.
+func (h *mapping[K, V]) eachPair(f func(k K, v V) bool) {
+ if h == nil {
+ return
+ }
+ if h.m != nil {
+ for k, v := range h.m {
+ if !f(k, v) {
+ return
+ }
+ }
+ } else {
+ for _, e := range h.s {
+ if !f(e.key, e.value) {
+ return
+ }
+ }
+ }
+}
--- /dev/null
+// 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.
+
+package http
+
+import (
+ "cmp"
+ "fmt"
+ "slices"
+ "strconv"
+ "testing"
+)
+
+func TestMapping(t *testing.T) {
+ var m mapping[int, string]
+ for i := 0; i < maxSlice; i++ {
+ m.add(i, strconv.Itoa(i))
+ }
+ if m.m != nil {
+ t.Fatal("m.m != nil")
+ }
+ for i := 0; i < maxSlice; i++ {
+ g, _ := m.find(i)
+ w := strconv.Itoa(i)
+ if g != w {
+ t.Fatalf("%d: got %s, want %s", i, g, w)
+ }
+ }
+ m.add(4, "4")
+ if m.s != nil {
+ t.Fatal("m.s != nil")
+ }
+ if m.m == nil {
+ t.Fatal("m.m == nil")
+ }
+ g, _ := m.find(4)
+ if w := "4"; g != w {
+ t.Fatalf("got %s, want %s", g, w)
+ }
+}
+
+func TestMappingEachPair(t *testing.T) {
+ var m mapping[int, string]
+ var want []entry[int, string]
+ for i := 0; i < maxSlice*2; i++ {
+ v := strconv.Itoa(i)
+ m.add(i, v)
+ want = append(want, entry[int, string]{i, v})
+
+ }
+
+ var got []entry[int, string]
+ m.eachPair(func(k int, v string) bool {
+ got = append(got, entry[int, string]{k, v})
+ return true
+ })
+ slices.SortFunc(got, func(e1, e2 entry[int, string]) int {
+ return cmp.Compare(e1.key, e2.key)
+ })
+ if !slices.Equal(got, want) {
+ t.Errorf("got %v, want %v", got, want)
+ }
+}
+
+func BenchmarkFindChild(b *testing.B) {
+ key := "articles"
+ children := []string{
+ "*",
+ "cmd.html",
+ "code.html",
+ "contrib.html",
+ "contribute.html",
+ "debugging_with_gdb.html",
+ "docs.html",
+ "effective_go.html",
+ "files.log",
+ "gccgo_contribute.html",
+ "gccgo_install.html",
+ "go-logo-black.png",
+ "go-logo-blue.png",
+ "go-logo-white.png",
+ "go1.1.html",
+ "go1.2.html",
+ "go1.html",
+ "go1compat.html",
+ "go_faq.html",
+ "go_mem.html",
+ "go_spec.html",
+ "help.html",
+ "ie.css",
+ "install-source.html",
+ "install.html",
+ "logo-153x55.png",
+ "Makefile",
+ "root.html",
+ "share.png",
+ "sieve.gif",
+ "tos.html",
+ "articles",
+ }
+ if len(children) != 32 {
+ panic("bad len")
+ }
+ for _, n := range []int{2, 4, 8, 16, 32} {
+ list := children[:n]
+ b.Run(fmt.Sprintf("n=%d", n), func(b *testing.B) {
+
+ b.Run("rep=linear", func(b *testing.B) {
+ var entries []entry[string, any]
+ for _, c := range list {
+ entries = append(entries, entry[string, any]{c, nil})
+ }
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ findChildLinear(key, entries)
+ }
+ })
+ b.Run("rep=map", func(b *testing.B) {
+ m := map[string]any{}
+ for _, c := range list {
+ m[c] = nil
+ }
+ var x any
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ x = m[key]
+ }
+ _ = x
+ })
+ b.Run(fmt.Sprintf("rep=hybrid%d", maxSlice), func(b *testing.B) {
+ var h mapping[string, any]
+ for _, c := range list {
+ h.add(c, nil)
+ }
+ var x any
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ x, _ = h.find(key)
+ }
+ _ = x
+ })
+ })
+ }
+}
+
+func findChildLinear(key string, entries []entry[string, any]) any {
+ for _, e := range entries {
+ if key == e.key {
+ return e.value
+ }
+ }
+ return nil
+}