Selaa lähdekoodia

Merge pull request #40856 from cpuguy83/reduce_allocs_on_env_repalce

Use strings.Index instead of strings.Split
Brian Goff 5 vuotta sitten
vanhempi
commit
4b03f520d3
3 muutettua tiedostoa jossa 69 lisäystä ja 12 poistoa
  1. 13 5
      container/container.go
  2. 6 6
      container/env.go
  3. 50 1
      container/env_test.go

+ 13 - 5
container/container.go

@@ -723,12 +723,20 @@ func (container *Container) CreateDaemonEnvironment(tty bool, linkedEnv []string
 	if os == "" {
 		os = runtime.GOOS
 	}
-	env := []string{}
+
+	// Figure out what size slice we need so we can allocate this all at once.
+	envSize := len(container.Config.Env)
 	if runtime.GOOS != "windows" || (runtime.GOOS == "windows" && os == "linux") {
-		env = []string{
-			"PATH=" + system.DefaultPathEnv(os),
-			"HOSTNAME=" + container.Config.Hostname,
-		}
+		envSize += 2 + len(linkedEnv)
+	}
+	if tty {
+		envSize++
+	}
+
+	env := make([]string, 0, envSize)
+	if runtime.GOOS != "windows" || (runtime.GOOS == "windows" && os == "linux") {
+		env = append(env, "PATH="+system.DefaultPathEnv(os))
+		env = append(env, "HOSTNAME="+container.Config.Hostname)
 		if tty {
 			env = append(env, "TERM=xterm")
 		}

+ 6 - 6
container/env.go

@@ -9,22 +9,22 @@ import (
 func ReplaceOrAppendEnvValues(defaults, overrides []string) []string {
 	cache := make(map[string]int, len(defaults))
 	for i, e := range defaults {
-		parts := strings.SplitN(e, "=", 2)
-		cache[parts[0]] = i
+		index := strings.Index(e, "=")
+		cache[e[:index]] = i
 	}
 
 	for _, value := range overrides {
 		// Values w/o = means they want this env to be removed/unset.
-		if !strings.Contains(value, "=") {
+		index := strings.Index(value, "=")
+		if index < 0 {
+			// no "=" in value
 			if i, exists := cache[value]; exists {
 				defaults[i] = "" // Used to indicate it should be removed
 			}
 			continue
 		}
 
-		// Just do a normal set/update
-		parts := strings.SplitN(value, "=", 2)
-		if i, exists := cache[parts[0]]; exists {
+		if i, exists := cache[value[:index]]; exists {
 			defaults[i] = value
 		} else {
 			defaults = append(defaults, value)

+ 50 - 1
container/env_test.go

@@ -1,6 +1,11 @@
 package container // import "github.com/docker/docker/container"
 
-import "testing"
+import (
+	"crypto/rand"
+	"testing"
+
+	"gotest.tools/v3/assert"
+)
 
 func TestReplaceAndAppendEnvVars(t *testing.T) {
 	var (
@@ -22,3 +27,47 @@ func TestReplaceAndAppendEnvVars(t *testing.T) {
 		t.Fatalf("expected TERM=xterm got '%s'", env[1])
 	}
 }
+
+func BenchmarkReplaceOrAppendEnvValues(b *testing.B) {
+	b.Run("0", func(b *testing.B) {
+		benchmarkReplaceOrAppendEnvValues(b, 0)
+	})
+	b.Run("100", func(b *testing.B) {
+		benchmarkReplaceOrAppendEnvValues(b, 100)
+	})
+	b.Run("1000", func(b *testing.B) {
+		benchmarkReplaceOrAppendEnvValues(b, 1000)
+	})
+	b.Run("10000", func(b *testing.B) {
+		benchmarkReplaceOrAppendEnvValues(b, 10000)
+	})
+}
+
+func benchmarkReplaceOrAppendEnvValues(b *testing.B, extraEnv int) {
+	b.StopTimer()
+	// remove FOO from env
+	// remove BAR from env (nop)
+	o := []string{"HOME=/root", "TERM=xterm", "FOO", "BAR"}
+
+	if extraEnv > 0 {
+		buf := make([]byte, 5)
+		for i := 0; i < extraEnv; i++ {
+			n, err := rand.Read(buf)
+			assert.NilError(b, err)
+			key := string(buf[:n])
+
+			n, err = rand.Read(buf)
+			assert.NilError(b, err)
+			val := string(buf[:n])
+
+			o = append(o, key+"="+val)
+		}
+	}
+	d := make([]string, 0, len(o)+2)
+	d = append(d, []string{"HOME=/", "FOO=foo_default"}...)
+
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		_ = ReplaceOrAppendEnvValues(d, o)
+	}
+}