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

Avoid deep copying objects twice in the CacheReader List method #621

Closed
charith-elastic opened this issue Oct 1, 2019 · 3 comments · Fixed by #625
Closed

Avoid deep copying objects twice in the CacheReader List method #621

charith-elastic opened this issue Oct 1, 2019 · 3 comments · Fixed by #625

Comments

@charith-elastic
Copy link
Contributor

In the List method of the CacheReader, the list of objects retrieved from the index is added to the runtimeObjs array by deep copying each object. This array is then passed to the objectutil.FilterWithLabels which again creates a deep copy of the objects that match the label selector.

It would be more efficient to inline the label filtering logic as follows to avoid this extra work and reduce heap usage of controllers that work with large number of items.

diff --git a/pkg/cache/internal/cache_reader.go b/pkg/cache/internal/cache_reader.go
index a9b7ae34..8564a2da 100644
--- a/pkg/cache/internal/cache_reader.go
+++ b/pkg/cache/internal/cache_reader.go
@@ -30,7 +30,6 @@ import (
 	"k8s.io/apimachinery/pkg/selection"
 	"k8s.io/client-go/tools/cache"
 	"sigs.k8s.io/controller-runtime/pkg/client"
-	"sigs.k8s.io/controller-runtime/pkg/internal/objectutil"
 )
 
 // CacheReader is a CacheReader
@@ -125,15 +124,22 @@ func (c *CacheReader) List(_ context.Context, out runtime.Object, opts ...client
 		if !isObj {
 			return fmt.Errorf("cache contained %T, which is not an Object", obj)
 		}
+		meta, err := apimeta.Accessor(obj)
+		if err != nil {
+			return err
+		}
+		if labelSel != nil {
+			lbls := labels.Set(meta.GetLabels())
+			if !labelSel.Matches(lbls) {
+				continue
+			}
+		}
+
 		outObj := obj.DeepCopyObject()
 		outObj.GetObjectKind().SetGroupVersionKind(c.groupVersionKind)
 		runtimeObjs = append(runtimeObjs, outObj)
 	}
-	filteredItems, err := objectutil.FilterWithLabels(runtimeObjs, labelSel)
-	if err != nil {
-		return err
-	}
-	return apimeta.SetList(out, filteredItems)
+	return apimeta.SetList(out, runtimeObjs)
 }
 
 // objectKeyToStorageKey converts an object key to store key.

Are there any downsides to patching the method as suggested? If not, I'll be happy to raise a PR.

@mengqiy
Copy link
Member

mengqiy commented Oct 1, 2019

runtimeObjs := make([]runtime.Object, 0, len(objs))
for _, item := range objs {
obj, isObj := item.(runtime.Object)
if !isObj {
return fmt.Errorf("cache contained %T, which is not an Object", obj)
}
outObj := obj.DeepCopyObject()
outObj.GetObjectKind().SetGroupVersionKind(c.groupVersionKind)
runtimeObjs = append(runtimeObjs, outObj)
}
filteredItems, err := objectutil.FilterWithLabels(runtimeObjs, labelSel)
if err != nil {
return err
}
return apimeta.SetList(out, filteredItems)

We deepcopy twice currently. One in the for loop, the other one in FilterWithLabels.
I believe we can remove one deepcopy.
What you propose makes sense to me overall, though we need to agree on the implementation details.

Also be aware this potential issue: https://github.com/kubernetes-sigs/controller-runtime/pull/389/files#r274350644

cc: @DirectXMan12

@charith-elastic
Copy link
Contributor Author

I saw the comments in #389 too. When I ran the tests with the proposed change, there were no race detector warnings so I don't think that will be an issue.

@mengqiy
Copy link
Member

mengqiy commented Oct 2, 2019

Cool! Feel free to open a PR.

DirectXMan12 pushed a commit that referenced this issue Jan 31, 2020
Improve Dockerfile template such that copying vendor dir is cached.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants