Skip to content

Commit 93aa273

Browse files
authored
Improve performance of v1.NewHash (#2194)
* Improve performance of v1.NewHash Previously, NewHash() made multiple unnecessary heap allocations that have been removed. I've also added a simple benchmark test to measure the impact. Results below: Before: ``` go test ./pkg/v1 -run XXX -bench BenchmarkNewHash goos: darwin goarch: arm64 pkg: github.com/google/go-containerregistry/pkg/v1 cpu: Apple M1 Pro BenchmarkNewHash-10 8977393 123.1 ns/op 160 B/op 2 allocs/op PASS ok github.com/google/go-containerregistry/pkg/v1 1.711s ``` After: ``` $ go test ./pkg/v1 -run XXX -bench BenchmarkNewHash goos: darwin goarch: arm64 pkg: github.com/google/go-containerregistry/pkg/v1 cpu: Apple M1 Pro BenchmarkNewHash-10 15214291 76.09 ns/op 0 B/op 0 allocs/op PASS ok github.com/google/go-containerregistry/pkg/v1 1.805s ``` * add "Too many separators" cases to TestBadHashes
1 parent 795787c commit 93aa273

File tree

2 files changed

+33
-10
lines changed

2 files changed

+33
-10
lines changed

pkg/v1/hash.go

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -84,27 +84,35 @@ func Hasher(name string) (hash.Hash, error) {
8484
}
8585

8686
func (h *Hash) parse(unquoted string) error {
87-
parts := strings.Split(unquoted, ":")
88-
if len(parts) != 2 {
87+
algo, body, ok := strings.Cut(unquoted, ":")
88+
if !ok || algo == "" || body == "" {
8989
return fmt.Errorf("cannot parse hash: %q", unquoted)
9090
}
9191

92-
rest := strings.TrimLeft(parts[1], "0123456789abcdef")
92+
rest := strings.TrimLeft(body, "0123456789abcdef")
9393
if len(rest) != 0 {
9494
return fmt.Errorf("found non-hex character in hash: %c", rest[0])
9595
}
9696

97-
hasher, err := Hasher(parts[0])
98-
if err != nil {
99-
return err
97+
var wantBytes int
98+
switch algo {
99+
case "sha256":
100+
wantBytes = crypto.SHA256.Size()
101+
default:
102+
hasher, err := Hasher(algo)
103+
if err != nil {
104+
return err
105+
}
106+
wantBytes = hasher.Size()
100107
}
108+
101109
// Compare the hex to the expected size (2 hex characters per byte)
102-
if len(parts[1]) != hasher.Size()*2 {
103-
return fmt.Errorf("wrong number of hex digits for %s: %s", parts[0], parts[1])
110+
if len(body) != hex.EncodedLen(wantBytes) {
111+
return fmt.Errorf("wrong number of hex digits for %s: %s", algo, body)
104112
}
105113

106-
h.Algorithm = parts[0]
107-
h.Hex = parts[1]
114+
h.Algorithm = algo
115+
h.Hex = body
108116
return nil
109117
}
110118

pkg/v1/hash_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ func TestBadHashes(t *testing.T) {
5151
"sha256:deadbeef",
5252
// Bad character
5353
"sha256:o123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
54+
// Too many separators
55+
"sha256:abc:def",
56+
"sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef:",
57+
"sha256:0:123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
58+
"sha256::0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
5459
// Unknown algorithm
5560
"md5:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
5661
// Too few parts
@@ -113,3 +118,13 @@ func TestTextMarshalling(t *testing.T) {
113118
t.Errorf("mismatched hash: %s != %s", h, g)
114119
}
115120
}
121+
122+
func BenchmarkNewHash(b *testing.B) {
123+
b.ReportAllocs()
124+
for b.Loop() {
125+
_, err := NewHash("sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")
126+
if err != nil {
127+
b.Fatal(err)
128+
}
129+
}
130+
}

0 commit comments

Comments
 (0)