shithub: furgit

Download patch

ref: 7eaa8614c897a97d241335982f4c04f1f27b0715
parent: 3a4b0c924197ba6d9570c8ac338c32b8093398da
author: Runxi Yu <me@runxiyu.org>
date: Sun Nov 23 03:00:00 EST 2025

bufpool: Return bytes.Buffer, rather than a pointer to it

It's silly to allocate a bytes.Buffer struct, however small it is,
every time Borrow is called, since the entire purpose is to reduce
allocations.

--- a/internal/bufpool/buffers.go
+++ b/internal/bufpool/buffers.go
@@ -23,10 +23,11 @@
 // pooled buffer, the caller should invoke Release() to return it to the pool.
 //
 // Buffers must not be copied after first use; doing so can cause double-returns
-// to the pool and data races. A zero-value Buffer is not valid for use. Use
-// the pointer type (*Buffer) returned by Borrow/FromOwned to avoid accidental
-// copies.
+// to the pool and data races.
+//
+//go:nocopy
 type Buffer struct {
+	_    struct{} // for nocopy
 	buf  []byte
 	pool poolIndex
 }
@@ -69,7 +70,7 @@
 // unpooled buffer is allocated.
 //
 // The caller must call Release() when finished using the returned Buffer.
-func Borrow(capHint int) *Buffer {
+func Borrow(capHint int) Buffer {
 	if capHint < DefaultBufferCap {
 		capHint = DefaultBufferCap
 	}
@@ -76,7 +77,7 @@
 	classIdx, classCap, pooled := classFor(capHint)
 	if !pooled {
 		newBuf := make([]byte, 0, capHint)
-		return &Buffer{buf: newBuf, pool: unpooled}
+		return Buffer{buf: newBuf, pool: unpooled}
 	}
 	buf := bufferPools[classIdx].Get().(*[]byte)
 	if cap(*buf) < classCap {
@@ -83,14 +84,14 @@
 		*buf = make([]byte, 0, classCap)
 	}
 	slice := (*buf)[:0]
-	return &Buffer{buf: slice, pool: poolIndex(classIdx)}
+	return Buffer{buf: slice, pool: poolIndex(classIdx)}
 }
 
 // FromOwned constructs a Buffer from a caller-owned byte slice. The resulting
 // Buffer does not participate in pooling and will never be returned to the
 // internal pool when released.
-func FromOwned(buf []byte) *Buffer {
-	return &Buffer{buf: buf, pool: unpooled}
+func FromOwned(buf []byte) Buffer {
+	return Buffer{buf: buf, pool: unpooled}
 }
 
 // Resize adjusts the length of the buffer to n bytes. If n exceeds the current
--- a/internal/flatex/decompress_bytes.go
+++ b/internal/flatex/decompress_bytes.go
@@ -21,16 +21,16 @@
 	},
 }
 
-func Decompress(src []byte) (*bufpool.Buffer, int, error) {
+func Decompress(src []byte) (bufpool.Buffer, int, error) {
 	return DecompressSized(src, 0)
 }
 
-func DecompressSized(src []byte, sizeHint int) (*bufpool.Buffer, int, error) {
+func DecompressSized(src []byte, sizeHint int) (bufpool.Buffer, int, error) {
 	d := bufferDecompressorPool.Get().(*bufferDecompressor)
 	defer bufferDecompressorPool.Put(d)
 
 	if err := d.inflater.reset(src); err != nil {
-		return nil, 0, err
+		return bufpool.Buffer{}, 0, err
 	}
 
 	out := bufpool.Borrow(sizeHint)
@@ -47,7 +47,7 @@
 				return out, d.inflater.pos, nil
 			}
 			out.Release()
-			return nil, 0, d.inflater.err
+			return bufpool.Buffer{}, 0, d.inflater.err
 		}
 		d.inflater.step(&d.inflater)
 		if d.inflater.err != nil && len(d.inflater.toRead) == 0 {
--- a/internal/zlibx/decompress.go
+++ b/internal/zlibx/decompress.go
@@ -9,45 +9,45 @@
 	"git.sr.ht/~runxiyu/furgit/internal/flatex"
 )
 
-func Decompress(src []byte) (*bufpool.Buffer, error) {
+func Decompress(src []byte) (bufpool.Buffer, error) {
 	return DecompressSized(src, 0)
 }
 
-func DecompressSized(src []byte, sizeHint int) (*bufpool.Buffer, error) {
+func DecompressSized(src []byte, sizeHint int) (bufpool.Buffer, error) {
 	if len(src) < 6 {
-		return nil, io.ErrUnexpectedEOF
+		return bufpool.Buffer{}, io.ErrUnexpectedEOF
 	}
 
 	cmf := src[0]
 	flg := src[1]
 	if (cmf&0x0f != zlibDeflate) || (cmf>>4 > zlibMaxWindow) || (binary.BigEndian.Uint16(src[:2])%31 != 0) {
-		return nil, ErrHeader
+		return bufpool.Buffer{}, ErrHeader
 	}
 
 	offset := 2
 	if flg&0x20 != 0 {
-		return nil, ErrHeader
+		return bufpool.Buffer{}, ErrHeader
 	}
 
 	if len(src[offset:]) < 4 {
-		return nil, io.ErrUnexpectedEOF
+		return bufpool.Buffer{}, io.ErrUnexpectedEOF
 	}
 
 	deflateData := src[offset:]
 	out, consumed, err := flatex.DecompressSized(deflateData, sizeHint)
 	if err != nil {
-		return nil, err
+		return bufpool.Buffer{}, err
 	}
 
 	checksumPos := offset + consumed
 	if checksumPos+4 > len(src) {
 		out.Release()
-		return nil, io.ErrUnexpectedEOF
+		return bufpool.Buffer{}, io.ErrUnexpectedEOF
 	}
 	expected := binary.BigEndian.Uint32(src[checksumPos : checksumPos+4])
 	if expected != adler32.Checksum(out.Bytes()) {
 		out.Release()
-		return nil, ErrChecksum
+		return bufpool.Buffer{}, ErrChecksum
 	}
 	return out, nil
 }
--- a/loose.go
+++ b/loose.go
@@ -24,37 +24,37 @@
 	return filepath.Join("objects", hex[:2], hex[2:]), nil
 }
 
-func (repo *Repository) looseRead(id Hash) (ObjectType, *bufpool.Buffer, error) {
+func (repo *Repository) looseRead(id Hash) (ObjectType, bufpool.Buffer, error) {
 	ty, body, err := repo.looseReadTyped(id)
 	if err != nil {
-		return ObjectTypeInvalid, nil, err
+		return ObjectTypeInvalid, bufpool.Buffer{}, err
 	}
 	return ty, body, nil
 }
 
-func (repo *Repository) looseReadTyped(id Hash) (ObjectType, *bufpool.Buffer, error) {
+func (repo *Repository) looseReadTyped(id Hash) (ObjectType, bufpool.Buffer, error) {
 	path, err := repo.loosePath(id)
 	if err != nil {
-		return ObjectTypeInvalid, nil, err
+		return ObjectTypeInvalid, bufpool.Buffer{}, err
 	}
 	path = repo.repoPath(path)
 	f, err := os.Open(path)
 	if err != nil {
 		if os.IsNotExist(err) {
-			return ObjectTypeInvalid, nil, ErrNotFound
+			return ObjectTypeInvalid, bufpool.Buffer{}, ErrNotFound
 		}
-		return ObjectTypeInvalid, nil, err
+		return ObjectTypeInvalid, bufpool.Buffer{}, err
 	}
 	defer func() { _ = f.Close() }()
 
 	compressed, err := io.ReadAll(f)
 	if err != nil {
-		return ObjectTypeInvalid, nil, err
+		return ObjectTypeInvalid, bufpool.Buffer{}, err
 	}
 
 	raw, err := zlibx.Decompress(compressed)
 	if err != nil {
-		return ObjectTypeInvalid, nil, err
+		return ObjectTypeInvalid, bufpool.Buffer{}, err
 	}
 
 	rawBytes := raw.Bytes()
@@ -61,7 +61,7 @@
 	nul := bytes.IndexByte(rawBytes, 0)
 	if nul < 0 {
 		raw.Release()
-		return ObjectTypeInvalid, nil, ErrInvalidObject
+		return ObjectTypeInvalid, bufpool.Buffer{}, ErrInvalidObject
 	}
 
 	header := rawBytes[:nul]
@@ -70,17 +70,17 @@
 	ty, declaredSize, err := parseLooseHeader(header)
 	if err != nil {
 		raw.Release()
-		return ObjectTypeInvalid, nil, err
+		return ObjectTypeInvalid, bufpool.Buffer{}, err
 	}
 	if declaredSize != int64(len(body)) {
 		raw.Release()
-		return ObjectTypeInvalid, nil, ErrInvalidObject
+		return ObjectTypeInvalid, bufpool.Buffer{}, ErrInvalidObject
 	}
 
 	copy(rawBytes, body)
 	raw.Resize(len(body))
 	// if !repo.verifyRawObject(raw, id) {
-	// 	return ObjectTypeInvalid, nil, ErrInvalidObject
+	// 	return ObjectTypeInvalid, bufpool.Buffer{}, ErrInvalidObject
 	// }
 
 	return ty, raw, nil
--- a/pack_pack.go
+++ b/pack_pack.go
@@ -22,10 +22,10 @@
 	Offset   uint64
 }
 
-func (repo *Repository) packRead(id Hash) (ObjectType, *bufpool.Buffer, error) {
+func (repo *Repository) packRead(id Hash) (ObjectType, bufpool.Buffer, error) {
 	loc, err := repo.packIndexFind(id)
 	if err != nil {
-		return ObjectTypeInvalid, nil, err
+		return ObjectTypeInvalid, bufpool.Buffer{}, err
 	}
 	return repo.packReadAt(loc, id)
 }
@@ -48,22 +48,22 @@
 	return packlocation{}, ErrNotFound
 }
 
-func (repo *Repository) packReadAt(loc packlocation, want Hash) (ObjectType, *bufpool.Buffer, error) {
+func (repo *Repository) packReadAt(loc packlocation, want Hash) (ObjectType, bufpool.Buffer, error) {
 	ty, body, err := repo.packBodyResolveAtLocation(loc)
 	if err != nil {
-		return ObjectTypeInvalid, nil, err
+		return ObjectTypeInvalid, bufpool.Buffer{}, err
 	}
 	// if !repo.verifyTypedObject(ty, body.Bytes(), want) {
 	// 	body.Release()
-	// 	return ObjectTypeInvalid, nil, ErrInvalidObject
+	// 	return ObjectTypeInvalid, bufpool.Buffer{}, ErrInvalidObject
 	// }
 	return ty, body, nil
 }
 
-func (repo *Repository) packBodyResolveAtLocation(loc packlocation) (ObjectType, *bufpool.Buffer, error) {
+func (repo *Repository) packBodyResolveAtLocation(loc packlocation) (ObjectType, bufpool.Buffer, error) {
 	pf, err := repo.packFile(loc.PackPath)
 	if err != nil {
-		return ObjectTypeInvalid, nil, err
+		return ObjectTypeInvalid, bufpool.Buffer{}, err
 	}
 	return repo.packBodyResolveWithin(pf, loc.Offset)
 }
@@ -97,17 +97,17 @@
 	return ty, size, consumed, nil
 }
 
-func packSectionInflate(pf *packFile, start uint64, sizeHint int) (*bufpool.Buffer, error) {
+func packSectionInflate(pf *packFile, start uint64, sizeHint int) (bufpool.Buffer, error) {
 	if start > uint64(len(pf.data)) {
-		return nil, ErrInvalidObject
+		return bufpool.Buffer{}, ErrInvalidObject
 	}
 	body, err := zlibx.DecompressSized(pf.data[start:], sizeHint)
 	if err != nil {
-		return nil, err
+		return bufpool.Buffer{}, err
 	}
 	if sizeHint > 0 && len(body.Bytes()) != sizeHint {
 		body.Release()
-		return nil, ErrInvalidObject
+		return bufpool.Buffer{}, ErrInvalidObject
 	}
 	return body, nil
 }
@@ -225,13 +225,13 @@
 	}
 }
 
-func (repo *Repository) packBodyResolveWithin(pf *packFile, ofs uint64) (ObjectType, *bufpool.Buffer, error) {
+func (repo *Repository) packBodyResolveWithin(pf *packFile, ofs uint64) (ObjectType, bufpool.Buffer, error) {
 	if pf == nil {
-		return ObjectTypeInvalid, nil, ErrInvalidObject
+		return ObjectTypeInvalid, bufpool.Buffer{}, ErrInvalidObject
 	}
 
 	type deltaFrame struct {
-		delta *bufpool.Buffer
+		delta bufpool.Buffer
 	}
 	var frames []deltaFrame
 	defer func() {
@@ -241,16 +241,16 @@
 	}()
 
 	var (
-		body      *bufpool.Buffer
+		body      bufpool.Buffer
 		bodyReady bool
 		resultTy  ObjectType
 	)
-	fail := func(err error) (ObjectType, *bufpool.Buffer, error) {
+	fail := func(err error) (ObjectType, bufpool.Buffer, error) {
 		if bodyReady {
 			body.Release()
 			bodyReady = false
 		}
-		return ObjectTypeInvalid, nil, err
+		return ObjectTypeInvalid, bufpool.Buffer{}, err
 	}
 
 	resolved := false
@@ -348,20 +348,20 @@
 	return resultTy, body, nil
 }
 
-func packDeltaApply(base, delta *bufpool.Buffer) (*bufpool.Buffer, error) {
+func packDeltaApply(base, delta bufpool.Buffer) (bufpool.Buffer, error) {
 	pos := 0
 	baseBytes := base.Bytes()
 	deltaBytes := delta.Bytes()
 	srcSize, err := packVarintRead(deltaBytes, &pos)
 	if err != nil {
-		return nil, err
+		return bufpool.Buffer{}, err
 	}
 	dstSize, err := packVarintRead(deltaBytes, &pos)
 	if err != nil {
-		return nil, err
+		return bufpool.Buffer{}, err
 	}
 	if srcSize != len(baseBytes) {
-		return nil, ErrInvalidObject
+		return bufpool.Buffer{}, ErrInvalidObject
 	}
 	out := bufpool.Borrow(dstSize)
 	out.Resize(dstSize)
@@ -378,7 +378,7 @@
 			if op&0x01 != 0 {
 				if pos >= len(deltaBytes) {
 					out.Release()
-					return nil, ErrInvalidObject
+					return bufpool.Buffer{}, ErrInvalidObject
 				}
 				off |= int(deltaBytes[pos])
 				pos++
@@ -386,7 +386,7 @@
 			if op&0x02 != 0 {
 				if pos >= len(deltaBytes) {
 					out.Release()
-					return nil, ErrInvalidObject
+					return bufpool.Buffer{}, ErrInvalidObject
 				}
 				off |= int(deltaBytes[pos]) << 8
 				pos++
@@ -394,7 +394,7 @@
 			if op&0x04 != 0 {
 				if pos >= len(deltaBytes) {
 					out.Release()
-					return nil, ErrInvalidObject
+					return bufpool.Buffer{}, ErrInvalidObject
 				}
 				off |= int(deltaBytes[pos]) << 16
 				pos++
@@ -402,7 +402,7 @@
 			if op&0x08 != 0 {
 				if pos >= len(deltaBytes) {
 					out.Release()
-					return nil, ErrInvalidObject
+					return bufpool.Buffer{}, ErrInvalidObject
 				}
 				off |= int(deltaBytes[pos]) << 24
 				pos++
@@ -410,7 +410,7 @@
 			if op&0x10 != 0 {
 				if pos >= len(deltaBytes) {
 					out.Release()
-					return nil, ErrInvalidObject
+					return bufpool.Buffer{}, ErrInvalidObject
 				}
 				n |= int(deltaBytes[pos])
 				pos++
@@ -418,7 +418,7 @@
 			if op&0x20 != 0 {
 				if pos >= len(deltaBytes) {
 					out.Release()
-					return nil, ErrInvalidObject
+					return bufpool.Buffer{}, ErrInvalidObject
 				}
 				n |= int(deltaBytes[pos]) << 8
 				pos++
@@ -426,7 +426,7 @@
 			if op&0x40 != 0 {
 				if pos >= len(deltaBytes) {
 					out.Release()
-					return nil, ErrInvalidObject
+					return bufpool.Buffer{}, ErrInvalidObject
 				}
 				n |= int(deltaBytes[pos]) << 16
 				pos++
@@ -436,7 +436,7 @@
 			}
 			if off+n > len(baseBytes) || outPos+n > len(outBytes) {
 				out.Release()
-				return nil, ErrInvalidObject
+				return bufpool.Buffer{}, ErrInvalidObject
 			}
 			copy(outBytes[outPos:], baseBytes[off:off+n])
 			outPos += n
@@ -444,7 +444,7 @@
 			n := int(op)
 			if pos+n > len(deltaBytes) || outPos+n > len(outBytes) {
 				out.Release()
-				return nil, ErrInvalidObject
+				return bufpool.Buffer{}, ErrInvalidObject
 			}
 			copy(outBytes[outPos:], deltaBytes[pos:pos+n])
 			pos += n
@@ -451,13 +451,13 @@
 			outPos += n
 		default:
 			out.Release()
-			return nil, ErrInvalidObject
+			return bufpool.Buffer{}, ErrInvalidObject
 		}
 	}
 
 	if outPos != len(outBytes) {
 		out.Release()
-		return nil, ErrInvalidObject
+		return bufpool.Buffer{}, ErrInvalidObject
 	}
 	return out, nil
 }
--