go.image/tiff: optimize reading uncompressed files from a tiff.buffer.
In case the image is read via a tiff.buffer, avoid copying the
data strip before decoding it. Remove corresponding TODO.
Speeds up reading uncompressed images (which is the common case)
and uses much less memory.
benchmark old ns/op new ns/op delta
BenchmarkDecodeCompressed 4619438 4630774 +0.25%
BenchmarkDecodeUncompressed 260809 219875 -15.70%
R=nigeltao
CC=golang-dev
https://golang.org/cl/5683050
diff --git a/tiff/buffer.go b/tiff/buffer.go
index 27533c6..d1801be 100644
--- a/tiff/buffer.go
+++ b/tiff/buffer.go
@@ -12,13 +12,8 @@
buf []byte
}
-func (b *buffer) ReadAt(p []byte, off int64) (int, error) {
- o := int(off)
- end := o + len(p)
- if int64(end) != off+int64(len(p)) {
- return 0, io.ErrUnexpectedEOF
- }
-
+// fill reads data from b.r until the buffer contains at least end bytes.
+func (b *buffer) fill(end int) error {
m := len(b.buf)
if end > m {
if end > cap(b.buf) {
@@ -35,11 +30,31 @@
if n, err := io.ReadFull(b.r, b.buf[m:end]); err != nil {
end = m + n
b.buf = b.buf[:end]
- return copy(p, b.buf[o:end]), err
+ return err
}
}
+ return nil
+}
- return copy(p, b.buf[o:end]), nil
+func (b *buffer) ReadAt(p []byte, off int64) (int, error) {
+ o := int(off)
+ end := o + len(p)
+ if int64(end) != off+int64(len(p)) {
+ return 0, io.ErrUnexpectedEOF
+ }
+
+ err := b.fill(end)
+ return copy(p, b.buf[o:end]), err
+}
+
+// Slice returns a slice of the underlying buffer. The slice contains
+// n bytes starting at offset off.
+func (b *buffer) Slice(off, n int) ([]byte, error) {
+ end := off + n
+ if err := b.fill(end); err != nil {
+ return nil, err
+ }
+ return b.buf[off:end], nil
}
// newReaderAt converts an io.Reader into an io.ReaderAt.
diff --git a/tiff/reader.go b/tiff/reader.go
index dc5a87a..0603b1e 100644
--- a/tiff/reader.go
+++ b/tiff/reader.go
@@ -397,9 +397,12 @@
n := int64(d.features[tStripByteCounts][i])
switch d.firstVal(tCompression) {
case cNone:
- // TODO(bsiegert): Avoid copy if r is a tiff.buffer.
- d.buf = make([]byte, n)
- _, err = d.r.ReadAt(d.buf, offset)
+ if b, ok := d.r.(*buffer); ok {
+ d.buf, err = b.Slice(int(offset), int(n))
+ } else {
+ d.buf = make([]byte, n)
+ _, err = d.r.ReadAt(d.buf, offset)
+ }
case cLZW:
r := lzw.NewReader(io.NewSectionReader(d.r, offset, n), lzw.MSB, 8)
d.buf, err = ioutil.ReadAll(r)