Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor mapping node traversal and optimize RNode.GetAnnotations and RNode.GetLabels #4944

Merged

Conversation

ephesused
Copy link
Contributor

This PR has two purposes.

First, it refactors mapping node traversal so that all code paths execute through the same function.

Second, it optimizes RNode's GetAnnotations and GetLabels in three ways:

  1. For heavily used functions, allocate memory to avoid overhead associated with map and array re-sizing.
  2. Where appropriate, limit annotation and label retrievals to only the desired keys.
  3. Adjust annotation and label retrieval to avoid unnecessary temporary object creation.

The execution time and memory use improvements over master can be significant. The results shown here are for current master and for the refactored and optimized code. One test case (ds) is a moderately sized and has a somewhat simple set of kustomizations. One (na) is very large and has a complex set of kustomizations. One (ro) is a large set of resources, with no kustomizations - it's just a resource load. kustomize builds using master match exactly with the builds using the PR.

Using go's profiling tools, here's how things look with the current master (a1bfab3) and with this PR (20b0d3c):

CPU
ds-cpu-mmasterr-a1bfab382.svg: Duration: 12.22s, Total samples = 13.41s (109.72%)
ds-cpu-refactor-20b0d3c7c.svg: Duration: 9.53s, Total samples = 9.12s (95.74%)
na-cpu-mmasterr-a1bfab382.svg: Duration: 186.21s, Total samples = 207.21s (111.28%)
na-cpu-refactor-20b0d3c7c.svg: Duration: 112.41s, Total samples = 110.55s (98.35%)
ro-cpu-mmasterr-a1bfab382.svg: Duration: 44.21s, Total samples = 49.45s (111.86%)
ro-cpu-refactor-20b0d3c7c.svg: Duration: 15.91s, Total samples = 12.80s (80.43%)

Memory
ds-mem-mmasterr-a1bfab382.svg: Showing nodes accounting for 5903.69MB, 76.03% of 7764.73MB total
ds-mem-refactor-20b0d3c7c.svg: Showing nodes accounting for 3108.60MB, 63.65% of 4884.12MB total
na-mem-mmasterr-a1bfab382.svg: Showing nodes accounting for 109331.30MB, 89.11% of 122696.48MB total
na-mem-refactor-20b0d3c7c.svg: Showing nodes accounting for 37047.11MB, 75.00% of 49398.96MB total
ro-mem-mmasterr-a1bfab382.svg: Showing nodes accounting for 26387.54MB, 97.10% of 27174.74MB total
ro-mem-refactor-20b0d3c7c.svg: Showing nodes accounting for 2146.75MB, 82.69% of 2596.03MB total

This PR also includes a benchmark test for GetAnnotations. Here's the run against master, followed by the run against the PR:

$ go test ./kyaml/yaml/ -bench=. -benchmem -run nope
goos: windows
goarch: amd64
pkg: sigs.k8s.io/kustomize/kyaml/yaml
cpu: Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz
BenchmarkGetAnnotations/00-8             5349039               229.1 ns/op           192 B/op          4 allocs/op
BenchmarkGetAnnotations/02-8             1225515               997.3 ns/op           960 B/op         16 allocs/op
BenchmarkGetAnnotations/05-8              631647              1830 ns/op            1584 B/op         27 allocs/op
BenchmarkGetAnnotations/08-8              463942              2500 ns/op            2016 B/op         36 allocs/op
PASS
ok      sigs.k8s.io/kustomize/kyaml/yaml        6.607s
$ go test ./kyaml/yaml/ -bench=. -benchmem -run nope
goos: windows
goarch: amd64
pkg: sigs.k8s.io/kustomize/kyaml/yaml
cpu: Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz
BenchmarkGetAnnotations/00-8            12776966                96.90 ns/op           48 B/op          1 allocs/op
BenchmarkGetAnnotations/02-8             4175845               285.9 ns/op           336 B/op          2 allocs/op
BenchmarkGetAnnotations/05-8             3299260               341.5 ns/op           336 B/op          2 allocs/op
BenchmarkGetAnnotations/08-8             2961939               417.2 ns/op           336 B/op          2 allocs/op
PASS
ok      sigs.k8s.io/kustomize/kyaml/yaml        6.544s

Closes #4940

Refactor mapping node content traversal so that all code paths execute
through the same root function.
This commit optimizes in three ways:

1. For heavily used functions, allocate memory to avoid overhead
   associated with map and array re-sizing.
2. Where appropriate, limit annotation and label retrievals to only the
   desired keys.
3. Adjust annotation and label retrieval to avoid unnecessary temporary
   object creation.
@k8s-ci-robot
Copy link
Contributor

@ephesused: This PR has multiple commits, and the default merge method is: merge.
You can request commits to be squashed using the label: tide/merge-method-squash

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

@k8s-ci-robot k8s-ci-robot added cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. labels Dec 21, 2022
@k8s-ci-robot
Copy link
Contributor

Hi @ephesused. Thanks for your PR.

I'm waiting for a kubernetes-sigs member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should join the org to skip this step.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

@k8s-ci-robot k8s-ci-robot added the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label Dec 21, 2022
@natasha41575
Copy link
Contributor

/ok-to-test

@k8s-ci-robot k8s-ci-robot added ok-to-test Indicates a non-member PR verified by an org member that is safe to test. and removed needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. labels Dec 21, 2022
Copy link
Contributor

@natasha41575 natasha41575 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I quite like the approach in this PR. I have a couple of comments as a first pass but I think this is a good direction. In particular, having node traversal go through the same code path is a good change on its own and the performance boost is a huge bonus. I think we should try to get this PR into the next release if we can.

cc @KnVerey

// restricted to only those entries with keys that match
// one of the specific annotations. If no annotations are
// provided, then the map will contain all entries.
func (rn *RNode) GetAnnotations(annotations ...string) map[string]string {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to this change, I like the option of providing specific annotations/labels to get.

return rn, nil

content := rn.Content()
stillMissing := true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I suggest changing the name of this bool to fieldStillNotFound

return true
})
default: // visit specified fields
// assumption: fields in content have unique names
Copy link
Contributor

@natasha41575 natasha41575 Dec 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to add test coverage for this assumption before we make it. The kyaml Filters are part of the public kyaml api, so we do need to be a little bit careful.

Could you pick one of the Filters that uses this visitMappingNodeFields, and write a test in fns_test.go that contains duplicate keys? If it throws some sort of error saying you can't have duplicate keys, then we are fine with this assumption. But if it does not throw any error and instead behaves in some unexpected or obviously incorrect way, then this assumption is risky. One option for handling duplicate names is to simply store a set of visited field names (in go, I usually implement this as a map[string]bool), so that we can check it right before we increment found.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback and discussion.

Considering the current behavior, I don't think we should introduce an error condition here. Instead, I think the best approach is to create a local "to be found" map[string]bool of fieldNames and use that to determine when a given field should be added to the result map.

I don't believe there's clear guidance since the current behavior does not support restricting the visited mapping node fields. However, I think we can use the current behavior for guidance. With a quick look around, I don't see any methods or functions that ensure mapping node keys are unique. Given that, I don't think it's appropriate to introduce that here. Instead, I think the uniqueness should be handled on the passed fieldNames.

In the case of matching keys, getMapFromMeta's current behavior stores the value of the first key: The VisitFields loop iterates over the full list of keys (in order), using Field for processing. Field returns the first match. So if there are duplicates, then the key is processed multiple times - but each time Field will find the first match and its value will be what's placed into the getMapFromMeta map. I'm going to follow that pattern for how visitMappingNodeFields will execute.

I'll have another commit coming to show what I'm thinking.

@ephesused
Copy link
Contributor Author

Thanks for the feedback - I hope to have updates (and lint fixes) in the next day or two. I haven't figured out how to get kustomize's linting working properly in my dev environment, so I'm working a bit in the dark on that front.

This commit adjusts visitMappingNodeFields so that it no longer assumes
the mapping node has unique keys.
@ephesused
Copy link
Contributor Author

Here are the CPU and memory amounts for a0e94c1, the latest commit in the PR. The key point is that, compared to 20b0d3c (the prior refactor commit), the changes in a0e94c1 didn't have a negative effect on either run time or memory consumption. (This output also highlights that there's some non-trivial variance between runs on this machine - the master runs previously were 13.41s, 207.21s, and 49.45s.)

CPU
ds-cpu-mmasterr-a1bfab382.svg: Duration: 15.83s, Total samples = 15.22s (96.13%)
ds-cpu-refactor-a0e94c164.svg: Duration: 9.54s, Total samples = 9.22s (96.67%)
na-cpu-mmasterr-a1bfab382.svg: Duration: 217.59s, Total samples = 235.22s (108.10%)
na-cpu-refactor-a0e94c164.svg: Duration: 108.78s, Total samples = 109.33s (100.50%)
ro-cpu-mmasterr-a1bfab382.svg: Duration: 52.26s, Total samples = 53.37s (102.13%)
ro-cpu-refactor-a0e94c164.svg: Duration: 13.33s, Total samples = 11.47s (86.05%)

Memory
ds-mem-mmasterr-a1bfab382.svg: Showing nodes accounting for 6074.23MB, 78.28% of 7759.69MB total
ds-mem-refactor-a0e94c164.svg: Showing nodes accounting for 2928.66MB, 61.72% of 4745.28MB total
na-mem-mmasterr-a1bfab382.svg: Showing nodes accounting for 109009.98MB, 89.07% of 122384.84MB total
na-mem-refactor-a0e94c164.svg: Showing nodes accounting for 36909.94MB, 75.09% of 49155.69MB total
ro-mem-mmasterr-a1bfab382.svg: Showing nodes accounting for 26625.33MB, 97.13% of 27412.12MB total
ro-mem-refactor-a0e94c164.svg: Showing nodes accounting for 2169.26MB, 82.00% of 2645.47MB total

The benchmark run results for GetAnnotations are pretty much the same as they were with the earlier refactor commit:

$ go test ./kyaml/yaml/ -bench=. -benchmem -run nope
goos: windows
goarch: amd64
pkg: sigs.k8s.io/kustomize/kyaml/yaml
cpu: Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz
BenchmarkGetAnnotations/00-8            12347928                89.35 ns/op           48 B/op          1 allocs/op
BenchmarkGetAnnotations/02-8             3991108               284.1 ns/op           336 B/op          2 allocs/op
BenchmarkGetAnnotations/05-8             3191481               343.9 ns/op           336 B/op          2 allocs/op
BenchmarkGetAnnotations/08-8             2733621               421.4 ns/op           336 B/op          2 allocs/op
PASS
ok      sigs.k8s.io/kustomize/kyaml/yaml        7.165s

@natasha41575
Copy link
Contributor

/approve

I'll defer the LGTM to @KnVerey, so that she has a chance to take a look as well.

@k8s-ci-robot k8s-ci-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Jan 6, 2023
@ephesused
Copy link
Contributor Author

@KnVerey, just checking in here. It'd be nice to get this in to the next release. Thanks.

Copy link
Contributor

@KnVerey KnVerey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/lgtm

Really nice work, thank you!

@@ -460,11 +457,13 @@ func (rn *RNode) getMetaData() *RNode {
} else {
n = rn
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I think we can avoid creating a new RNode above as well

	content := rn.Content()
	if rn.YNode().Kind == DocumentNode {
		// get the content if this is the document node
		content = content[0].Content
	} 

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, good catch. I'll plan to provide a small PR to tidy that up. Thanks!

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Jan 26, 2023
@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: ephesused, KnVerey, natasha41575

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:
  • OWNERS [KnVerey,natasha41575]

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. lgtm "Looks good to me", indicates that a PR is ready to be merged. ok-to-test Indicates a non-member PR verified by an org member that is safe to test. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Optimize memory and execution time for RNode.GetAnnotations and RNode.GetLabels
4 participants