Skip to content

Commit

Permalink
Reduce memory allocations
Browse files Browse the repository at this point in the history
Add `sync.Pool` to the packed and padded readers, the latter requires
changing to be an `io.ReadCloser` so there's a `Close()` method.

Switch from `io.CopyN(io.MultiWriter(w, h), r) to `io.CopyN(w,
io.TeeReader(r, h))` as `io.MultiWriter()` will mask any `io.Writer`
that implements `io.ReaderFrom` so a buffer would have to be allocated
instead.
  • Loading branch information
bodgit committed Jun 8, 2022
1 parent 64a544d commit ba09685
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 19 deletions.
47 changes: 37 additions & 10 deletions internal/packed/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import (
"encoding/binary"
"errors"
"io"
"sync"

"github.com/bodgit/plumbing"
"github.com/bodgit/rvz/internal/padding"
)

Expand All @@ -14,9 +16,11 @@ const (
sizeMask = padded - 1
)

var pool sync.Pool //nolint:gochecknoglobals

type readCloser struct {
rc io.ReadCloser
src io.Reader
src io.ReadCloser
size int64
buf *bytes.Buffer
offset int64
Expand All @@ -30,19 +34,18 @@ func (rc *readCloser) nextReader() (err error) {

rc.size = int64(size & sizeMask)

var nr io.Reader

if size&padded == padded {
nr, err = padding.NewReader(rc.rc, rc.offset)
nrc, err := padding.NewReader(rc.rc, rc.offset)
if err != nil {
return err
}

rc.src = plumbing.LimitReadCloser(nrc, rc.size)
} else {
nr = rc.rc
// Intentionally "hide" the underlying Close method
rc.src = io.NopCloser(io.LimitReader(rc.rc, rc.size))
}

rc.src = io.LimitReader(nr, rc.size)

return nil
}

Expand Down Expand Up @@ -76,6 +79,14 @@ func (rc *readCloser) read() (err error) {
rc.size -= n
rc.offset += n

if rc.size == 0 {
if err = rc.src.Close(); err != nil {
return
}

rc.src = nil
}

if rc.buf.Len() == rc.buf.Cap() {
break
}
Expand All @@ -92,7 +103,15 @@ func (rc *readCloser) Read(p []byte) (int, error) {
return rc.buf.Read(p)
}

func (rc *readCloser) Close() error {
func (rc *readCloser) Close() (err error) {
pool.Put(rc.buf)

if rc.src != nil {
if err = rc.src.Close(); err != nil {
return
}
}

return rc.rc.Close()
}

Expand All @@ -103,10 +122,18 @@ func (rc *readCloser) Close() error {
func NewReadCloser(rc io.ReadCloser, offset int64) (io.ReadCloser, error) {
nrc := &readCloser{
rc: rc,
buf: new(bytes.Buffer),
offset: offset,
}
nrc.buf.Grow(1 << 16)

b, ok := pool.Get().(*bytes.Buffer)
if ok {
b.Reset()
} else {
b = new(bytes.Buffer)
b.Grow(1 << 16)
}

nrc.buf = b

return nrc, nil
}
35 changes: 30 additions & 5 deletions internal/padding/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"encoding/binary"
"io"
"sync"

"github.com/bodgit/rvz/internal/util"
)
Expand All @@ -13,6 +14,9 @@ const (
maximumSize = 521
)

//nolint:gochecknoglobals
var prngPool, bufPool sync.Pool

type paddingReader struct {
prng []uint32
buf *bytes.Buffer
Expand All @@ -39,22 +43,43 @@ func (pr *paddingReader) Read(p []byte) (int, error) {
return pr.buf.Read(p)
}

func (pr *paddingReader) Close() error {
prngPool.Put(&pr.prng)
bufPool.Put(pr.buf)

return nil
}

// NewReader returns an io.Reader that generates a stream of GameCube and Wii
// padding data. The PRNG is seeded from the io.Reader r. The offset of where
// this padded stream starts relative to the beginning of the uncompressed
// disc image is also required.
func NewReader(r io.Reader, offset int64) (io.Reader, error) {
func NewReader(r io.Reader, offset int64) (io.ReadCloser, error) {
pr := new(paddingReader)

pr.prng = make([]uint32, initialSize, maximumSize)
p, ok := prngPool.Get().(*[]uint32)
if ok {
pr.prng = *p
pr.prng = pr.prng[:initialSize]
} else {
pr.prng = make([]uint32, initialSize, maximumSize)
}

if err := binary.Read(r, binary.BigEndian, pr.prng); err != nil {
return nil, err
}

pr.prng = pr.prng[:cap(pr.prng)]
pr.prng = pr.prng[:maximumSize]

b, ok := bufPool.Get().(*bytes.Buffer)
if ok {
b.Reset()
} else {
b = new(bytes.Buffer)
b.Grow(maximumSize << 2)
}

pr.buf = new(bytes.Buffer)
pr.buf.Grow(maximumSize << 2)
pr.buf = b

for i := initialSize; i < maximumSize; i++ {
pr.prng[i] = pr.prng[i-17]<<23 ^ pr.prng[i-16]>>9 ^ pr.prng[i-1]
Expand Down
12 changes: 8 additions & 4 deletions reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ func (pr *partReader) read() (err error) {

var n int64
//nolint:nestif
if n, err = io.CopyN(io.MultiWriter(pr.cluster[i], h), pr.gr, blockSize); err != nil {
if n, err = io.CopyN(pr.cluster[i], io.TeeReader(pr.gr, h), blockSize); err != nil {
if errors.Is(err, io.EOF) && j == 0 && n == 0 {
if err = pr.gr.Close(); err != nil {
return
Expand Down Expand Up @@ -351,7 +351,7 @@ func (pr *partReader) read() (err error) {
for j := 0; j < blocksPerCluster; j++ {
h.Reset()

if _, err = io.CopyN(io.MultiWriter(pr.cluster[k], h), zero.NewReader(), blockSize); err != nil {
if _, err = io.CopyN(pr.cluster[k], io.TeeReader(zero.NewReader(), h), blockSize); err != nil {
return
}

Expand All @@ -361,11 +361,13 @@ func (pr *partReader) read() (err error) {
_, _ = io.CopyN(pr.h0[k], zero.NewReader(), h0Padding)
}

buf := make([]byte, hashSize)

// Calculate the H1 hashes
for i := 0; i < subGroup; i++ {
for j := 0; j < subGroup; j++ {
h.Reset()
_, _ = io.Copy(h, io.LimitReader(bytes.NewReader(pr.h0[i*subGroup+j].Bytes()), h0Size))
_, _ = io.CopyBuffer(h, io.LimitReader(bytes.NewReader(pr.h0[i*subGroup+j].Bytes()), h0Size), buf)
_, _ = pr.h1[i].Write(h.Sum(nil))
}

Expand All @@ -375,7 +377,9 @@ func (pr *partReader) read() (err error) {
// Calculate the H2 hashes
for i := 0; i < subGroup; i++ {
h.Reset()
_, _ = io.Copy(h, io.NewSectionReader(bytes.NewReader(pr.h0[i*subGroup].Bytes()), h0Size+h0Padding, h1Size))
_, _ = io.CopyBuffer(h,
io.NewSectionReader(bytes.NewReader(pr.h0[i*subGroup].Bytes()), h0Size+h0Padding, h1Size),
buf)
_, _ = pr.h2.Write(h.Sum(nil))
}

Expand Down

0 comments on commit ba09685

Please sign in to comment.