diff --git a/arc_cache_test.go b/arc_cache_test.go index a15ff2d..64f45df 100644 --- a/arc_cache_test.go +++ b/arc_cache_test.go @@ -2,7 +2,11 @@ package blockstore import ( "context" + "io" + "math/rand" + "sync/atomic" "testing" + "time" blocks "github.com/ipfs/go-block-format" cid "github.com/ipfs/go-cid" @@ -26,7 +30,7 @@ func testArcCached(ctx context.Context, bs Blockstore) (*arccache, error) { return nil, err } -func createStores(t *testing.T) (*arccache, Blockstore, *callbackDatastore) { +func createStores(t testing.TB) (*arccache, Blockstore, *callbackDatastore) { cd := &callbackDatastore{f: func() {}, ds: ds.NewMapDatastore()} bs := NewBlockstore(syncds.MutexWrap(cd)) arc, err := testArcCached(context.TODO(), bs) @@ -275,5 +279,120 @@ func TestPutManyCaches(t *testing.T) { trap("PunMany has hit datastore", cd, t) arc.PutMany([]blocks.Block{exampleBlock}) }) +} + +func BenchmarkARCCacheConcurrentOps(b *testing.B) { + // ~4k blocks seems high enough to be realistic, + // but low enough to cause collisions. + // Keep it as a power of 2, to simplify code below. + const numBlocks = 4 << 10 + + dummyBlocks := make([]blocks.Block, numBlocks) + + { + // scope dummyRand to prevent its unsafe concurrent use below + dummyRand := rand.New(rand.NewSource(time.Now().UnixNano())) + for i := range dummyBlocks { + dummy := make([]byte, 32) + if _, err := io.ReadFull(dummyRand, dummy); err != nil { + b.Fatal(err) + } + dummyBlocks[i] = blocks.NewBlock(dummy) + } + } + + // Each test begins with half the blocks present in the cache. + // This allows test cases to have both hits and misses, + // regardless of whether or not they do Puts. + putHalfBlocks := func(arc *arccache) { + for i, block := range dummyBlocks { + if i%2 == 0 { + if err := arc.Put(block); err != nil { + b.Fatal(err) + } + } + } + } + + // We always mix just two operations at a time. + const numOps = 2 + var testOps = []struct { + name string + ops [numOps]func(*arccache, blocks.Block) + }{ + {"PutDelete", [...]func(*arccache, blocks.Block){ + func(arc *arccache, block blocks.Block) { + arc.Put(block) + }, + func(arc *arccache, block blocks.Block) { + arc.DeleteBlock(block.Cid()) + }, + }}, + {"GetDelete", [...]func(*arccache, blocks.Block){ + func(arc *arccache, block blocks.Block) { + arc.Get(block.Cid()) + }, + func(arc *arccache, block blocks.Block) { + arc.DeleteBlock(block.Cid()) + }, + }}, + {"GetPut", [...]func(*arccache, blocks.Block){ + func(arc *arccache, block blocks.Block) { + arc.Get(block.Cid()) + }, + func(arc *arccache, block blocks.Block) { + arc.Put(block) + }, + }}, + } + for _, test := range testOps { + test := test // prevent reuse of the range var + b.Run(test.name, func(b *testing.B) { + arc, _, _ := createStores(b) + putHalfBlocks(arc) + var opCounts [numOps]uint64 + + b.ResetTimer() + b.ReportAllocs() + + b.RunParallel(func(pb *testing.PB) { + rnd := rand.New(rand.NewSource(time.Now().UnixNano())) + for pb.Next() { + n := rnd.Int63() + blockIdx := n % numBlocks // lower bits decide the block + opIdx := (n / numBlocks) % numOps // higher bits decide what operation + + block := dummyBlocks[blockIdx] + op := test.ops[opIdx] + op(arc, block) + + atomic.AddUint64(&opCounts[opIdx], 1) + } + }) + + // We expect each op to fire roughly an equal amount of times. + // Error otherwise, as that likely means the logic is wrong. + var minIdx, maxIdx int + var minCount, maxCount uint64 + for opIdx, count := range opCounts { + if minCount == 0 || count < minCount { + minIdx = opIdx + minCount = count + } + if maxCount == 0 || count > maxCount { + maxIdx = opIdx + maxCount = count + } + } + // Skip this check if we ran few times, to avoid false positives. + if maxCount > 100 { + ratio := float64(maxCount) / float64(minCount) + if maxRatio := 2.0; ratio > maxRatio { + b.Fatalf("op %d ran %fx as many times as %d", maxIdx, ratio, minIdx) + } + } + + }) + } } diff --git a/go.sum b/go.sum index cc30010..6dcd339 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,3 @@ -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -12,11 +11,9 @@ github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= github.com/ipfs/go-block-format v0.0.3 h1:r8t66QstRp/pd/or4dpnbVfXT5Gt7lOqRvC+/dDTpMc= github.com/ipfs/go-block-format v0.0.3/go.mod h1:4LmD4ZUw0mhO+JSKdpWwrzATiEfM7WWgQ8H5l6P8MVk= -github.com/ipfs/go-cid v0.0.5 h1:o0Ix8e/ql7Zb5UVUJEUfjsWCIY8t48++9lR8qi6oiJU= github.com/ipfs/go-cid v0.0.5/go.mod h1:plgt+Y5MnOey4vO4UlUazGqdbEXuFYitED67FexhXog= github.com/ipfs/go-cid v0.0.7 h1:ysQJVJA3fNDF1qigJbsSQOdjhVLsOEoPdh0+R97k3jY= github.com/ipfs/go-cid v0.0.7/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= -github.com/ipfs/go-datastore v0.4.1 h1:W4ZfzyhNi3xmuU5dQhjfuRn/wFuqEE1KnOmmQiOevEY= github.com/ipfs/go-datastore v0.4.1/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= github.com/ipfs/go-datastore v0.4.2 h1:h8/n7WPzhp239kkLws+epN3Ic7YtcBPgcaXfEfdVDWM= github.com/ipfs/go-datastore v0.4.2/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= @@ -46,7 +43,6 @@ github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0 github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771 h1:MHkK1uRtFbVqvAgvWxafZe54+5uBxLluGylDiKgdhwo= github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= -github.com/mr-tron/base58 v1.1.0 h1:Y51FGVJ91WBqCEabAi5OPUz38eAx8DakuAm5svLcsfQ= github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= github.com/mr-tron/base58 v1.1.3 h1:v+sk57XuaCKGXpWtVBX8YJzO7hMGx4Aajh4TQbdEFdc= github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= @@ -54,11 +50,9 @@ github.com/multiformats/go-base32 v0.0.3 h1:tw5+NhuwaOjJCC5Pp82QuXbrmLzWg7uxlMFp github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA= github.com/multiformats/go-base36 v0.1.0 h1:JR6TyF7JjGd3m6FbLU2cOxhC0Li8z8dLNGQ89tUg4F4= github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM= -github.com/multiformats/go-multibase v0.0.1 h1:PN9/v21eLywrFWdFNsFKaU04kLJzuYzmrJR+ubhT9qA= github.com/multiformats/go-multibase v0.0.1/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/gviWFaSteVbWT51qgs= github.com/multiformats/go-multibase v0.0.3 h1:l/B6bJDQjvQ5G52jw4QGSYeOTZoAwIO77RblWplfIqk= github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc= -github.com/multiformats/go-multihash v0.0.13 h1:06x+mk/zj1FoMsgNejLpy6QTvJqlSt/BhLEy87zidlc= github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= github.com/multiformats/go-multihash v0.0.14 h1:QoBceQYQQtNUuf6s7wHxnE2c8bhbMqhfGzNI032se/I= github.com/multiformats/go-multihash v0.0.14/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= @@ -82,16 +76,13 @@ golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 h1:1wopBVtVdWnn03fZelqdXT golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/net v0.0.0-20190227160552-c95aed5357e7 h1:C2F/nMkR/9sfUTpvR3QrjBuTdvMUC/cFajkphs1YLQo= golang.org/x/net v0.0.0-20190227160552-c95aed5357e7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=