Repository: moby/moby
File: daemon/list.go (Go)

Code before:
				if filter.Size {
					sizeRw, sizeRootFs, err := daemon.imageService.GetContainerLayerSize(ctx, newC.ID)
					if err != nil {
						return
					}
					newC.SizeRw = sizeRw
					newC.SizeRootFs = sizeRootFs
				}
				results <- newC
			}(currentContainer)
		}
	}
	go func() {
		wg.Wait()
		close(results)
	}()
	for c := range results {
		containers = append(containers, c)
	}
	return containers, nil
}

func (daemon *Daemon) filterByNameIDMatches(view *container.View, filter *listContext) ([]container.Snapshot, error) {
	idSearch := false
	names := filter.filters.Get("name")
	ids := filter.filters.Get("id")
	if len(names)+len(ids) == 0 {
		// if name or ID filters are not in use, return to
		// standard behavior of walking the entire container
		// list from the daemon's in-memory store
		all, err := view.All()
		if err != nil {
			return nil, err
		}
		sort.Sort(byCreatedDescending(all))
		return all, nil
	}

	// idSearch will determine if we limit name matching to the IDs
	// matched from any IDs which were specified as filters
	if len(ids) > 0 {
		idSearch = true
	}

	matches := make(map[string]bool)
	// find ID matches; errors represent "not found" and can be ignored
	for _, id := range ids {
		if fullID, err := daemon.containersReplica.GetByPrefix(id); err == nil {
			matches[fullID] = true
		}
	}

Review comment (suggestion):
I was curious if using a slice + mutex would be faster; also because taking the channel out of the code, would allow the filter to directly append to the results, so we wouldn't have to deal with managing the channel, and wouldn't have to copy the results back.


I did some quick benchmark with 100000 container summaries and the "filter" goroutine being a "1 ms" sleep, and from that it looks like a mutex is equally fast (if not faster), and same for allocations (memory was slightly less), so from that perspective, it seems like using a slice and mutex would be a win-win (easier in this context, and possibly slightly more performant);

```
BenchmarkListWithMutex-10      	       1	6344344208 ns/op	17709304 B/op	  400274 allocs/op
BenchmarkListWithChannel-10    	       1	6371296125 ns/op	21312696 B/op	  400063 allocs/op
```


Code after:
				// get the image size (calculation is slow)
				if filter.Size {
					var err error
					newC.SizeRw, newC.SizeRootFs, err = daemon.imageService.GetContainerLayerSize(ctx, newC.ID)
					if err != nil {
						return err
					}
				}

				// insert the result at the given index (so the output is in the
				// same order as containerList above).
				resultsMut.Lock()
				results[idx] = newC
				resultsMut.Unlock()

				return nil
			})
		}
		if stop {
			break
		}
	}

	if err := g.Wait(); err != nil {
		return nil, err
	}

	return results[:filter.idx], nil
}

func (daemon *Daemon) filterByNameIDMatches(view *container.View, filter *listContext) ([]container.Snapshot, error) {
	idSearch := false
	names := filter.filters.Get("name")
	ids := filter.filters.Get("id")
	if len(names)+len(ids) == 0 {
		// if name or ID filters are not in use, return to
		// standard behavior of walking the entire container
		// list from the daemon's in-memory store
		all, err := view.All()
		if err != nil {
			return nil, err
		}

		sort.Sort(byCreatedDescending(all))
		return all, nil
	}

	// idSearch will determine if we limit name matching to the IDs
	// matched from any IDs which were specified as filters
	if len(ids) > 0 {
		idSearch = true