Change:
<!-- Please use this template while reporting a bug and provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner. Thanks! If the matter is security related, please disclose it privately via --What happened: I used to fake a clientset to test my code. I found that it became very slow to init the clientset with a big number of objects when I was trying to simulate a large scale cluster, and the time seemed exponential increasing. For example: Initializing a clientset with: 5000 objects took 300ms objects took 2.58s objects took 11.82s objects took 54.64s. It's because the fake clientset used a slice to store each kind of objects, so it's quite slow to init the clientset with massive objects because it checked existence of an object by traversing all objects and comparing with them before adding one, which leads to O(n^2) time complexity. What you expected to happen: The fake clientset is very useful for development, it should have a reasonable time complexity to allow developers to use it for large scale scenario. How to reproduce it (as minimally and precisely as possible): Run this code: You would see the time spent on each operation, like: Anything else we need to know?: Environment: Kubernetes version (use ): 1.15.0-1.18.0 Cloud provider or hardware configuration: OS (e.g: ): Kernel (e.g. ): Install tools: Network plugin and version (if this is a network-related bug): Others:
There are no sig labels on this issue. Please add an appropriate label by using one of the following commands: - Please see the for a listing of the SIGs, working groups, and committees available. <detailsInstructions for interacting with me using PR comments are available . If you have questions or suggestions related to my behavior, please file an issue against the repository. </details
I had PR that uses map to store objects, which can reduce the init time to a reasonable value (tens of ms for objects). /assign

Discussion:
} expected := []unstructured.Unstructured{ <del> *newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"), </del> *newUnstructured("group/version", "TheKind", "ns-foo", "name-bar"), *newUnstructured("group/version", "TheKind", "ns-foo", "name-baz"), <ins> *newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"), </ins> } if !equality.Semantic.DeepEqual(listFirst.Items, expected) { t.Fatal(diff.ObjectGoPrintDiff(expected, listFirst.Items))

} expected := []metav1.PartialObjectMetadata{ <del> *newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-foo"), </del> *newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-bar"), *newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-baz"), <ins> *newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-foo"), </ins> } if !equality.Semantic.DeepEqual(listFirst.Items, expected) { t.Fatal(diff.ObjectGoPrintDiff(expected, listFirst.Items))

import ( "fmt" "reflect" <ins> "sort" </ins> "sync" jsonpatch "github.com/evanphx/json-patch"

scheme  ObjectScheme decoder runtime.Decoder lock    sync.RWMutex <del> objects map[schema.GroupVersionResource][]runtime.Object </del> <ins> objects map[schema.GroupVersionResource]map[types.NamespacedName]runtime.Object </ins> // The value type of watchers is a map of which the key is either a namespace or // all/non namespace aka "" and its value is list of fake watchers. // Manipulations on resources will broadcast the notification events into the

return &tracker{ scheme:   scheme, decoder:  decoder, <del> objects:  make(map[schema.GroupVersionResource][]runtime.Object), </del> <ins> objects:  make(map[schema.GroupVersionResource]map[types.NamespacedName]runtime.Object), </ins> watchers: make(map[schema.GroupVersionResource]map[string][]*watch.RaceFreeFakeWatcher), } }

return nil, errNotFound } <del> var matchingObjs []runtime.Object for _, obj := range objs { acc, err := meta.Accessor(obj) if err != nil { return nil, err } if acc.GetNamespace() != ns { continue } if acc.GetName() != name { continue } matchingObjs = append(matchingObjs, obj) } if len(matchingObjs) == 0 { </del> <ins> matchingObj, ok := objs[types.NamespacedName{Namespace: ns, Name: name}] if !ok { </ins> return nil, errNotFound } <del> if len(matchingObjs) > 1 { return nil, fmt.Errorf("more than one object matched gvr %s, ns: %q name: %q", gvr, ns, name) } </del> // Only one object should match in the tracker if it works // correctly, as Add/Update methods enforce kind/namespace/name // uniqueness. <del> obj := matchingObjs[0].DeepCopyObject() </del> <ins> obj := matchingObj.DeepCopyObject() </ins> if status, ok := obj.(*metav1.Status); ok { if status.Status != metav1.StatusSuccess { return nil, &errors.StatusError{ErrStatus: *status}

return errors.NewBadRequest(msg) } <del> for i, existingObj := range t.objects[gvr] { oldMeta, err := meta.Accessor(existingObj) if err != nil { return err } if oldMeta.GetNamespace() == newMeta.GetNamespace() && oldMeta.GetName() == newMeta.GetName() { if replaceExisting { for _, w := range t.getWatches(gvr, ns) { w.Modify(obj) } t.objects[gvr][i] = obj return nil </del> <ins> _, ok := t.objects[gvr] if !ok { t.objects[gvr] = make(map[types.NamespacedName]runtime.Object) } namespacedName := types.NamespacedName{Namespace: newMeta.GetNamespace(), Name: newMeta.GetName()} if _, ok = t.objects[gvr][namespacedName]; ok { if replaceExisting { for _, w := range t.getWatches(gvr, ns) { w.Modify(obj) </ins> } <del> return errors.NewAlreadyExists(gr, newMeta.GetName()) </del> <ins> t.objects[gvr][namespacedName] = obj return nil </ins> } <ins> return errors.NewAlreadyExists(gr, newMeta.GetName()) </ins> } if replaceExisting {

return errors.NewNotFound(gr, newMeta.GetName()) } <del> t.objects[gvr] = append(t.objects[gvr], obj) </del> <ins> t.objects[gvr][namespacedName] = obj </ins> for _, w := range t.getWatches(gvr, ns) { w.Add(obj)

t.lock.Lock() defer t.lock.Unlock() <del> found := false for i, existingObj := range t.objects[gvr] { objMeta, err := meta.Accessor(existingObj) if err != nil { return err } if objMeta.GetNamespace() == ns && objMeta.GetName() == name { obj := t.objects[gvr][i] t.objects[gvr] = append(t.objects[gvr][:i], t.objects[gvr][i+1:]...) for _, w := range t.getWatches(gvr, ns) { w.Delete(obj) } found = true break } </del> <ins> objs, ok := t.objects[gvr] if !ok { return errors.NewNotFound(gvr.GroupResource(), name) </ins> } <del> if found { return nil </del> <ins> namespacedName := types.NamespacedName{Namespace: ns, Name: name} obj, ok := objs[namespacedName] if !ok { return errors.NewNotFound(gvr.GroupResource(), name) </ins> } <del> return errors.NewNotFound(gvr.GroupResource(), name) </del> <ins> delete(objs, namespacedName) for _, w := range t.getWatches(gvr, ns) { w.Delete(obj) } return nil </ins> } // filterByNamespace returns all objects in the collection that // match provided namespace. Empty namespace matches // non-namespaced objects. <del> func filterByNamespace(objs []runtime.Object, ns string) ([]runtime.Object, error) { </del> <ins> func filterByNamespace(objs map[types.NamespacedName]runtime.Object, ns string) ([]runtime.Object, error) { </ins> var res []runtime.Object for _, obj := range objs {

res = append(res, obj) } <ins> // Sort res to get deterministic order. sort.Slice(res, func(i, j int) bool { acc1, _ := meta.Accessor(res[i]) acc2, _ := meta.Accessor(res[j]) if acc1.GetNamespace() != acc2.GetNamespace() { return acc1.GetNamespace() < acc2.GetNamespace() } return acc1.GetName() < acc2.GetName() }) </ins> return res, nil }