Skip to content

Commit ffb98f7

Browse files
lukechampineFiloSottile
authored andcommitted
xts: reduce tweak allocations
The call to k2.Encrypt causes tweak to escape to the heap, resulting in a 16-byte allocation for each call to Encrypt/Decrypt. Moving tweak into the Cipher struct would allow it to be reused, but this is ruled out by the Cipher docstring, which states that it is safe for concurrent use. Instead, manage tweak arrays with a sync.Pool. Benchmarks indicate that this amortizes allocation cost without impacting performance. benchmark old ns/op new ns/op delta BenchmarkXTS-4 234 245 +4.70% benchmark old allocs new allocs delta BenchmarkXTS-4 2 0 -100.00% benchmark old bytes new bytes delta BenchmarkXTS-4 32 0 -100.00% Change-Id: I5e0dd8c2e1a1078a151bbeb1d0760936b6b56216 GitHub-Last-Rev: 14d81f5 GitHub-Pull-Request: #51 Reviewed-on: https://go-review.googlesource.com/c/118535 Reviewed-by: Filippo Valsorda <[email protected]> Run-TryBot: Filippo Valsorda <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent 6635ad9 commit ffb98f7

File tree

2 files changed

+39
-6
lines changed

2 files changed

+39
-6
lines changed

Diff for: xts/xts.go

+23-6
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,13 @@ import (
2727
"crypto/cipher"
2828
"encoding/binary"
2929
"errors"
30+
"sync"
3031

3132
"golang.org/x/crypto/internal/subtle"
3233
)
3334

34-
// Cipher contains an expanded key structure. It doesn't contain mutable state
35-
// and therefore can be used concurrently.
35+
// Cipher contains an expanded key structure. It is safe for concurrent use if
36+
// the underlying block cipher is safe for concurrent use.
3637
type Cipher struct {
3738
k1, k2 cipher.Block
3839
}
@@ -41,6 +42,12 @@ type Cipher struct {
4142
// only defined for 16-byte ciphers.
4243
const blockSize = 16
4344

45+
var tweakPool = sync.Pool{
46+
New: func() interface{} {
47+
return new([blockSize]byte)
48+
},
49+
}
50+
4451
// NewCipher creates a Cipher given a function for creating the underlying
4552
// block cipher (which must have a block size of 16 bytes). The key must be
4653
// twice the length of the underlying cipher's key.
@@ -72,7 +79,10 @@ func (c *Cipher) Encrypt(ciphertext, plaintext []byte, sectorNum uint64) {
7279
panic("xts: invalid buffer overlap")
7380
}
7481

75-
var tweak [blockSize]byte
82+
tweak := tweakPool.Get().(*[blockSize]byte)
83+
for i := range tweak {
84+
tweak[i] = 0
85+
}
7686
binary.LittleEndian.PutUint64(tweak[:8], sectorNum)
7787

7888
c.k2.Encrypt(tweak[:], tweak[:])
@@ -88,8 +98,10 @@ func (c *Cipher) Encrypt(ciphertext, plaintext []byte, sectorNum uint64) {
8898
plaintext = plaintext[blockSize:]
8999
ciphertext = ciphertext[blockSize:]
90100

91-
mul2(&tweak)
101+
mul2(tweak)
92102
}
103+
104+
tweakPool.Put(tweak)
93105
}
94106

95107
// Decrypt decrypts a sector of ciphertext and puts the result into plaintext.
@@ -106,7 +118,10 @@ func (c *Cipher) Decrypt(plaintext, ciphertext []byte, sectorNum uint64) {
106118
panic("xts: invalid buffer overlap")
107119
}
108120

109-
var tweak [blockSize]byte
121+
tweak := tweakPool.Get().(*[blockSize]byte)
122+
for i := range tweak {
123+
tweak[i] = 0
124+
}
110125
binary.LittleEndian.PutUint64(tweak[:8], sectorNum)
111126

112127
c.k2.Encrypt(tweak[:], tweak[:])
@@ -122,8 +137,10 @@ func (c *Cipher) Decrypt(plaintext, ciphertext []byte, sectorNum uint64) {
122137
plaintext = plaintext[blockSize:]
123138
ciphertext = ciphertext[blockSize:]
124139

125-
mul2(&tweak)
140+
mul2(tweak)
126141
}
142+
143+
tweakPool.Put(tweak)
127144
}
128145

129146
// mul2 multiplies tweak by 2 in GF(2¹²⁸) with an irreducible polynomial of

Diff for: xts/xts_test.go

+16
Original file line numberDiff line numberDiff line change
@@ -103,3 +103,19 @@ func TestShorterCiphertext(t *testing.T) {
103103
t.Errorf("En/Decryption is not inverse")
104104
}
105105
}
106+
107+
func BenchmarkXTS(b *testing.B) {
108+
b.ReportAllocs()
109+
c, err := NewCipher(aes.NewCipher, make([]byte, 32))
110+
if err != nil {
111+
b.Fatalf("NewCipher failed: %s", err)
112+
}
113+
plaintext := make([]byte, 32)
114+
encrypted := make([]byte, 48)
115+
decrypted := make([]byte, 48)
116+
117+
for i := 0; i < b.N; i++ {
118+
c.Encrypt(encrypted, plaintext, 0)
119+
c.Decrypt(decrypted, encrypted[:len(plaintext)], 0)
120+
}
121+
}

0 commit comments

Comments
 (0)