Skip to content

Commit

Permalink
Cache l2tables during read
Browse files Browse the repository at this point in the history
We read the L2 table from storage for reading every cluster. This
duplicates the number of I/Os. Now we read every L2 table exactly once
and cache it. With the default cluster size (64 KiB), images up to 8 GiB
will use 1 MiB for the L2 tables cache.

With this change reading entire image is 35.4 times faster for qcow2
image, and 1.6 times faster for compressed qcow2 image. Testing with
limactl create shows similar results.

Before:

    % go test -bench Read
    ...
    BenchmarkRead/qcow2-12               1        3958425000 ns/op
    BenchmarkRead/compressed_qcow2-12    1       10840170042 ns/op

    % limactl create --tty=false --plain
    ...
    3.50 GiB / 3.50 GiB [-------------------------------------] 100.00% 212.28 MiB/s

After:

    % go test -bench Read
    ...
    BenchmarkRead/qcow2-12         	10       111570342 ns/op
    BenchmarkRead/compressed_qcow2-12    1      6977782041 ns/op

    % time limactl create --tty=false --plain
    ...
    3.50 GiB / 3.50 GiB [-------------------------------------] 100.00% 324.13 MiB/s

Signed-off-by: Nir Soffer <nsoffer@redhat.com>
  • Loading branch information
nirs committed Oct 15, 2024
1 parent e137c22 commit bc1a9be
Showing 1 changed file with 17 additions and 2 deletions.
19 changes: 17 additions & 2 deletions image/qcow2/qcow2.go
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,7 @@ type Qcow2 struct {
errUnreadable error
clusterSize int
l1Table []l1TableEntry
l2TablesCache map[l1TableEntry][]l2TableEntry
decompressor Decompressor
BackingFile string `json:"backing_file"`
BackingFileFullPath string `json:"backing_file_full_path"`
Expand All @@ -549,7 +550,8 @@ type Qcow2 struct {
// and openWithType must be non-nil.
func Open(ra io.ReaderAt, openWithType image.OpenWithType) (*Qcow2, error) {
img := &Qcow2{
ra: ra,
ra: ra,
l2TablesCache: make(map[l1TableEntry][]l2TableEntry),
}
r := io.NewSectionReader(ra, 0, -1)
var err error
Expand Down Expand Up @@ -712,7 +714,7 @@ func (img *Qcow2) readAtAligned(p []byte, off int64) (int, error) {
extL2Entry = &extL2Table[l2Index]
l2Entry = extL2Entry.L2TableEntry
} else {
l2Table, err := readL2Table(img.ra, l2TableOffset, img.clusterSize)
l2Table, err := img.getL2Table(l1Entry)
if err != nil {
return 0, fmt.Errorf("failed to read L2 table for L1 entry %v (index %d): %w", l1Entry, l1Index, err)
}
Expand Down Expand Up @@ -752,6 +754,19 @@ func (img *Qcow2) readAtAligned(p []byte, off int64) (int, error) {
return n, err
}

func (img *Qcow2) getL2Table(l1Entry l1TableEntry) ([]l2TableEntry, error) {
var err error
l2Table := img.l2TablesCache[l1Entry]
if l2Table == nil {
l2Table, err = readL2Table(img.ra, l1Entry.l2Offset(), img.clusterSize)
if err != nil {
return nil, err
}
img.l2TablesCache[l1Entry] = l2Table
}
return l2Table, nil
}

func (img *Qcow2) readAtAlignedUnallocated(p []byte, off int64) (int, error) {
if img.backingImage == nil {
return img.readZero(p, off)
Expand Down

0 comments on commit bc1a9be

Please sign in to comment.