diff --git a/pkg/v1/partial/uncompressed.go b/pkg/v1/partial/uncompressed.go index 669dc0485..629c1ffb9 100644 --- a/pkg/v1/partial/uncompressed.go +++ b/pkg/v1/partial/uncompressed.go @@ -110,6 +110,9 @@ type uncompressedImageExtender struct { lock sync.Mutex manifest *v1.Manifest + + layers []v1.Layer + layerOnce sync.Once } // Assert that our extender type completes the v1.Image interface @@ -192,19 +195,28 @@ func (i *uncompressedImageExtender) ConfigFile() (*v1.ConfigFile, error) { // Layers implements v1.Image func (i *uncompressedImageExtender) Layers() ([]v1.Layer, error) { - diffIDs, err := DiffIDs(i) - if err != nil { - return nil, err - } - ls := make([]v1.Layer, 0, len(diffIDs)) - for _, h := range diffIDs { - l, err := i.LayerByDiffID(h) + var outerErr error + i.layerOnce.Do(func() { + diffIDs, err := DiffIDs(i) if err != nil { - return nil, err + outerErr = err + return + } + i.layers = make([]v1.Layer, 0, len(diffIDs)) + for _, h := range diffIDs { + l, err := i.LayerByDiffID(h) + if err != nil { + outerErr = err + return + } + i.layers = append(i.layers, l) } - ls = append(ls, l) + }) + if outerErr != nil { + return nil, outerErr } - return ls, nil + + return i.layers, nil } // LayerByDiffID implements v1.Image diff --git a/pkg/v1/remote/write.go b/pkg/v1/remote/write.go index 8939cd3b5..6fc5ff0ae 100644 --- a/pkg/v1/remote/write.go +++ b/pkg/v1/remote/write.go @@ -57,7 +57,7 @@ func Write(ref name.Reference, img v1.Image, options ...Option) error { if err != nil { return err } - w := writer{ + w := &writer{ ref: ref, client: &http.Client{Transport: tr}, } @@ -67,6 +67,26 @@ func Write(ref name.Reference, img v1.Image, options ...Option) error { // the digest for whatever reason, we can't dedupe and might re-upload. var g errgroup.Group uploaded := map[v1.Hash]bool{} + + // precompute digests in parallel + // computing digests is expensive, and the next stage does it serially + // since digests are cached, there is no need to store them in an array + for _, l := range ls { + l := l + + g.Go(func() error { + // Streaming layers calculate their digests while uploading them. Assume + // an error here indicates we need to upload the layer. + _, _ = l.Digest() + return nil + }) + } + err = g.Wait() + if err != nil { + return err + } + + g = errgroup.Group{} for _, l := range ls { l := l @@ -346,6 +366,7 @@ func (w *writer) uploadOne(l v1.Layer) error { if err != nil { return err } + digest := h.String() if err := w.commitBlob(location, digest); err != nil { @@ -448,7 +469,7 @@ func WriteIndex(ref name.Reference, ii v1.ImageIndex, options ...Option) error { if err != nil { return err } - w := writer{ + w := &writer{ ref: ref, client: &http.Client{Transport: tr}, } @@ -504,7 +525,7 @@ func WriteLayer(ref name.Digest, layer v1.Layer, options ...Option) error { if err != nil { return err } - w := writer{ + w := &writer{ ref: ref, client: &http.Client{Transport: tr}, } diff --git a/pkg/v1/remote/write_test.go b/pkg/v1/remote/write_test.go index 7dce1c705..9903819bf 100644 --- a/pkg/v1/remote/write_test.go +++ b/pkg/v1/remote/write_test.go @@ -1269,3 +1269,30 @@ func TestTag(t *testing.T) { t.Errorf("Validate() = %v", err) } } + +func BenchmarkWrite(b *testing.B) { + // unfortunately the registry _and_ the img have caching behaviour, so we need a new registry + // and image every iteration of benchmarking. + for i := 0; i < b.N; i++ { + // set up the registry + s := httptest.NewServer(registry.New()) + defer s.Close() + + // load the image + img, err := random.Image(50*1024*1024, 10) + if err != nil { + b.Fatalf("random.Image(...): %v", err) + } + + tagStr := strings.TrimPrefix(s.URL+"/test/image:tag", "http://") + tag, err := name.NewTag(tagStr) + if err != nil { + b.Fatalf("parsing tag (%s): %v", tagStr, err) + } + + err = Write(tag, img) + if err != nil { + b.Fatalf("pushing tag one: %v", err) + } + } +}