Skip to content

Commit

Permalink
Use fully random input (#167)
Browse files Browse the repository at this point in the history
Instead of using a 10MiB circular buffer with random data,
use an infinite stream that is encrypted continuously.

We use a smaller circular buffer as input, but repeats should not be present.

Provides good solid speed, per thread:

```
BenchmarkWithRandomData/64KB-32         	   83332	     14328 ns/op	4573.91 MB/s	      72 B/op	       4 allocs/op
BenchmarkWithRandomData/1MB-32          	    5216	    222968 ns/op	4702.82 MB/s	      72 B/op	       4 allocs/op
BenchmarkWithRandomData/10MB-32         	     542	   2223248 ns/op	4716.42 MB/s	      72 B/op	       4 allocs/op
BenchmarkWithRandomData/1GB-32          	       1	2272999700 ns/op	4723.90 MB/s	      80 B/op	       4 allocs/op
```
  • Loading branch information
klauspost authored Feb 22, 2021
1 parent b3ae7ec commit 5d66f54
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 48 deletions.
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ require (
github.com/minio/minio v0.0.0-20201102034248-d8e07f2c41c8
github.com/minio/minio-go/v7 v7.0.6
github.com/posener/complete v1.2.3
github.com/secure-io/sio-go v0.3.0
github.com/secure-io/sio-go v0.3.1
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 // indirect
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43 // indirect
)
9 changes: 9 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,8 @@ github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFo
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
github.com/secure-io/sio-go v0.3.0 h1:QKGb6rGJeiExac9wSWxnWPYo8O8OFN7lxXQvHshX6vo=
github.com/secure-io/sio-go v0.3.0/go.mod h1:D3KmXgKETffyYxBdFRN+Hpd2WzhzqS0EQwT3XWsAcBU=
github.com/secure-io/sio-go v0.3.1 h1:dNvY9awjabXTYGsTF1PiCySl9Ltofk9GA3VdWlo7rRc=
github.com/secure-io/sio-go v0.3.1/go.mod h1:+xbkjDzPjwh4Axd07pRKSNriS9SCiYksWnZqdnfpQxs=
github.com/shirou/gopsutil v2.20.3-0.20200314133625-53cec6b37e6a+incompatible h1:YiKUe2ZOmfpDBH4OSyxwkx/mjNqHHnNhOtZ2mPyRme8=
github.com/shirou/gopsutil v2.20.3-0.20200314133625-53cec6b37e6a+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
Expand Down Expand Up @@ -402,13 +404,16 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 h1:DZhuSZLsGlFL4CmhA8BcRA0mnthyA/nZ00AqCUo7vHg=
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee h1:4yd7jl+vXjalO5ztz6Vc1VADv+S/80LGJmyl1ROJ2AI=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
Expand Down Expand Up @@ -469,6 +474,7 @@ golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae h1:Ih9Yo4hSPImZOpfGuA4bR/ORKTAbhZo2AbWNRCnevdo=
Expand All @@ -477,6 +483,9 @@ golang.org/x/sys v0.0.0-20200915084602-288bc346aa39/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201013132646-2da7054afaeb h1:HS9IzC4UFbpMBLQUDSQcU+ViVT1vdFCQVjdPVpTlZrs=
golang.org/x/sys v0.0.0-20201013132646-2da7054afaeb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43 h1:SgQ6LNaYJU0JIuEHv9+s6EbhSCwYeAf5Yvj6lpYlqAE=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
Expand Down
9 changes: 6 additions & 3 deletions pkg/generator/generator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,10 @@ func BenchmarkWithRandomData(b *testing.B) {
name: "10MB",
args: args{opts: []Option{WithSize(10 << 20), WithRandomData().Apply()}},
},
{
name: "1GB",
args: args{opts: []Option{WithSize(10 << 30), WithRandomData().Apply()}},
},
}
for _, tt := range tests {
b.Run(tt.name, func(b *testing.B) {
Expand All @@ -202,13 +206,12 @@ func BenchmarkWithRandomData(b *testing.B) {
return
}
obj := got.Object()
payload, err := ioutil.ReadAll(obj.Reader)
n, err := io.Copy(ioutil.Discard, obj.Reader)
if err != nil {
b.Errorf("ioutil error = %v", err)
return
}
b.SetBytes(int64(len(payload)))
//ioutil.WriteFile(tt.name+".bin", payload, os.ModePerm)
b.SetBytes(n)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
Expand Down
55 changes: 11 additions & 44 deletions pkg/generator/random.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,11 @@
package generator

import (
"bytes"
"crypto/aes"
"crypto/cipher"
"errors"
"fmt"
"io"
"math/rand"
"sync/atomic"

"github.com/secure-io/sio-go"
)

func WithRandomData() RandomOpts {
Expand Down Expand Up @@ -75,18 +70,16 @@ type RandomOpts struct {
func randomOptsDefaults() RandomOpts {
return RandomOpts{
seed: nil,
// 10 MB before we wrap around.
size: 10 << 20,
// Use 128KB as base.
size: 128 << 10,
}
}

type randomSrc struct {
counter uint64
o Options
buf *circularBuffer
buf *scrambler
rng *rand.Rand
randSrc [16]byte
databuf *bytes.Reader
obj Object
}

Expand All @@ -104,16 +97,17 @@ func newRandom(o Options) (Source, error) {
if size <= 0 {
return nil, fmt.Errorf("size must be >= 0, got %d", size)
}

// Seed with random data.
data := make([]byte, size)
_, err := io.ReadFull(rng, data)
if err != nil {
return nil, err
}
r := randomSrc{
o: o,
databuf: bytes.NewReader(data),
rng: rng,
buf: newCircularBuffer(data, o.totalSize),
o: o,
rng: rng,
buf: newScrambler(data, o.totalSize, rng),
obj: Object{
Reader: nil,
Name: "",
Expand All @@ -131,44 +125,17 @@ func (r *randomSrc) Object() *Object {
randASCIIBytes(nBuf[:], r.rng)
r.obj.Size = r.o.getSize(r.rng)
r.obj.setName(fmt.Sprintf("%d.%s.rnd", atomic.LoadUint64(&r.counter), string(nBuf[:])))
data := r.buf.data
if int64(len(data)) > r.obj.Size {
data = data[:r.obj.Size]
}

if len(data) < 128 {
_, err := io.ReadFull(r.rng, data)
if err != nil {
panic(err)
}
r.obj.Reader = r.buf.Reset(r.obj.Size)
return &r.obj
}

_, err := io.ReadFull(r.rng, r.randSrc[:])
if err != nil {
panic(err)
}

// Scramble data
block, _ := aes.NewCipher(r.randSrc[:])
gcm, _ := cipher.NewGCM(block)
stream := sio.NewStream(gcm, sio.BufSize)
r.databuf.Reset(data)
rr := stream.EncryptReader(r.databuf, r.randSrc[:stream.NonceSize()], nil)
_, err = io.ReadFull(rr, data)
if err != nil {
panic(err)
}
// Reset scrambler
r.obj.Reader = r.buf.Reset(r.obj.Size)
return &r.obj
}

func (r *randomSrc) String() string {
if r.o.randSize {
return fmt.Sprintf("Random data; random size up to %d bytes, %d byte buffer", r.o.totalSize, len(r.buf.data))
return fmt.Sprintf("Random data; random size up to %d bytes", r.o.totalSize)
}
return fmt.Sprintf("Random data; %d bytes total, %d byte buffer", r.buf.want, len(r.buf.data))
return fmt.Sprintf("Random data; %d bytes total", r.buf.want)
}

func (r *randomSrc) Prefix() string {
Expand Down
124 changes: 124 additions & 0 deletions pkg/generator/scambler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* Warp (C) 2021 MinIO, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package generator

import (
"crypto/aes"
"crypto/cipher"
"errors"
"io"
"math"
"math/rand"

"github.com/secure-io/sio-go"
)

type scrambler struct {
// The total number of bytes to return
want int64
// Number of bytes read
read int64
// Data source
stream *sio.EncReader
}

// Reset will reset the scrambler.
// The number of bytes to return can be specified.
// If the number of bytes wanted is <= 0 the value will not be updated.
func (c *scrambler) Reset(want int64) io.ReadSeeker {
if want > 0 {
c.want = want
}
c.read = 0
return c
}

// Implement seeker compatible circular buffer,
// implemented for minio-go to allow retries.
func (c *scrambler) Seek(offset int64, whence int) (n int64, err error) {
// Switch through whence.
switch whence {
default:
return 0, errors.New("circularBuffer.Seek: invalid whence")
case io.SeekStart:
if offset > c.want {
return 0, io.EOF
}
c.read = offset
case io.SeekCurrent:
if offset+c.read > c.want {
return 0, io.EOF
}
c.read += offset
case io.SeekEnd:
if offset > 0 {
return 0, io.EOF
}
if c.want+offset < 0 {
return 0, io.ErrShortBuffer
}
c.read = c.want + offset
}
if c.read < 0 {
return 0, errors.New("circularBuffer.Seek: negative position")
}
return c.read, nil
}

// newCircularBuffer a reader that will produce (virtually) infinitely amounts of random data.
func newScrambler(data []byte, size int64, rng *rand.Rand) *scrambler {
var randSrc [16]byte

_, err := io.ReadFull(rng, randSrc[:])
if err != nil {
panic(err)
}
rand.New(rng).Read(randSrc[:])
block, _ := aes.NewCipher(randSrc[:])
gcm, _ := cipher.NewGCM(block)
stream := sio.NewStream(gcm, sio.BufSize)

return &scrambler{
want: size,
read: 0,
stream: stream.EncryptReader(newCircularBuffer(data, math.MaxInt64), randSrc[:stream.NonceSize()], nil),
}
}

func (c *scrambler) Read(p []byte) (n int, err error) {
remain := c.want - c.read
if remain <= 0 {
if remain != 0 {
panic(remain)
}
return n, io.EOF
}
// Make sure we don't overread.
toDo := len(p)
if int64(toDo) > remain {
p = p[:remain]
}
copied, err := io.ReadFull(c.stream, p)
// Assign remaining back to c.left
p = p[copied:]
c.read += int64(copied)
if c.read == c.want {
return copied, io.EOF
}
return copied, nil
}

0 comments on commit 5d66f54

Please sign in to comment.