crypto: Add buffer management to writer

benchmarks:

    benchmark                         old ns/op     new ns/op     delta
    BenchmarkEncryptWriter            37032949      37754353      +1.95%
    BenchmarkEncrypt                  35989611      36344593      +0.99%
    BenchmarkDecryptReader            38274466      38282979      +0.02%
    BenchmarkEncryptDecryptReader     77506506      77088612      -0.54%
    BenchmarkDecrypt                  36219298      36128875      -0.25%

    benchmark                         old MB/s     new MB/s     speedup
    BenchmarkEncryptWriter            226.52       222.19       0.98x
    BenchmarkEncrypt                  233.08       230.81       0.99x
    BenchmarkDecryptReader            219.17       219.12       1.00x
    BenchmarkEncryptDecryptReader     108.23       108.82       1.01x
    BenchmarkDecrypt                  231.61       232.19       1.00x

    benchmark                         old allocs     new allocs     delta
    BenchmarkEncryptWriter            20             16             -20.00%
    BenchmarkEncrypt                  12             12             +0.00%
    BenchmarkDecryptReader            13             13             +0.00%
    BenchmarkEncryptDecryptReader     32             27             -15.62%
    BenchmarkDecrypt                  10             10             +0.00%

    benchmark                         old bytes     new bytes     delta
    BenchmarkEncryptWriter            1020064       170331        -83.30%
    BenchmarkEncrypt                  1600          1600          +0.00%
    BenchmarkDecryptReader            841070        842094        +0.12%
    BenchmarkEncryptDecryptReader     3027433       845129        -72.08%
    BenchmarkDecrypt                  1573          1573          +0.00%
This commit is contained in:
Alexander Neumann 2015-04-24 22:55:40 +02:00
parent 387082f983
commit 9c43688d1a
2 changed files with 44 additions and 70 deletions

View file

@ -2,7 +2,7 @@ package crypto
import "sync" import "sync"
const defaultBufSize = 2048 const defaultBufSize = 32 * 1024 // 32KiB
var bufPool = sync.Pool{ var bufPool = sync.Pool{
New: func() interface{} { New: func() interface{} {

View file

@ -1,110 +1,84 @@
package crypto package crypto
import ( import (
"bytes"
"crypto/aes" "crypto/aes"
"crypto/cipher" "crypto/cipher"
"errors"
"fmt" "fmt"
"io" "io"
"sync"
) )
type encryptWriter struct { type encryptWriter struct {
iv []byte data []byte
wroteIV bool
data *bytes.Buffer
key *Key key *Key
s cipher.Stream s cipher.Stream
w io.Writer w io.Writer
origWr io.Writer closed bool
err error // remember error writing iv
} }
func (e *encryptWriter) Close() error { func (e *encryptWriter) Close() error {
// write mac if e.closed {
mac := poly1305Sign(e.data.Bytes()[ivSize:], e.data.Bytes()[:ivSize], &e.key.Sign) return errors.New("Close() called on already closed writer")
_, err := e.origWr.Write(mac) }
e.closed = true
// encrypt everything
iv, c := e.data[:ivSize], e.data[ivSize:]
e.s.XORKeyStream(c, c)
// compute mac
mac := poly1305Sign(c, iv, &e.key.Sign)
e.data = append(e.data, mac...)
// write everything
n, err := e.w.Write(e.data)
if err != nil { if err != nil {
return err return err
} }
// return buffer if n != len(e.data) {
bufPool.Put(e.data.Bytes()) return errors.New("not all bytes written")
}
// return buffer to pool
freeBuffer(e.data)
return nil return nil
} }
const encryptWriterChunkSize = 512 * 1024 // 512 KiB
var encryptWriterBufPool = sync.Pool{
New: func() interface{} {
return make([]byte, encryptWriterChunkSize)
},
}
func (e *encryptWriter) Write(p []byte) (int, error) { func (e *encryptWriter) Write(p []byte) (int, error) {
// write iv first // if e.data is too small, return it to the buffer and create new slice
if !e.wroteIV { if cap(e.data) < len(e.data)+len(p) {
_, e.err = e.origWr.Write(e.iv[:]) b := make([]byte, len(e.data), len(e.data)*2)
e.wroteIV = true copy(b, e.data)
freeBuffer(e.data)
e.data = b
} }
if e.err != nil { // copy new data to e.data
return 0, e.err e.data = append(e.data, p...)
} return len(p), nil
buf := encryptWriterBufPool.Get().([]byte)
defer encryptWriterBufPool.Put(buf)
written := 0
for len(p) > 0 {
max := len(p)
if max > encryptWriterChunkSize {
max = encryptWriterChunkSize
}
e.s.XORKeyStream(buf, p[:max])
n, err := e.w.Write(buf[:max])
if n != max {
if err == nil { // should never happen
err = io.ErrShortWrite
}
}
written += n
p = p[n:]
if err != nil {
e.err = err
return written, err
}
}
return written, nil
} }
// EncryptTo buffers data written to the returned io.WriteCloser. When Close() // EncryptTo buffers data written to the returned io.WriteCloser. When Close()
// is called, the data is encrypted an written to the underlying writer. // is called, the data is encrypted and written to the underlying writer.
func EncryptTo(ks *Key, wr io.Writer) io.WriteCloser { func EncryptTo(ks *Key, wr io.Writer) io.WriteCloser {
ew := &encryptWriter{ ew := &encryptWriter{
iv: newIV(), data: getBuffer(),
data: bytes.NewBuffer(getBuffer()[:0]),
key: ks, key: ks,
origWr: wr,
} }
// buffer iv for mac // buffer iv for mac
_, err := ew.data.Write(ew.iv[:]) ew.data = ew.data[:ivSize]
if err != nil { copy(ew.data, newIV())
panic(err)
}
c, err := aes.NewCipher(ks.Encrypt[:]) c, err := aes.NewCipher(ks.Encrypt[:])
if err != nil { if err != nil {
panic(fmt.Sprintf("unable to create cipher: %v", err)) panic(fmt.Sprintf("unable to create cipher: %v", err))
} }
ew.s = cipher.NewCTR(c, ew.iv[:]) ew.s = cipher.NewCTR(c, ew.data[:ivSize])
ew.w = io.MultiWriter(ew.data, wr) ew.w = wr
return ew return ew
} }