Add AjwernerBtree as an alternative btree backing for piece request order

Also add some scans to benchmarks. Make a few changes to reduce allocations using piece request order.
This commit is contained in:
Matt Joiner 2021-12-18 16:07:44 +11:00
parent c54dc22592
commit 934944e498
8 changed files with 156 additions and 67 deletions

3
go.mod
View File

@ -4,6 +4,7 @@ go 1.18
require ( require (
github.com/RoaringBitmap/roaring v0.9.4 github.com/RoaringBitmap/roaring v0.9.4
github.com/ajwerner/btree v0.0.0-20211201061316-91c8b66ad617
github.com/alexflint/go-arg v1.4.2 github.com/alexflint/go-arg v1.4.2
github.com/anacrolix/args v0.4.1-0.20211104085705-59f0fe94eb8f github.com/anacrolix/args v0.4.1-0.20211104085705-59f0fe94eb8f
github.com/anacrolix/chansync v0.3.0 github.com/anacrolix/chansync v0.3.0
@ -15,7 +16,7 @@ require (
github.com/anacrolix/missinggo v1.3.0 github.com/anacrolix/missinggo v1.3.0
github.com/anacrolix/missinggo/perf v1.0.0 github.com/anacrolix/missinggo/perf v1.0.0
github.com/anacrolix/missinggo/v2 v2.5.2 github.com/anacrolix/missinggo/v2 v2.5.2
github.com/anacrolix/multiless v0.2.0 github.com/anacrolix/multiless v0.2.1-0.20211218050420-533661eef5dc
github.com/anacrolix/squirrel v0.4.0 github.com/anacrolix/squirrel v0.4.0
github.com/anacrolix/sync v0.4.0 github.com/anacrolix/sync v0.4.0
github.com/anacrolix/tagflag v1.3.0 github.com/anacrolix/tagflag v1.3.0

6
go.sum
View File

@ -12,6 +12,8 @@ github.com/RoaringBitmap/roaring v0.9.4 h1:ckvZSX5gwCRaJYBNe7syNawCU5oruY9gQmjXl
github.com/RoaringBitmap/roaring v0.9.4/go.mod h1:icnadbWcNyfEHlYdr+tDlOTih1Bf/h+rzPpv4sbomAA= github.com/RoaringBitmap/roaring v0.9.4/go.mod h1:icnadbWcNyfEHlYdr+tDlOTih1Bf/h+rzPpv4sbomAA=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/ajwerner/btree v0.0.0-20211201061316-91c8b66ad617 h1:sxP5D87Mq99SZMHhYBmq1yY4AAVkfNY5Wn02B9crYs0=
github.com/ajwerner/btree v0.0.0-20211201061316-91c8b66ad617/go.mod h1:q37NoqncT41qKc048STsifIt69LfUJ8SrWWcz/yam5k=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@ -59,8 +61,8 @@ github.com/anacrolix/missinggo/v2 v2.5.2/go.mod h1:yNvsLrtZYRYCOI+KRH/JM8TodHjtI
github.com/anacrolix/mmsg v0.0.0-20180515031531-a4a3ba1fc8bb/go.mod h1:x2/ErsYUmT77kezS63+wzZp8E3byYB0gzirM/WMBLfw= github.com/anacrolix/mmsg v0.0.0-20180515031531-a4a3ba1fc8bb/go.mod h1:x2/ErsYUmT77kezS63+wzZp8E3byYB0gzirM/WMBLfw=
github.com/anacrolix/mmsg v1.0.0 h1:btC7YLjOn29aTUAExJiVUhQOuf/8rhm+/nWCMAnL3Hg= github.com/anacrolix/mmsg v1.0.0 h1:btC7YLjOn29aTUAExJiVUhQOuf/8rhm+/nWCMAnL3Hg=
github.com/anacrolix/mmsg v1.0.0/go.mod h1:x8kRaJY/dCrY9Al0PEcj1mb/uFHwP6GCJ9fLl4thEPc= github.com/anacrolix/mmsg v1.0.0/go.mod h1:x8kRaJY/dCrY9Al0PEcj1mb/uFHwP6GCJ9fLl4thEPc=
github.com/anacrolix/multiless v0.2.0 h1:HtGBBOQcHaJM59RP3ysITId7AMIgiNF4xJucaFh14Ms= github.com/anacrolix/multiless v0.2.1-0.20211218050420-533661eef5dc h1:K047jUtd0Xv4SEpv/5DoBgDvj4ZNpT1SOVtMlFpRrh0=
github.com/anacrolix/multiless v0.2.0/go.mod h1:TrCLEZfIDbMVfLoQt5tOoiBS/uq4y8+ojuEVVvTNPX4= github.com/anacrolix/multiless v0.2.1-0.20211218050420-533661eef5dc/go.mod h1:TrCLEZfIDbMVfLoQt5tOoiBS/uq4y8+ojuEVVvTNPX4=
github.com/anacrolix/squirrel v0.4.0 h1:MOxYwh0mD2rcEJT+N2tFj2Z3dBDrWn6kRxP4qsBxfyk= github.com/anacrolix/squirrel v0.4.0 h1:MOxYwh0mD2rcEJT+N2tFj2Z3dBDrWn6kRxP4qsBxfyk=
github.com/anacrolix/squirrel v0.4.0/go.mod h1:dJyE7VefQvX0KAKMkOQDGOVEs91a+LvXQ2BjEKl/DIw= github.com/anacrolix/squirrel v0.4.0/go.mod h1:dJyE7VefQvX0KAKMkOQDGOVEs91a+LvXQ2BjEKl/DIw=
github.com/anacrolix/stm v0.2.0/go.mod h1:zoVQRvSiGjGoTmbM0vSLIiaKjWtNPeTvXUSdJQA4hsg= github.com/anacrolix/stm v0.2.0/go.mod h1:zoVQRvSiGjGoTmbM0vSLIiaKjWtNPeTvXUSdJQA4hsg=

View File

@ -0,0 +1,44 @@
package request_strategy
import (
"github.com/ajwerner/btree"
)
type ajwernerBtree struct {
btree btree.Set[pieceRequestOrderItem]
}
var _ Btree = (*ajwernerBtree)(nil)
func NewAjwernerBtree() *ajwernerBtree {
return &ajwernerBtree{
btree: btree.MakeSet(func(t, t2 pieceRequestOrderItem) int {
return pieceOrderLess(&t, &t2).OrderingInt()
}),
}
}
func mustValue[V any](b bool, panicValue V) {
if !b {
panic(panicValue)
}
}
func (a *ajwernerBtree) Delete(item pieceRequestOrderItem) {
mustValue(a.btree.Delete(item), item)
}
func (a *ajwernerBtree) Add(item pieceRequestOrderItem) {
_, overwrote := a.btree.Upsert(item)
mustValue(!overwrote, item)
}
func (a *ajwernerBtree) Scan(f func(pieceRequestOrderItem) bool) {
it := a.btree.Iterator()
it.First()
for it.First(); it.Valid(); it.Next() {
if !f(it.Cur()) {
break
}
}
}

View File

@ -51,11 +51,7 @@ func GetRequestablePieces(input Input, pro *PieceRequestOrder, f func(ih metainf
storageLeft = &cap storageLeft = &cap
} }
var allTorrentsUnverifiedBytes int64 var allTorrentsUnverifiedBytes int64
min, ok := pro.tree.Min() pro.tree.Scan(func(_i pieceRequestOrderItem) bool {
if !ok {
return
}
pro.tree.Ascend(min, func(_i pieceRequestOrderItem) bool {
ih := _i.key.InfoHash ih := _i.key.InfoHash
var t Torrent = input.Torrent(ih) var t Torrent = input.Torrent(ih)
var piece Piece = t.Piece(_i.key.Index) var piece Piece = t.Piece(_i.key.Index)

View File

@ -1,27 +1,23 @@
package request_strategy package request_strategy
import ( import "github.com/anacrolix/torrent/metainfo"
"fmt"
"github.com/anacrolix/torrent/metainfo" type Btree interface {
"github.com/tidwall/btree" Delete(pieceRequestOrderItem)
) Add(pieceRequestOrderItem)
Scan(func(pieceRequestOrderItem) bool)
}
func NewPieceOrder() *PieceRequestOrder { func NewPieceOrder(btree Btree, cap int) *PieceRequestOrder {
return &PieceRequestOrder{ return &PieceRequestOrder{
tree: btree.NewOptions( tree: btree,
func(a, b pieceRequestOrderItem) bool { keys: make(map[PieceRequestOrderKey]PieceRequestOrderState, cap),
return a.Less(&b)
},
btree.Options{NoLocks: true}),
keys: make(map[PieceRequestOrderKey]PieceRequestOrderState),
} }
} }
type PieceRequestOrder struct { type PieceRequestOrder struct {
tree *btree.BTree[pieceRequestOrderItem] tree Btree
keys map[PieceRequestOrderKey]PieceRequestOrderState keys map[PieceRequestOrderKey]PieceRequestOrderState
PathHint *btree.PathHint
} }
type PieceRequestOrderKey struct { type PieceRequestOrderKey struct {
@ -48,17 +44,10 @@ func (me *PieceRequestOrder) Add(key PieceRequestOrderKey, state PieceRequestOrd
if _, ok := me.keys[key]; ok { if _, ok := me.keys[key]; ok {
panic(key) panic(key)
} }
if _, ok := me.tree.SetHint(pieceRequestOrderItem{ me.tree.Add(pieceRequestOrderItem{key, state})
key: key,
state: state,
}, me.PathHint); ok {
panic("shouldn't already have this")
}
me.keys[key] = state me.keys[key] = state
} }
type PieceRequestOrderPathHint = btree.PathHint
func (me *PieceRequestOrder) Update( func (me *PieceRequestOrder) Update(
key PieceRequestOrderKey, key PieceRequestOrderKey,
state PieceRequestOrderState, state PieceRequestOrderState,
@ -70,17 +59,8 @@ func (me *PieceRequestOrder) Update(
if state == oldState { if state == oldState {
return return
} }
item := pieceRequestOrderItem{ me.tree.Delete(pieceRequestOrderItem{key, oldState})
key: key, me.tree.Add(pieceRequestOrderItem{key, state})
state: oldState,
}
if _, ok := me.tree.DeleteHint(item, me.PathHint); !ok {
panic(fmt.Sprintf("%#v", key))
}
item.state = state
if _, ok := me.tree.SetHint(item, me.PathHint); ok {
panic(key)
}
me.keys[key] = state me.keys[key] = state
} }
@ -92,10 +72,7 @@ func (me *PieceRequestOrder) existingItemForKey(key PieceRequestOrderKey) pieceR
} }
func (me *PieceRequestOrder) Delete(key PieceRequestOrderKey) { func (me *PieceRequestOrder) Delete(key PieceRequestOrderKey) {
item := me.existingItemForKey(key) me.tree.Delete(pieceRequestOrderItem{key, me.keys[key]})
if _, ok := me.tree.DeleteHint(item, me.PathHint); !ok {
panic(key)
}
delete(me.keys, key) delete(me.keys, key)
} }

View File

@ -6,22 +6,25 @@ import (
"github.com/bradfitz/iter" "github.com/bradfitz/iter"
) )
func benchmarkPieceRequestOrder( func benchmarkPieceRequestOrder[B Btree](
b *testing.B, b *testing.B,
hintForPiece func(index int) *PieceRequestOrderPathHint, // Initialize the next run, and return a Btree
newBtree func() B,
// Set any path hinting for the specified piece
hintForPiece func(index int),
numPieces int, numPieces int,
) { ) {
b.ResetTimer() b.ResetTimer()
b.ReportAllocs() b.ReportAllocs()
for range iter.N(b.N) { for range iter.N(b.N) {
pro := NewPieceOrder() pro := NewPieceOrder(newBtree(), numPieces)
state := PieceRequestOrderState{} state := PieceRequestOrderState{}
doPieces := func(m func(PieceRequestOrderKey)) { doPieces := func(m func(PieceRequestOrderKey)) {
for i := range iter.N(numPieces) { for i := range iter.N(numPieces) {
key := PieceRequestOrderKey{ key := PieceRequestOrderKey{
Index: i, Index: i,
} }
pro.PathHint = hintForPiece(i) hintForPiece(i)
m(key) m(key)
} }
} }
@ -32,15 +35,24 @@ func benchmarkPieceRequestOrder(
doPieces(func(key PieceRequestOrderKey) { doPieces(func(key PieceRequestOrderKey) {
pro.Update(key, state) pro.Update(key, state)
}) })
pro.tree.Scan(func(item pieceRequestOrderItem) bool {
return true
})
doPieces(func(key PieceRequestOrderKey) { doPieces(func(key PieceRequestOrderKey) {
state.Priority = piecePriority(key.Index / 4) state.Priority = piecePriority(key.Index / 4)
pro.Update(key, state) pro.Update(key, state)
}) })
// state.Priority = 0 pro.tree.Scan(func(item pieceRequestOrderItem) bool {
return item.key.Index < 1000
})
state.Priority = 0
state.Availability++ state.Availability++
doPieces(func(key PieceRequestOrderKey) { doPieces(func(key PieceRequestOrderKey) {
pro.Update(key, state) pro.Update(key, state)
}) })
pro.tree.Scan(func(item pieceRequestOrderItem) bool {
return item.key.Index < 1000
})
state.Availability-- state.Availability--
doPieces(func(key PieceRequestOrderKey) { doPieces(func(key PieceRequestOrderKey) {
pro.Update(key, state) pro.Update(key, state)
@ -52,23 +64,43 @@ func benchmarkPieceRequestOrder(
} }
} }
func zero[T any](t *T) {
var zt T
*t = zt
}
func BenchmarkPieceRequestOrder(b *testing.B) { func BenchmarkPieceRequestOrder(b *testing.B) {
const numPieces = 2000 const numPieces = 2000
b.Run("TidwallBtree", func(b *testing.B) {
b.Run("NoPathHints", func(b *testing.B) { b.Run("NoPathHints", func(b *testing.B) {
benchmarkPieceRequestOrder(b, func(int) *PieceRequestOrderPathHint { benchmarkPieceRequestOrder(b, NewTidwallBtree, func(int) {}, numPieces)
return nil
}, numPieces)
}) })
b.Run("SharedPathHint", func(b *testing.B) { b.Run("SharedPathHint", func(b *testing.B) {
var pathHint PieceRequestOrderPathHint var pathHint PieceRequestOrderPathHint
benchmarkPieceRequestOrder(b, func(int) *PieceRequestOrderPathHint { var btree *tidwallBtree
return &pathHint benchmarkPieceRequestOrder(
}, numPieces) b, func() *tidwallBtree {
zero(&pathHint)
btree = NewTidwallBtree()
btree.PathHint = &pathHint
return btree
}, func(int) {}, numPieces,
)
}) })
b.Run("PathHintPerPiece", func(b *testing.B) { b.Run("PathHintPerPiece", func(b *testing.B) {
pathHints := make([]PieceRequestOrderPathHint, numPieces) pathHints := make([]PieceRequestOrderPathHint, numPieces)
benchmarkPieceRequestOrder(b, func(index int) *PieceRequestOrderPathHint { var btree *tidwallBtree
return &pathHints[index] benchmarkPieceRequestOrder(
}, numPieces) b, func() *tidwallBtree {
btree = NewTidwallBtree()
return btree
}, func(index int) {
btree.PathHint = &pathHints[index]
}, numPieces,
)
})
})
b.Run("AjwernerBtree", func(b *testing.B) {
benchmarkPieceRequestOrder(b, NewAjwernerBtree, func(index int) {}, numPieces)
}) })
} }

View File

@ -0,0 +1,37 @@
package request_strategy
import (
"github.com/tidwall/btree"
)
type tidwallBtree struct {
tree *btree.BTree[pieceRequestOrderItem]
PathHint *btree.PathHint
}
func (me *tidwallBtree) Scan(f func(pieceRequestOrderItem) bool) {
me.tree.Scan(f)
}
func NewTidwallBtree() *tidwallBtree {
return &tidwallBtree{
tree: btree.NewOptions(
func(a, b pieceRequestOrderItem) bool {
return a.Less(&b)
},
btree.Options{NoLocks: true}),
}
}
func (me *tidwallBtree) Add(item pieceRequestOrderItem) {
if _, ok := me.tree.SetHint(item, me.PathHint); ok {
panic("shouldn't already have this")
}
}
type PieceRequestOrderPathHint = btree.PathHint
func (me *tidwallBtree) Delete(item pieceRequestOrderItem) {
_, deleted := me.tree.DeleteHint(item, me.PathHint)
mustValue(deleted, item)
}

View File

@ -45,7 +45,7 @@ func (t *Torrent) initPieceRequestOrder() {
key := t.clientPieceRequestOrderKey() key := t.clientPieceRequestOrderKey()
cpro := t.cl.pieceRequestOrder cpro := t.cl.pieceRequestOrder
if cpro[key] == nil { if cpro[key] == nil {
cpro[key] = request_strategy.NewPieceOrder() cpro[key] = request_strategy.NewPieceOrder(request_strategy.NewTidwallBtree(), t.numPieces())
} }
} }