From e52594dae67a5f2532a2e65abaafc8c993fb2db4 Mon Sep 17 00:00:00 2001 From: Jakob Unterwurzacher Date: Sun, 11 Jun 2017 13:34:46 +0200 Subject: [PATCH] contentenc: parallelize encryption for 128kiB writes 128kiB = 32 x 4kiB pages is the maximum we get from the kernel. Splitting up smaller writes is probably not worth it. Parallelism is limited to two for now. --- internal/contentenc/content.go | 36 +++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/internal/contentenc/content.go b/internal/contentenc/content.go index 3643f56..3f578e8 100644 --- a/internal/contentenc/content.go +++ b/internal/contentenc/content.go @@ -7,6 +7,8 @@ import ( "encoding/hex" "errors" "log" + "runtime" + "sync" "github.com/rfjakob/gocryptfs/internal/cryptocore" "github.com/rfjakob/gocryptfs/internal/stupidgcm" @@ -149,14 +151,42 @@ func (be *ContentEnc) DecryptBlock(ciphertext []byte, blockNo uint64, fileID []b return plaintext, nil } +// At some point, splitting the ciphertext into more groups will not improve +// performance, as spawning goroutines comes at a cost. +// 2 seems to work ok for now. +const encryptMaxSplit = 2 + // EncryptBlocks is like EncryptBlock but takes multiple plaintext blocks. func (be *ContentEnc) EncryptBlocks(plaintextBlocks [][]byte, firstBlockNo uint64, fileID []byte) []byte { - // Encrypt piecewise. This allows easy parallization in the future. ciphertextBlocks := make([][]byte, len(plaintextBlocks)) - be.doEncryptBlocks(plaintextBlocks, ciphertextBlocks, firstBlockNo, fileID) + // For large writes, we parallelize encryption. + if len(plaintextBlocks) >= 32 { + ncpu := runtime.NumCPU() + if ncpu > encryptMaxSplit { + ncpu = encryptMaxSplit + } + groupSize := len(plaintextBlocks) / ncpu + var wg sync.WaitGroup + for i := 0; i < ncpu; i++ { + wg.Add(1) + go func(i int) { + low := i * groupSize + high := (i + 1) * groupSize + if i == ncpu-1 { + // Last group, pick up any left-over blocks + high = len(plaintextBlocks) + } + be.doEncryptBlocks(plaintextBlocks[low:high], ciphertextBlocks[low:high], firstBlockNo+uint64(low), fileID) + wg.Done() + }(i) + } + wg.Wait() + } else { + 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 allocations in out.Write() + // to prevent further allocations in out.Write() tmp := make([]byte, len(plaintextBlocks)*int(be.CipherBS())) out := bytes.NewBuffer(tmp[:0]) for _, v := range ciphertextBlocks {