Skip to content

Commit 2fda12d

Browse files
authored
perf: MakeDefaultConfig once (#5082)
* perf: MakeDefaultConfig once This shaves of another 2 seconds (62%) of the remaining execution time for a kustomization tree with 4000 documents, reducing the execution time from 4.79s to 1.82s 0 0% 1.38% 2.98s 37.25% sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig.MakeDefaultConfig before: (pprof) top30 -cum Showing nodes accounting for 1.82s, 22.75% of 8s total Dropped 408 nodes (cum <= 0.04s) Showing top 30 nodes out of 308 flat flat% sum% cum cum% 0 0% 0% 4.79s 59.88% github.com/spf13/cobra.(*Command).Execute 0 0% 0% 4.79s 59.88% github.com/spf13/cobra.(*Command).ExecuteC 0 0% 0% 4.79s 59.88% github.com/spf13/cobra.(*Command).execute 0 0% 0% 4.79s 59.88% main.main 0 0% 0% 4.79s 59.88% runtime.main 0 0% 0% 4.79s 59.88% sigs.k8s.io/kustomize/kustomize/v5/commands/build.NewCmdBuild.func1 0 0% 0% 4.22s 52.75% sigs.k8s.io/kustomize/api/krusty.(*Kustomizer).Run 0 0% 0% 4.18s 52.25% sigs.k8s.io/kustomize/api/internal/target.(*KustTarget).MakeCustomizedResMap (inline) 0 0% 0% 4.18s 52.25% sigs.k8s.io/kustomize/api/internal/target.(*KustTarget).makeCustomizedResMap 0 0% 0% 4.06s 50.75% sigs.k8s.io/kustomize/api/internal/target.(*KustTarget).AccumulateTarget 0 0% 0% 4.06s 50.75% sigs.k8s.io/kustomize/api/internal/target.(*KustTarget).accumulateResources 0 0% 0% 4.06s 50.75% sigs.k8s.io/kustomize/api/internal/target.(*KustTarget).accumulateTarget 0 0% 0% 4.05s 50.62% sigs.k8s.io/kustomize/api/internal/target.(*KustTarget).accumulateDirectory 0 0% 0% 3.22s 40.25% runtime.systemstack 0 0% 0% 3.03s 37.88% runtime.gcBgMarkWorker 0 0% 0% 3.03s 37.88% runtime.gcBgMarkWorker.func2 0.11s 1.38% 1.38% 3.03s 37.88% runtime.gcDrain 0 0% 1.38% 2.98s 37.25% sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig.MakeDefaultConfig 0 0% 1.38% 2.98s 37.25% sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig.MakeTransformerConfig 0 0% 1.38% 2.98s 37.25% sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig.makeTransformerConfigFromBytes 0 0% 1.38% 2.97s 37.12% sigs.k8s.io/yaml.yamlUnmarshal 0 0% 1.38% 2.91s 36.38% sigs.k8s.io/yaml.Unmarshal (inline) 1.34s 16.75% 18.12% 2.87s 35.88% runtime.scanobject 0 0% 18.12% 2.53s 31.62% sigs.k8s.io/yaml.yamlToJSON 0 0% 18.12% 1.91s 23.88% gopkg.in/yaml%2ev2.unmarshal 0 0% 18.12% 1.86s 23.25% gopkg.in/yaml%2ev2.Unmarshal (partial-inline) 0 0% 18.12% 1.43s 17.88% gopkg.in/yaml%2ev2.(*parser).parse 0.01s 0.12% 18.25% 1.27s 15.88% gopkg.in/yaml%2ev2.(*parser).document 0.01s 0.12% 18.38% 1.25s 15.62% gopkg.in/yaml%2ev2.(*parser).mapping 0.35s 4.38% 22.75% 1.21s 15.12% runtime.mallocgc after: (pprof) top30 -cum Showing nodes accounting for 0.84s, 24.42% of 3.44s total Dropped 225 nodes (cum <= 0.02s) Showing top 30 nodes out of 345 flat flat% sum% cum cum% 0 0% 0% 1.82s 52.91% github.com/spf13/cobra.(*Command).Execute 0 0% 0% 1.82s 52.91% github.com/spf13/cobra.(*Command).ExecuteC 0 0% 0% 1.82s 52.91% github.com/spf13/cobra.(*Command).execute 0 0% 0% 1.82s 52.91% main.main 0 0% 0% 1.82s 52.91% runtime.main 0 0% 0% 1.82s 52.91% sigs.k8s.io/kustomize/kustomize/v5/commands/build.NewCmdBuild.func1 0 0% 0% 1.57s 45.64% runtime.systemstack 0 0% 0% 1.49s 43.31% runtime.gcBgMarkWorker 0 0% 0% 1.49s 43.31% runtime.gcBgMarkWorker.func2 0.03s 0.87% 0.87% 1.49s 43.31% runtime.gcDrain 0.62s 18.02% 18.90% 1.45s 42.15% runtime.scanobject 0 0% 18.90% 1.25s 36.34% sigs.k8s.io/kustomize/api/krusty.(*Kustomizer).Run 0 0% 18.90% 1.21s 35.17% sigs.k8s.io/kustomize/api/internal/target.(*KustTarget).MakeCustomizedResMap (inline) 0 0% 18.90% 1.21s 35.17% sigs.k8s.io/kustomize/api/internal/target.(*KustTarget).makeCustomizedResMap 0 0% 18.90% 1.08s 31.40% sigs.k8s.io/kustomize/api/internal/target.(*KustTarget).AccumulateTarget 0 0% 18.90% 1.08s 31.40% sigs.k8s.io/kustomize/api/internal/target.(*KustTarget).accumulateResources 0 0% 18.90% 1.08s 31.40% sigs.k8s.io/kustomize/api/internal/target.(*KustTarget).accumulateTarget 0 0% 18.90% 1.07s 31.10% sigs.k8s.io/kustomize/api/internal/target.(*KustTarget).accumulateDirectory 0 0% 18.90% 0.57s 16.57% sigs.k8s.io/kustomize/api/resmap.(*resWrangler).AsYaml 0 0% 18.90% 0.57s 16.57% sigs.k8s.io/kustomize/api/resource.(*Resource).AsYAML 0.11s 3.20% 22.09% 0.48s 13.95% runtime.mallocgc 0 0% 22.09% 0.45s 13.08% sigs.k8s.io/kustomize/api/internal/target.(*KustTarget).accumulateFile 0 0% 22.09% 0.45s 13.08% sigs.k8s.io/kustomize/api/resmap.(*Factory).FromFile 0 0% 22.09% 0.33s 9.59% sigs.k8s.io/kustomize/kyaml/yaml.(*RNode).MarshalJSON 0.08s 2.33% 24.42% 0.32s 9.30% runtime.greyobject 0 0% 24.42% 0.30s 8.72% sigs.k8s.io/kustomize/api/resmap.(*Factory).NewResMapFromBytes 0 0% 24.42% 0.27s 7.85% sigs.k8s.io/yaml.JSONToYAML 0 0% 24.42% 0.25s 7.27% sigs.k8s.io/kustomize/api/internal/target.(*KustTarget).runTransformers 0 0% 24.42% 0.25s 7.27% sigs.k8s.io/kustomize/api/resource.(*Factory).RNodesFromBytes 0 0% 24.42% 0.25s 7.27% sigs.k8s.io/kustomize/api/resource.(*Factory).SliceFromBytes * Tests and comments for MakeDefaultConfig perf work Document updated code in MakeDefaultConfig. Add unit tests to ensure DeepCopy works. Add hints to other code to ensure DeepCopy is kept up to date.
1 parent 5b51722 commit 2fda12d

File tree

6 files changed

+128
-6
lines changed

6 files changed

+128
-6
lines changed

api/internal/plugins/builtinconfig/namebackreferences.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ type NameBackReferences struct {
4747
// TODO: rename json 'fieldSpecs' to 'referrers' for clarity.
4848
// This will, however, break anyone using a custom config.
4949
Referrers types.FsSlice `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
50+
51+
// Note: If any new pointer based members are added, DeepCopy needs to be updated
5052
}
5153

5254
func (n NameBackReferences) String() string {
@@ -66,6 +68,17 @@ func (s nbrSlice) Less(i, j int) bool {
6668
return s[i].Gvk.IsLessThan(s[j].Gvk)
6769
}
6870

71+
// DeepCopy returns a new copy of nbrSlice
72+
func (s nbrSlice) DeepCopy() nbrSlice {
73+
ret := make(nbrSlice, len(s))
74+
copy(ret, s)
75+
for i, slice := range ret {
76+
ret[i].Referrers = slice.Referrers.DeepCopy()
77+
}
78+
79+
return ret
80+
}
81+
6982
func (s nbrSlice) mergeAll(o nbrSlice) (result nbrSlice, err error) {
7083
result = s
7184
for _, r := range o {

api/internal/plugins/builtinconfig/namebackreferences_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,29 @@ func TestMergeAll(t *testing.T) {
9595
t.Fatalf("expected\n %v\n but got\n %v\n", expected, actual)
9696
}
9797
}
98+
99+
func TestNbrSlice_DeepCopy(t *testing.T) {
100+
original := make(nbrSlice, 2, 4)
101+
original[0] = NameBackReferences{Gvk: resid.FromKind("A"), Referrers: types.FsSlice{{Path: "a"}}}
102+
original[1] = NameBackReferences{Gvk: resid.FromKind("B"), Referrers: types.FsSlice{{Path: "b"}}}
103+
104+
copied := original.DeepCopy()
105+
106+
original, _ = original.mergeOne(NameBackReferences{Gvk: resid.FromKind("C"), Referrers: types.FsSlice{{Path: "c"}}})
107+
108+
// perform mutations which should not affect original
109+
copied.Swap(0, 1)
110+
copied[0].Referrers[0].Path = "very b" // ensure Referrers are not shared
111+
_, _ = copied.mergeOne(NameBackReferences{Gvk: resid.FromKind("D"), Referrers: types.FsSlice{{Path: "d"}}})
112+
113+
// if DeepCopy does not work, original would be {very b,a,d} instead of {a,b,c}
114+
expected := nbrSlice{
115+
{Gvk: resid.FromKind("A"), Referrers: types.FsSlice{{Path: "a"}}},
116+
{Gvk: resid.FromKind("B"), Referrers: types.FsSlice{{Path: "b"}}},
117+
{Gvk: resid.FromKind("C"), Referrers: types.FsSlice{{Path: "c"}}},
118+
}
119+
120+
if !reflect.DeepEqual(original, expected) {
121+
t.Fatalf("original affected by mutations to copied object:\ngot\t%+v,\nexpected: %+v", original, expected)
122+
}
123+
}

api/internal/plugins/builtinconfig/transformerconfig.go

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package builtinconfig
66
import (
77
"log"
88
"sort"
9+
"sync"
910

1011
"sigs.k8s.io/kustomize/api/ifc"
1112
"sigs.k8s.io/kustomize/api/internal/konfig/builtinpluginconsts"
@@ -15,6 +16,7 @@ import (
1516

1617
// TransformerConfig holds the data needed to perform transformations.
1718
type TransformerConfig struct {
19+
// if any fields are added, update the DeepCopy implementation
1820
NamePrefix types.FsSlice `json:"namePrefix,omitempty" yaml:"namePrefix,omitempty"`
1921
NameSuffix types.FsSlice `json:"nameSuffix,omitempty" yaml:"nameSuffix,omitempty"`
2022
NameSpace types.FsSlice `json:"namespace,omitempty" yaml:"namespace,omitempty"`
@@ -32,14 +34,43 @@ func MakeEmptyConfig() *TransformerConfig {
3234
return &TransformerConfig{}
3335
}
3436

37+
// DeepCopy returns a new copy of TransformerConfig
38+
func (t *TransformerConfig) DeepCopy() *TransformerConfig {
39+
return &TransformerConfig{
40+
NamePrefix: t.NamePrefix.DeepCopy(),
41+
NameSuffix: t.NameSuffix.DeepCopy(),
42+
NameSpace: t.NameSpace.DeepCopy(),
43+
CommonLabels: t.CommonLabels.DeepCopy(),
44+
TemplateLabels: t.TemplateLabels.DeepCopy(),
45+
CommonAnnotations: t.CommonAnnotations.DeepCopy(),
46+
NameReference: t.NameReference.DeepCopy(),
47+
VarReference: t.VarReference.DeepCopy(),
48+
Images: t.Images.DeepCopy(),
49+
Replicas: t.Replicas.DeepCopy(),
50+
}
51+
}
52+
53+
// the default transformer config is initialized by MakeDefaultConfig,
54+
// and must only be accessed via that function.
55+
var (
56+
initDefaultConfig sync.Once //nolint:gochecknoglobals
57+
defaultConfig *TransformerConfig //nolint:gochecknoglobals
58+
)
59+
3560
// MakeDefaultConfig returns a default TransformerConfig.
3661
func MakeDefaultConfig() *TransformerConfig {
37-
c, err := makeTransformerConfigFromBytes(
38-
builtinpluginconsts.GetDefaultFieldSpecs())
39-
if err != nil {
40-
log.Fatalf("Unable to make default transformconfig: %v", err)
41-
}
42-
return c
62+
// parsing is expensive when having a large tree with many kustomization modules, so only do it once
63+
initDefaultConfig.Do(func() {
64+
var err error
65+
defaultConfig, err = makeTransformerConfigFromBytes(
66+
builtinpluginconsts.GetDefaultFieldSpecs())
67+
if err != nil {
68+
log.Fatalf("Unable to make default transformconfig: %v", err)
69+
}
70+
})
71+
72+
// return a copy to avoid any mutations to protect the reference copy
73+
return defaultConfig.DeepCopy()
4374
}
4475

4576
// MakeTransformerConfig returns a merger of custom config,

api/internal/plugins/builtinconfig/transformerconfig_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,3 +173,22 @@ func TestMerge(t *testing.T) {
173173
t.Fatalf("expected: %v\n but got: %v\n", cfga, actual)
174174
}
175175
}
176+
177+
func TestMakeDefaultConfig_mutation(t *testing.T) {
178+
a := MakeDefaultConfig()
179+
180+
// mutate
181+
a.NameReference[0].Kind = "mutated"
182+
a.NameReference = a.NameReference[:1]
183+
184+
clean := MakeDefaultConfig()
185+
if clean.NameReference[0].Kind == "mutated" {
186+
t.Errorf("MakeDefaultConfig() did not return a clean copy: %+v", clean.NameReference)
187+
}
188+
}
189+
190+
func BenchmarkMakeDefaultConfig(b *testing.B) {
191+
for i := 0; i < b.N; i++ {
192+
_ = MakeDefaultConfig()
193+
}
194+
}

api/types/fieldspec.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ type FieldSpec struct {
3030
resid.Gvk `json:",inline,omitempty" yaml:",inline,omitempty"`
3131
Path string `json:"path,omitempty" yaml:"path,omitempty"`
3232
CreateIfNotPresent bool `json:"create,omitempty" yaml:"create,omitempty"`
33+
34+
// Note: If any new pointer based members are added, FsSlice.DeepCopy needs to be updated
3335
}
3436

3537
func (fs FieldSpec) String() string {
@@ -50,6 +52,13 @@ func (s FsSlice) Less(i, j int) bool {
5052
return s[i].Gvk.IsLessThan(s[j].Gvk)
5153
}
5254

55+
// DeepCopy returns a new copy of FsSlice
56+
func (s FsSlice) DeepCopy() FsSlice {
57+
ret := make(FsSlice, len(s))
58+
copy(ret, s)
59+
return ret
60+
}
61+
5362
// MergeAll merges the argument into this, returning the result.
5463
// Items already present are ignored.
5564
// Items that conflict (primary key matches, but remain data differs)

api/types/fieldspec_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,3 +142,27 @@ func TestFsSlice_MergeAll(t *testing.T) {
142142
}
143143
}
144144
}
145+
146+
func TestFsSlice_DeepCopy(t *testing.T) {
147+
original := make(FsSlice, 2, 4)
148+
original[0] = FieldSpec{Path: "a"}
149+
original[1] = FieldSpec{Path: "b"}
150+
151+
copied := original.DeepCopy()
152+
153+
original, _ = original.MergeOne(FieldSpec{Path: "c"})
154+
155+
// perform mutations which should not affect original
156+
copied.Swap(0, 1)
157+
_, _ = copied.MergeOne(FieldSpec{Path: "d"})
158+
159+
// if DeepCopy does not work, original would be {b,a,d} instead of {a,b,c}
160+
expected := FsSlice{
161+
{Path: "a"},
162+
{Path: "b"},
163+
{Path: "c"},
164+
}
165+
if !reflect.DeepEqual(original, expected) {
166+
t.Fatalf("original affected by mutations to copied object:\ngot\t%+v,\nexpected: %+v", original, expected)
167+
}
168+
}

0 commit comments

Comments
 (0)