From 3c6fe98eb1e5dada2613664182ead18be9e68819 Mon Sep 17 00:00:00 2001 From: Jakob Unterwurzacher Date: Tue, 20 Jun 2017 21:22:00 +0200 Subject: [PATCH] contentenc: use sync.Pool memory pools for encryption We use two levels of buffers: 1) 4kiB+overhead for each ciphertext block 2) 128kiB+overhead for each FUSE write (32 ciphertext blocks) This commit adds a sync.Pool for both levels. The memory-efficiency for small writes could be improved, as we now always use a 128kiB buffer. --- internal/contentenc/content.go | 56 ++++++++++++++++++++++++++++------ internal/fusefrontend/file.go | 2 ++ 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/internal/contentenc/content.go b/internal/contentenc/content.go index 3f578e8..9bc1c50 100644 --- a/internal/contentenc/content.go +++ b/internal/contentenc/content.go @@ -10,6 +10,8 @@ import ( "runtime" "sync" + "github.com/hanwen/go-fuse/fuse" + "github.com/rfjakob/gocryptfs/internal/cryptocore" "github.com/rfjakob/gocryptfs/internal/stupidgcm" "github.com/rfjakob/gocryptfs/internal/tlog" @@ -51,20 +53,43 @@ type ContentEnc struct { allZeroNonce []byte // Force decode even if integrity check fails (openSSL only) forceDecode bool + // Ciphertext block pool. Always returns cipherBS-sized byte slices. + cBlockPool sync.Pool + // Ciphertext write pool. Always returns byte slices of size + // fuse.MAX_KERNEL_WRITE + overhead. + cWritePool sync.Pool + cWriteSize int } // New returns an initialized ContentEnc instance. func New(cc *cryptocore.CryptoCore, plainBS uint64, forceDecode bool) *ContentEnc { cipherBS := plainBS + uint64(cc.IVLen) + cryptocore.AuthTagLen - - return &ContentEnc{ + // Take IV and GHASH overhead into account. + cWriteSize := int(fuse.MAX_KERNEL_WRITE / plainBS * cipherBS) + if fuse.MAX_KERNEL_WRITE%plainBS != 0 { + log.Panicf("unaligned MAX_KERNEL_WRITE=%d", fuse.MAX_KERNEL_WRITE) + } + c := &ContentEnc{ cryptoCore: cc, plainBS: plainBS, cipherBS: cipherBS, allZeroBlock: make([]byte, cipherBS), allZeroNonce: make([]byte, cc.IVLen), forceDecode: forceDecode, + cWriteSize: cWriteSize, } + c.cBlockPool.New = func() interface{} { return make([]byte, cipherBS) } + c.cWritePool.New = func() interface{} { return make([]byte, cWriteSize) } + return c +} + +// CWritePut puts "buf" back into the cWritePool. +func (be *ContentEnc) CWritePut(buf []byte) { + buf = buf[:cap(buf)] + if len(buf) != be.cWriteSize { + log.Panicf("wrong len=%d, want=%d", len(buf), be.cWriteSize) + } + be.cWritePool.Put(buf) } // PlainBS returns the plaintext block size @@ -185,12 +210,16 @@ func (be *ContentEnc) EncryptBlocks(plaintextBlocks [][]byte, firstBlockNo uint6 be.doEncryptBlocks(plaintextBlocks, ciphertextBlocks, firstBlockNo, fileID) } // Concatenate ciphertext into a single byte array. - // Size the output buffer for the maximum possible size (all blocks complete) - // to prevent further allocations in out.Write() - tmp := make([]byte, len(plaintextBlocks)*int(be.CipherBS())) + tmp := be.cWritePool.Get().([]byte) out := bytes.NewBuffer(tmp[:0]) for _, v := range ciphertextBlocks { out.Write(v) + // Return the memory to cBlockPool + cBlock := v[:cap(v)] + if len(cBlock) != int(be.cipherBS) { + log.Panicf("unexpected cBlock length: len=%d cipherBS=%d", len(cBlock), be.cipherBS) + } + be.cBlockPool.Put(cBlock) } return out.Bytes() } @@ -233,15 +262,22 @@ func (be *ContentEnc) doEncryptBlock(plaintext []byte, blockNo uint64, fileID [] if len(nonce) != be.cryptoCore.IVLen { log.Panic("wrong nonce length") } - - // Authenticate block with block number and file ID + // Block is authenticated with block number and file ID aData := make([]byte, 8) binary.BigEndian.PutUint64(aData, blockNo) aData = append(aData, fileID...) - + // Get a cipherBS-sized block of memory, copy the nonce into it and truncate to + // nonce length + cBlock := be.cBlockPool.Get().([]byte) + copy(cBlock, nonce) + cBlock = cBlock[0:len(nonce)] // Encrypt plaintext and append to nonce - ciphertext := be.cryptoCore.AEADCipher.Seal(nonce, nonce, plaintext, aData) - + ciphertext := be.cryptoCore.AEADCipher.Seal(cBlock, nonce, plaintext, aData) + overhead := int(be.cipherBS - be.plainBS) + if len(plaintext)+overhead != len(ciphertext) { + log.Panicf("unexpected ciphertext length: plaintext=%d, overhead=%d, ciphertext=%d", + len(plaintext), overhead, len(ciphertext)) + } return ciphertext } diff --git a/internal/fusefrontend/file.go b/internal/fusefrontend/file.go index 5b0a254..f70c9a5 100644 --- a/internal/fusefrontend/file.go +++ b/internal/fusefrontend/file.go @@ -311,6 +311,8 @@ func (f *file) doWrite(data []byte, off int64) (uint32, fuse.Status) { } // Write _, err = f.fd.WriteAt(ciphertext, cOff) + // Return memory to cWritePool + f.fs.contentEnc.CWritePut(ciphertext) if err != nil { tlog.Warn.Printf("doWrite: Write failed: %s", err.Error()) return 0, fuse.ToStatus(err)