index/suffixarray: 4.5x faster index serialization (to memory)

Benchmark results (best of 3 runs):

old: suffixarray.BenchmarkSaveRestore	       1	1931909000 ns/op	  28.21 MB/s
new: suffixarray.BenchmarkSaveRestore	       5	 429721800 ns/op	 117.14 MB/s

R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5161043
diff --git a/src/pkg/index/suffixarray/suffixarray.go b/src/pkg/index/suffixarray/suffixarray.go
index 05b06c6..174460c 100644
--- a/src/pkg/index/suffixarray/suffixarray.go
+++ b/src/pkg/index/suffixarray/suffixarray.go
@@ -18,7 +18,7 @@
 
 import (
 	"bytes"
-	"gob"
+	"encoding/binary"
 	"io"
 	"os"
 	"regexp"
@@ -37,17 +37,76 @@
 	return &Index{data, qsufsort(data)}
 }
 
-// Read and Write slice the data into successive portions of length gobN,
-// so gob can allocate smaller buffers for its I/O.
-const gobN = 1 << 16 // slightly better than say 1 << 20 (BenchmarkSaveRestore)
+// writeInt writes an int x to w using buf to buffer the write.
+func writeInt(w io.Writer, buf []byte, x int) os.Error {
+	binary.PutVarint(buf, int64(x))
+	_, err := w.Write(buf[0:binary.MaxVarintLen64])
+	return err
+}
+
+// readInt reads an int x from r using buf to buffer the read and returns x.
+func readInt(r io.Reader, buf []byte) (int, os.Error) {
+	_, err := io.ReadFull(r, buf[0:binary.MaxVarintLen64]) // ok to continue with error
+	x, _ := binary.Varint(buf)
+	return int(x), err
+}
+
+// writeSlice writes data[:n] to w and returns n.
+// It uses buf to buffer the write.
+func writeSlice(w io.Writer, buf []byte, data []int) (n int, err os.Error) {
+	// encode as many elements as fit into buf
+	p := binary.MaxVarintLen64
+	for ; n < len(data) && p+binary.MaxVarintLen64 <= len(buf); n++ {
+		p += binary.PutUvarint(buf[p:], uint64(data[n]))
+	}
+
+	// update buffer size
+	binary.PutVarint(buf, int64(p))
+
+	// write buffer
+	_, err = w.Write(buf[0:p])
+	return
+}
+
+// readSlice reads data[:n] from r and returns n.
+// It uses buf to buffer the read.
+func readSlice(r io.Reader, buf []byte, data []int) (n int, err os.Error) {
+	// read buffer size
+	var size int
+	size, err = readInt(r, buf)
+	if err != nil {
+		return
+	}
+
+	// read buffer w/o the size
+	if _, err = io.ReadFull(r, buf[binary.MaxVarintLen64:size]); err != nil {
+		return
+	}
+
+	// decode as many elements as present in buf
+	for p := binary.MaxVarintLen64; p < size; n++ {
+		x, w := binary.Uvarint(buf[p:])
+		data[n] = int(x)
+		p += w
+	}
+
+	return
+}
+
+const bufSize = 16 << 10 // reasonable for BenchmarkSaveRestore
 
 // Read reads the index from r into x; x must not be nil.
 func (x *Index) Read(r io.Reader) os.Error {
-	d := gob.NewDecoder(r)
-	var n int
-	if err := d.Decode(&n); err != nil {
+	// buffer for all reads
+	buf := make([]byte, bufSize)
+
+	// read length
+	n, err := readInt(r, buf)
+	if err != nil {
 		return err
 	}
+
+	// allocate space
 	if 2*n < cap(x.data) || cap(x.data) < n {
 		// new data is significantly smaller or larger then
 		// existing buffers - allocate new ones
@@ -58,51 +117,45 @@
 		x.data = x.data[0:n]
 		x.sa = x.sa[0:n]
 	}
-	for i := 0; i < n; {
-		j := i + gobN
-		if j > n {
-			j = n
-		}
-		// data holds next piece of x.data; its length is updated by Decode
-		data := x.data[i:j]
-		if err := d.Decode(&data); err != nil {
+
+	// read data
+	if _, err := io.ReadFull(r, x.data); err != nil {
+		return err
+	}
+
+	// read index
+	for sa := x.sa; len(sa) > 0; {
+		n, err := readSlice(r, buf, sa)
+		if err != nil {
 			return err
 		}
-		if len(data) != j-i {
-			return os.NewError("suffixarray.Read: inconsistent data format")
-		}
-		// sa holds next piece of x.data; its length is updated by Decode
-		sa := x.sa[i:j]
-		if err := d.Decode(&sa); err != nil {
-			return err
-		}
-		if len(sa) != j-i {
-			return os.NewError("suffixarray.Read: inconsistent data format")
-		}
-		i = j
+		sa = sa[n:]
 	}
 	return nil
 }
 
 // Write writes the index x to w.
 func (x *Index) Write(w io.Writer) os.Error {
-	e := gob.NewEncoder(w)
-	n := len(x.data)
-	if err := e.Encode(n); err != nil {
+	// buffer for all writes
+	buf := make([]byte, bufSize)
+
+	// write length
+	if err := writeInt(w, buf, len(x.data)); err != nil {
 		return err
 	}
-	for i := 0; i < n; {
-		j := i + gobN
-		if j > n {
-			j = n
-		}
-		if err := e.Encode(x.data[i:j]); err != nil {
+
+	// write data
+	if _, err := w.Write(x.data); err != nil {
+		return err
+	}
+
+	// write index
+	for sa := x.sa; len(sa) > 0; {
+		n, err := writeSlice(w, buf, sa)
+		if err != nil {
 			return err
 		}
-		if err := e.Encode(x.sa[i:j]); err != nil {
-			return err
-		}
-		i = j
+		sa = sa[n:]
 	}
 	return nil
 }