Skip to content

Commit f1d9962

Browse files
author
Kubernetes Submit Queue
authored
Merge pull request kubernetes#53720 from shyamjvs/test-kubemark
Automatic merge from submit-queue. If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. Optimize random string generator to avoid multiple locks & use bit-masking Ref kubernetes#53327 We recently started seeing a 50% decrease in scheduling throughput (for e.g in kubemark-500 scale job) and turns out kubernetes#53135 introduced it. The reason is [this call](https://github.com/jsafrane/kubernetes/blob/2caae38d323720a96be34606589f41488ba82b87/plugin/pkg/scheduler/algorithm/predicates/predicates.go#L272) to create a random 32-length string. From the code of the `rand` utility (which is being heavily used throughout the system for randomizing object names), I noticed following performance issues: - to create an n-length string, we are making n calls to `rand.Intn()` each of which does a lock+unlock operation on the RNG.. while just 1 lock+unlock operation is enough for all - we're choosing one character (from an alphabet of 27 chars) per each random integer.. while we can select 10 characters using a single int63 (by masking and bit-shifting) as 1 character uses just 5 bits of randomness - the character set is defined as a global slice (mutable), so the compiler needs to fetch length of the slice on each invocation to `len()` (we're making n of those).. while we can just use a const string (immutable) which will make len directly available as a cached constant (yes, go does it!) This PR is making the above fixes. I'll try to add some benchmarking to measure the difference (as @wojtek-t suggested). /cc @kubernetes/sig-scalability-misc @kubernetes/sig-scheduling-bugs @kubernetes/sig-api-machinery-misc @wojtek-t @smarterclayton
2 parents cc49b34 + 0185e55 commit f1d9962

File tree

3 files changed

+54
-16
lines changed

3 files changed

+54
-16
lines changed

pkg/controller/serviceaccount/tokens_controller_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ func tokenSecretReferences() []v1.ObjectReference {
7373

7474
// addTokenSecretReference adds a reference to the ServiceAccountToken that will be created
7575
func addTokenSecretReference(refs []v1.ObjectReference) []v1.ObjectReference {
76-
return addNamedTokenSecretReference(refs, "default-token-stdpg")
76+
return addNamedTokenSecretReference(refs, "default-token-xn8fg")
7777
}
7878

7979
// addNamedTokenSecretReference adds a reference to the named ServiceAccountToken
@@ -118,9 +118,9 @@ func opaqueSecret() *v1.Secret {
118118
}
119119

120120
// createdTokenSecret returns the ServiceAccountToken secret posted when creating a new token secret.
121-
// Named "default-token-stdpg", since that is the first generated name after rand.Seed(1)
121+
// Named "default-token-xn8fg", since that is the first generated name after rand.Seed(1)
122122
func createdTokenSecret(overrideName ...string) *v1.Secret {
123-
return namedCreatedTokenSecret("default-token-stdpg")
123+
return namedCreatedTokenSecret("default-token-xn8fg")
124124
}
125125

126126
// namedTokenSecret returns the ServiceAccountToken secret posted when creating a new token secret with the given name.
@@ -264,12 +264,12 @@ func TestTokenCreation(t *testing.T) {
264264

265265
// Attempt 2
266266
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"),
267-
core.NewCreateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, namedCreatedTokenSecret("default-token-jk9rt")),
267+
core.NewCreateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, namedCreatedTokenSecret("default-token-txhzt")),
268268

269269
// Attempt 3
270270
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"),
271-
core.NewCreateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, namedCreatedTokenSecret("default-token-684pg")),
272-
core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, serviceAccount(addNamedTokenSecretReference(emptySecretReferences(), "default-token-684pg"))),
271+
core.NewCreateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, namedCreatedTokenSecret("default-token-vnmz7")),
272+
core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, serviceAccount(addNamedTokenSecretReference(emptySecretReferences(), "default-token-vnmz7"))),
273273
},
274274
},
275275
"new serviceaccount with no secrets encountering unending create error": {
@@ -293,10 +293,10 @@ func TestTokenCreation(t *testing.T) {
293293
core.NewCreateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, createdTokenSecret()),
294294
// Retry 1
295295
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"),
296-
core.NewCreateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, namedCreatedTokenSecret("default-token-jk9rt")),
296+
core.NewCreateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, namedCreatedTokenSecret("default-token-txhzt")),
297297
// Retry 2
298298
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"),
299-
core.NewCreateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, namedCreatedTokenSecret("default-token-684pg")),
299+
core.NewCreateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, namedCreatedTokenSecret("default-token-vnmz7")),
300300
},
301301
},
302302
"new serviceaccount with missing secrets": {

staging/src/k8s.io/apimachinery/pkg/util/rand/rand.go

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -70,24 +70,49 @@ func Perm(n int) []int {
7070
return rng.rand.Perm(n)
7171
}
7272

73-
// We omit vowels from the set of available characters to reduce the chances
74-
// of "bad words" being formed.
75-
var alphanums = []rune("bcdfghjklmnpqrstvwxz2456789")
73+
const (
74+
// We omit vowels from the set of available characters to reduce the chances
75+
// of "bad words" being formed.
76+
alphanums = "bcdfghjklmnpqrstvwxz2456789"
77+
// No. of bits required to index into alphanums string.
78+
alphanumsIdxBits = 5
79+
// Mask used to extract last alphanumsIdxBits of an int.
80+
alphanumsIdxMask = 1<<alphanumsIdxBits - 1
81+
// No. of random letters we can extract from a single int63.
82+
maxAlphanumsPerInt = 63 / alphanumsIdxBits
83+
)
7684

7785
// String generates a random alphanumeric string, without vowels, which is n
7886
// characters long. This will panic if n is less than zero.
79-
func String(length int) string {
80-
b := make([]rune, length)
81-
for i := range b {
82-
b[i] = alphanums[Intn(len(alphanums))]
87+
// How the random string is created:
88+
// - we generate random int63's
89+
// - from each int63, we are extracting multiple random letters by bit-shifting and masking
90+
// - if some index is out of range of alphanums we neglect it (unlikely to happen multiple times in a row)
91+
func String(n int) string {
92+
b := make([]byte, n)
93+
rng.Lock()
94+
defer rng.Unlock()
95+
96+
randomInt63 := rng.rand.Int63()
97+
remaining := maxAlphanumsPerInt
98+
for i := 0; i < n; {
99+
if remaining == 0 {
100+
randomInt63, remaining = rng.rand.Int63(), maxAlphanumsPerInt
101+
}
102+
if idx := int(randomInt63 & alphanumsIdxMask); idx < len(alphanums) {
103+
b[i] = alphanums[idx]
104+
i++
105+
}
106+
randomInt63 >>= alphanumsIdxBits
107+
remaining--
83108
}
84109
return string(b)
85110
}
86111

87112
// SafeEncodeString encodes s using the same characters as rand.String. This reduces the chances of bad words and
88113
// ensures that strings generated from hash functions appear consistent throughout the API.
89114
func SafeEncodeString(s string) string {
90-
r := make([]rune, len(s))
115+
r := make([]byte, len(s))
91116
for i, b := range []rune(s) {
92117
r[i] = alphanums[(int(b) % len(alphanums))]
93118
}

staging/src/k8s.io/apimachinery/pkg/util/rand/rand_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424

2525
const (
2626
maxRangeTestCount = 500
27+
testStringLength = 32
2728
)
2829

2930
func TestString(t *testing.T) {
@@ -99,3 +100,15 @@ func TestInt63nRange(t *testing.T) {
99100
}
100101
}
101102
}
103+
104+
func BenchmarkRandomStringGeneration(b *testing.B) {
105+
b.ResetTimer()
106+
var s string
107+
for i := 0; i < b.N; i++ {
108+
s = String(testStringLength)
109+
}
110+
b.StopTimer()
111+
if len(s) == 0 {
112+
b.Fatal(s)
113+
}
114+
}

0 commit comments

Comments
 (0)