From f6cb4d4fb66970c3b8e31b009db9a02e14cabae4 Mon Sep 17 00:00:00 2001 From: ValarDragon Date: Fri, 24 Aug 2018 10:18:17 -0700 Subject: [PATCH] Speedup IAVL iterator by removing defers when unneeded. Note each defer occurs a 30ish ns overhead here, which is significant for IAVL iteration. We were previously using around 3-4 defers. (2 in next, one in Valid, one in Key, one in Value) This slows down the entire application quite significantly, as we require fast iteration. --- Gopkg.lock | 53 +++++++++++++++++++++-------------------- PENDING.md | 1 + store/iavlstore.go | 35 +++++++++++++++++---------- store/iavlstore_test.go | 23 ++++++++++++++++++ 4 files changed, 73 insertions(+), 39 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 81591a0ae..fa6fafb99 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -34,11 +34,11 @@ [[projects]] branch = "master" - digest = "1:2c00f064ba355903866cbfbf3f7f4c0fe64af6638cc7d1b8bdcf3181bc67f1d8" + digest = "1:6aabc1566d6351115d561d038da82a4c19b46c3b6e17f4a0a2fa60260663dc79" name = "github.com/btcsuite/btcd" packages = ["btcec"] pruneopts = "UT" - revision = "f899737d7f2764dc13e4d01ff00108ec58f766a9" + revision = "d81d8877b8f327112e94e814937143a71d1692a7" [[projects]] digest = "1:386de157f7d19259a7f9c81f26ce011223ce0f090353c1152ffdf730d7d10ac2" @@ -71,7 +71,7 @@ version = "v1.4.7" [[projects]] - digest = "1:fdf5169073fb0ad6dc12a70c249145e30f4058647bea25f0abd48b6d9f228a11" + digest = "1:fa30c0652956e159cdb97dcb2ef8b8db63ed668c02a5c3a40961c8f0641252fe" name = "github.com/go-kit/kit" packages = [ "log", @@ -103,7 +103,7 @@ version = "v1.7.0" [[projects]] - digest = "1:35621fe20f140f05a0c4ef662c26c0ab4ee50bca78aa30fe87d33120bd28165e" + digest = "1:212285efb97b9ec2e20550d81f0446cb7897e57cbdfd7301b1363ab113d8be45" name = "github.com/gogo/protobuf" packages = [ "gogoproto", @@ -118,7 +118,7 @@ version = "v1.1.1" [[projects]] - digest = "1:17fe264ee908afc795734e8c4e63db2accabaf57326dbf21763a7d6b86096260" + digest = "1:cb22af0ed7c72d495d8be1106233ee553898950f15fd3f5404406d44c2e86888" name = "github.com/golang/protobuf" packages = [ "proto", @@ -165,12 +165,13 @@ [[projects]] branch = "master" - digest = "1:12247a2e99a060cc692f6680e5272c8adf0b8f572e6bce0d7095e624c958a240" + digest = "1:ac64f01acc5eeea9dde40e326de6b6471e501392ec06524c3b51033aa50789bc" name = "github.com/hashicorp/hcl" packages = [ ".", "hcl/ast", "hcl/parser", + "hcl/printer", "hcl/scanner", "hcl/strconv", "hcl/token", @@ -230,12 +231,12 @@ version = "v1.0.1" [[projects]] - branch = "master" - digest = "1:5ab79470a1d0fb19b041a624415612f8236b3c06070161a910562f2b2d064355" + digest = "1:645110e089152bd0f4a011a2648fbb0e4df5977be73ca605781157ac297f50c4" name = "github.com/mitchellh/mapstructure" packages = ["."] pruneopts = "UT" - revision = "f15292f7a699fcc1a38a80977f80a046874ba8ac" + revision = "fa473d140ef3c6adf42d6b391fe76707f1f243c8" + version = "v1.0.0" [[projects]] digest = "1:95741de3af260a92cc5c7f3f3061e85273f5a81b5db20d4bd68da74bd521675e" @@ -262,7 +263,7 @@ version = "v1.0.0" [[projects]] - digest = "1:c1a04665f9613e082e1209cf288bf64f4068dcd6c87a64bf1c4ff006ad422ba0" + digest = "1:98225904b7abff96c052b669b25788f18225a36673fba022fb93514bb9a2a64e" name = "github.com/prometheus/client_golang" packages = [ "prometheus", @@ -273,7 +274,7 @@ [[projects]] branch = "master" - digest = "1:2d5cd61daa5565187e1d96bae64dbbc6080dacf741448e9629c64fd93203b0d4" + digest = "1:0f37e09b3e92aaeda5991581311f8dbf38944b36a3edec61cc2d1991f527554a" name = "github.com/prometheus/client_model" packages = ["go"] pruneopts = "UT" @@ -281,7 +282,7 @@ [[projects]] branch = "master" - digest = "1:63b68062b8968092eb86bedc4e68894bd096ea6b24920faca8b9dcf451f54bb5" + digest = "1:dad2e5a2153ee7a6c9ab8fc13673a16ee4fb64434a7da980965a3741b0c981a3" name = "github.com/prometheus/common" packages = [ "expfmt", @@ -293,7 +294,7 @@ [[projects]] branch = "master" - digest = "1:8c49953a1414305f2ff5465147ee576dd705487c35b15918fcd4efdc0cb7a290" + digest = "1:a37c98f4b7a66bb5c539c0539f0915a74ef1c8e0b3b6f45735289d94cae92bfd" name = "github.com/prometheus/procfs" packages = [ ".", @@ -312,7 +313,7 @@ revision = "e2704e165165ec55d062f5919b4b29494e9fa790" [[projects]] - digest = "1:bd1ae00087d17c5a748660b8e89e1043e1e5479d0fea743352cda2f8dd8c4f84" + digest = "1:37ace7f35375adec11634126944bdc45a673415e2fcc07382d03b75ec76ea94c" name = "github.com/spf13/afero" packages = [ ".", @@ -331,7 +332,7 @@ version = "v1.2.0" [[projects]] - digest = "1:7ffc0983035bc7e297da3688d9fe19d60a420e9c38bef23f845c53788ed6a05e" + digest = "1:627ab2f549a6a55c44f46fa24a4307f4d0da81bfc7934ed0473bf38b24051d26" name = "github.com/spf13/cobra" packages = ["."] pruneopts = "UT" @@ -363,7 +364,7 @@ version = "v1.0.0" [[projects]] - digest = "1:7e8d267900c7fa7f35129a2a37596e38ed0f11ca746d6d9ba727980ee138f9f6" + digest = "1:73697231b93fb74a73ebd8384b68b9a60c57ea6b13c56d2425414566a72c8e6d" name = "github.com/stretchr/testify" packages = [ "assert", @@ -375,7 +376,7 @@ [[projects]] branch = "master" - digest = "1:f2ffd421680b0a3f7887501b3c6974bcf19217ecd301d0e2c9b681940ec363d5" + digest = "1:442d2ffa75ffae302ce8800bf4144696b92bef02917923ea132ce2d39efe7d65" name = "github.com/syndtr/goleveldb" packages = [ "leveldb", @@ -396,7 +397,7 @@ [[projects]] branch = "master" - digest = "1:087aaa7920e5d0bf79586feb57ce01c35c830396ab4392798112e8aae8c47722" + digest = "1:203b409c21115233a576f99e8f13d8e07ad82b25500491f7e1cca12588fb3232" name = "github.com/tendermint/ed25519" packages = [ ".", @@ -423,7 +424,7 @@ version = "v0.9.2" [[projects]] - digest = "1:4f15e95fe3888cc75dd34f407d6394cbc7fd3ff24920851b92b295f6a8b556e6" + digest = "1:963f6c04345ce36f900c1d6367200eebc3cc2db6ee632ff865ea8dcf64b748a0" name = "github.com/tendermint/tendermint" packages = [ "abci/client", @@ -490,7 +491,7 @@ version = "v0.23.1-rc0" [[projects]] - digest = "1:bf6d9a827ea3cad964c2f863302e4f6823170d0b5ed16f72cf1184a7c615067e" + digest = "1:ad879bb8c71020a3f92f0c61f414d93eae1d5dc2f37023b6abaa3cc84b00165e" name = "github.com/tendermint/tmlibs" packages = ["cli"] pruneopts = "UT" @@ -506,7 +507,7 @@ [[projects]] branch = "master" - digest = "1:27507554c6d4f060d8d700c31c624a43d3a92baa634e178ddc044bdf7d13b44a" + digest = "1:2a3ce1f08dcae8bac666deb6e4c88b5d7170c510da38fd746231144cac351704" name = "golang.org/x/crypto" packages = [ "blowfish", @@ -528,7 +529,7 @@ revision = "614d502a4dac94afa3a6ce146bd1736da82514c6" [[projects]] - digest = "1:d36f55a999540d29b6ea3c2ea29d71c76b1d9853fdcd3e5c5cb4836f2ba118f1" + digest = "1:04dda8391c3e2397daf254ac68003f30141c069b228d06baec8324a5f81dc1e9" name = "golang.org/x/net" packages = [ "context", @@ -545,17 +546,17 @@ [[projects]] branch = "master" - digest = "1:a0e12bc26f317c0e2d497baf767285e1790e526e8dd46553c5a67fcbc8692157" + digest = "1:c8baf78f0ac6eb27c645e264fe5e8a74d5a50db188ab41a7ff3b275c112e0735" name = "golang.org/x/sys" packages = [ "cpu", "unix", ] pruneopts = "UT" - revision = "3b58ed4ad3395d483fc92d5d14123ce2c3581fec" + revision = "11551d06cbcc94edc80a0facaccbda56473c19c1" [[projects]] - digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18" + digest = "1:7509ba4347d1f8de6ae9be8818b0cd1abc3deeffe28aeaf4be6d4b6b5178d9ca" name = "golang.org/x/text" packages = [ "collate", @@ -586,7 +587,7 @@ revision = "c66870c02cf823ceb633bcd05be3c7cda29976f4" [[projects]] - digest = "1:2dab32a43451e320e49608ff4542fdfc653c95dcc35d0065ec9c6c3dd540ed74" + digest = "1:4515e3030c440845b046354fd5d57671238428b820deebce2e9dabb5cd3c51ac" name = "google.golang.org/grpc" packages = [ ".", diff --git a/PENDING.md b/PENDING.md index 01bdc2158..426f368a3 100644 --- a/PENDING.md +++ b/PENDING.md @@ -59,6 +59,7 @@ IMPROVEMENTS * SDK * [tools] Make get_vendor_deps deletes `.vendor-new` directories, in case scratch files are present. * [cli] \#1632 Add integration tests to ensure `basecoind init && basecoind` start sequences run successfully for both `democoin` and `basecoin` examples. + * [store] Speedup IAVL iteration, and consequently everything that requires IAVL iteration. [#2143](https://github.com/cosmos/cosmos-sdk/issues/2143) * Tendermint diff --git a/store/iavlstore.go b/store/iavlstore.go index 6ab50dfc9..daffa7dd5 100644 --- a/store/iavlstore.go +++ b/store/iavlstore.go @@ -332,39 +332,42 @@ func (iter *iavlIterator) Domain() (start, end []byte) { func (iter *iavlIterator) Valid() bool { iter.waitInit() iter.mtx.Lock() - defer iter.mtx.Unlock() - return !iter.invalid + validity := !iter.invalid + iter.mtx.Unlock() + return validity } // Implements Iterator. func (iter *iavlIterator) Next() { iter.waitInit() iter.mtx.Lock() - defer iter.mtx.Unlock() - iter.assertIsValid() + iter.assertIsValid(true) iter.receiveNext() + iter.mtx.Unlock() } // Implements Iterator. func (iter *iavlIterator) Key() []byte { iter.waitInit() iter.mtx.Lock() - defer iter.mtx.Unlock() - iter.assertIsValid() + iter.assertIsValid(true) - return iter.key + key := iter.key + iter.mtx.Unlock() + return key } // Implements Iterator. func (iter *iavlIterator) Value() []byte { iter.waitInit() iter.mtx.Lock() - defer iter.mtx.Unlock() - iter.assertIsValid() + iter.assertIsValid(true) - return iter.value + val := iter.value + iter.mtx.Unlock() + return val } // Implements Iterator. @@ -375,14 +378,14 @@ func (iter *iavlIterator) Close() { //---------------------------------------- func (iter *iavlIterator) setNext(key, value []byte) { - iter.assertIsValid() + iter.assertIsValid(false) iter.key = key iter.value = value } func (iter *iavlIterator) setInvalid() { - iter.assertIsValid() + iter.assertIsValid(false) iter.invalid = true } @@ -400,8 +403,14 @@ func (iter *iavlIterator) receiveNext() { } } -func (iter *iavlIterator) assertIsValid() { +// assertIsValid panics if the iterator is invalid. If unlockMutex is true, +// it also unlocks the mutex before panicing, to prevent deadlocks in code that +// recovers from panics +func (iter *iavlIterator) assertIsValid(unlockMutex bool) { if iter.invalid { + if unlockMutex { + iter.mtx.Unlock() + } panic("invalid iterator") } } diff --git a/store/iavlstore_test.go b/store/iavlstore_test.go index 38d85c658..793089a26 100644 --- a/store/iavlstore_test.go +++ b/store/iavlstore_test.go @@ -464,3 +464,26 @@ func TestIAVLStoreQuery(t *testing.T) { require.Equal(t, uint32(sdk.CodeOK), qres.Code) require.Equal(t, v1, qres.Value) } + +func BenchmarkIAVLIteratorNext(b *testing.B) { + db := dbm.NewMemDB() + treeSize := 1000 + tree := iavl.NewVersionedTree(db, cacheSize) + for i := 0; i < treeSize; i++ { + key := cmn.RandBytes(4) + value := cmn.RandBytes(50) + tree.Set(key, value) + } + iavlStore := newIAVLStore(tree, numRecent, storeEvery) + iterators := make([]Iterator, b.N/treeSize) + for i := 0; i < len(iterators); i++ { + iterators[i] = iavlStore.Iterator([]byte{0}, []byte{255, 255, 255, 255, 255}) + } + b.ResetTimer() + for i := 0; i < len(iterators); i++ { + iter := iterators[i] + for j := 0; j < treeSize; j++ { + iter.Next() + } + } +}