@JeremyRubin found a performance regression introduced by b3b3c2a5623d5c942d2b3565cc2d833c65105555 which causes us to make copies of every transaction we connect to tip a while back, but no one had gotten around to fixing it yet. By switching to using shared_ptrs for blocks in several places we not only fix the performance regression but open up the possibility of switching GetMainSignals() callbacks to background threads much more easily.
Fix block-connection performance regression #9014
pull TheBlueMatt wants to merge 6 commits into bitcoin:master from TheBlueMatt:2016-10-fix-tx-regression changing 7 files +60 −40-
TheBlueMatt commented at 3:32 PM on October 25, 2016: member
-
in src/main.cpp:None in d0f3b60785 outdated
2798 | @@ -2799,20 +2799,40 @@ static int64_t nTimeFlush = 0; 2799 | static int64_t nTimeChainState = 0; 2800 | static int64_t nTimePostConnect = 0; 2801 | 2802 | +struct CompareBlockIndexByWork { 2803 | + bool operator()(const CBlockIndex* a, const CBlockIndex* b) const { 2804 | + return a->nChainWork < b->nChainWork;
sipa commented at 3:35 PM on October 25, 2016:Don't you need a fallback for when the chainwork is identical?
TheBlueMatt commented at 4:16 PM on October 25, 2016:Since its only used in each ActivateBestChainStep call individually, there is only ever one thing per given height/total work, but the map came out of an older version of the code and is now useless...so I'm replacing it with a vector.
in src/main.cpp:None in d0f3b60785 outdated
2830 | - if (!pblock) { 2831 | - if (!ReadBlockFromDisk(block, pindexNew, chainparams.GetConsensus())) 2832 | + const CBlock *pblock; 2833 | + auto it = blockTrace.mapBlocks.find(pindexNew); 2834 | + if (it == blockTrace.mapBlocks.end()) { 2835 | + CBlock *pblockNew = new CBlock();
JeremyRubin commented at 4:15 PM on October 25, 2016:nit: maybe do this as
it = blockTrace.mapBlocks.emplace(pindexNew, std::shared_ptr<const CBlock>(new CBlock())).first;Or
auto pblockNew = std::make_shared<CBlock>();and update following code accordingly
TheBlueMatt commented at 6:29 PM on October 25, 2016:The new version should be cleaner.
in src/main.cpp:None in d0f3b60785 outdated
6049 | @@ -6023,23 +6050,24 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, 6050 | 6051 | else if (strCommand == NetMsgType::BLOCK && !fImporting && !fReindex) // Ignore blocks received while importing 6052 | { 6053 | - CBlock block; 6054 | - vRecv >> block; 6055 | + CBlock *pblock = new CBlock();
JeremyRubin commented at 4:50 PM on October 25, 2016:See other comment about using make_shared
in src/rpc/mining.cpp:None in d0f3b60785 outdated
131 | @@ -132,7 +132,8 @@ UniValue generateBlocks(boost::shared_ptr<CReserveScript> coinbaseScript, int nG 132 | continue; 133 | } 134 | CValidationState state; 135 | - if (!ProcessNewBlock(state, Params(), NULL, pblock, true, NULL)) 136 | + std::shared_ptr<const CBlock> shared_pblock(new CBlock(*pblock));
JeremyRubin commented at 4:51 PM on October 25, 2016:make_shared
in src/rpc/mining.cpp:None in d0f3b60785 outdated
726 | @@ -726,7 +727,9 @@ UniValue submitblock(const JSONRPCRequest& request) 727 | + HelpExampleRpc("submitblock", "\"mydata\"") 728 | ); 729 | 730 | - CBlock block; 731 | + CBlock* blockptr = new CBlock();
JeremyRubin commented at 4:51 PM on October 25, 2016:make_shared
in src/test/miner_tests.cpp:None in d0f3b60785 outdated
222 | @@ -223,7 +223,8 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) 223 | pblock->hashMerkleRoot = BlockMerkleRoot(*pblock); 224 | pblock->nNonce = blockinfo[i].nonce; 225 | CValidationState state; 226 | - BOOST_CHECK(ProcessNewBlock(state, chainparams, NULL, pblock, true, NULL)); 227 | + std::shared_ptr<const CBlock> shared_pblock(new CBlock(*pblock));
JeremyRubin commented at 4:52 PM on October 25, 2016:make_shared
in src/test/test_bitcoin.cpp:None in d0f3b60785 outdated
126 | @@ -127,7 +127,8 @@ TestChain100Setup::CreateAndProcessBlock(const std::vector<CMutableTransaction>& 127 | while (!CheckProofOfWork(block.GetHash(), block.nBits, chainparams.GetConsensus())) ++block.nNonce; 128 | 129 | CValidationState state; 130 | - ProcessNewBlock(state, chainparams, NULL, &block, true, NULL); 131 | + std::shared_ptr<const CBlock> shared_pblock(new CBlock(block));
JeremyRubin commented at 4:52 PM on October 25, 2016:make_shared
in src/main.cpp:None in d0f3b60785 outdated
3765 | @@ -3740,7 +3766,7 @@ static bool AcceptBlock(const CBlock& block, CValidationState& state, const CCha 3766 | return true; 3767 | } 3768 | 3769 | -bool ProcessNewBlock(CValidationState& state, const CChainParams& chainparams, CNode* pfrom, const CBlock* pblock, bool fForceProcessing, const CDiskBlockPos* dbp) 3770 | +bool ProcessNewBlock(CValidationState& state, const CChainParams& chainparams, CNode* pfrom, std::shared_ptr<const CBlock>& pblock, bool fForceProcessing, const CDiskBlockPos* dbp)
JeremyRubin commented at 4:58 PM on October 25, 2016:Semantics of passing shared_ptr by reference are a bit wonky. I think it is correct in this use case though. Perhaps it would be better for maintainability to eat the performance cost of an atomic increment and pass by value, or pass by const reference (if possible?).
sipa commented at 1:32 AM on October 26, 2016:In my opinion, passing by reference is preferred at any time when you're sure the shared_ptr itself (not the object it points to) is only accessed from a single thread. The cost of the atomic increment is negligable here though, so I don't care in this particular case.
in src/main.cpp:None in d0f3b60785 outdated
5854 | @@ -5829,7 +5855,8 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, 5855 | BlockTransactions resp; 5856 | vRecv >> resp; 5857 | 5858 | - CBlock block; 5859 | + CBlock *pblock = new CBlock();
JeremyRubin commented at 4:58 PM on October 25, 2016:make_shared
TheBlueMatt commented at 5:13 PM on October 25, 2016:The shared_ptrs are to a const CBlock, and IIRC you cant blindly cast a shared_ptr<CBlock> to a shared_ptr<const CBlock>, but you need to access the block as non-const, and I kinda prefer this over a const_cast.
JeremyRubin commented at 5:32 PM on October 25, 2016:implicit copy-cast should work...
#include <memory> int main() { auto a = std::make_shared<int>(10); std::shared_ptr<const int> b = a; }
TheBlueMatt commented at 6:06 PM on October 25, 2016:Cool, didnt realize that was a thing!
in src/main.cpp:None in d0f3b60785 outdated
2808 | +// Used in ActivateBestChain/ConnectBlock to keep track of blocks connected 2809 | +// and notify listeners in the right order. 2810 | +// Note that since mapBlocks is sorted lowest-work to highest-work, iterating 2811 | +// over it will give you the order in which the blocks were connected 2812 | +struct BlockConnectTrace { 2813 | + std::map<CBlockIndex*, std::shared_ptr<const CBlock>, CompareBlockIndexByWork> mapBlocks;
JeremyRubin commented at 5:06 PM on October 25, 2016:I think it may be worthwhile for you to make this a class and keep the implementation details hidden...
TheBlueMatt commented at 6:29 PM on October 25, 2016:I added txConflicted here to make diffs of individual commits easier, which now requires a lot of access to the contents directly :/ Not sure its worth it.
JeremyRubin commented at 5:10 PM on October 25, 2016: contributorutACK
couple things that could be cleaned up, but I think this is the right approach for this and is OK as is, certainly better than txChanged.
TheBlueMatt force-pushed on Oct 25, 2016TheBlueMatt commented at 6:30 PM on October 25, 2016: memberOK, ended up rewriting the PR quite a bit, cleaning it up and resolving the nits folks had raised - it now no longer has this crazy map thing that can be used as both a paramter and a return value.
TheBlueMatt force-pushed on Oct 25, 2016TheBlueMatt force-pushed on Oct 25, 2016TheBlueMatt force-pushed on Oct 25, 2016JeremyRubin commented at 7:26 PM on October 25, 2016: contributorre-utack, much cleaner!
TheBlueMatt force-pushed on Oct 25, 2016in src/main.cpp:None in 34d0c884d6 outdated
2798 | @@ -2799,11 +2799,18 @@ static int64_t nTimeFlush = 0; 2799 | static int64_t nTimeChainState = 0; 2800 | static int64_t nTimePostConnect = 0; 2801 | 2802 | +// Used to track conflicted transactions removed from mempool and transactions
sipa commented at 1:06 AM on October 26, 2016:Can you use a doxygen compatible comment here?
in src/main.cpp:None in a0d30f80f3 outdated
2974 | @@ -2974,6 +2975,8 @@ static bool ActivateBestChainStep(CValidationState& state, const CChainParams& c 2975 | state = CValidationState(); 2976 | fInvalidFound = true; 2977 | fContinue = false; 2978 | + // If we didn't actually connect the block, don't notify listeners about it
sipa commented at 1:17 AM on October 26, 2016:This logic needs to be duplicated in the disconnect logic above. It's possible that an invalid block is received which triggers a reorg. So we start from chain A-B, and receive A-C-D, with D invalid. We'll first disconnect B, then successfully connect C, but fail when connecting D. We'll loop back then, disconnect C, and reconnect B. Your code will (I believe) report both C and B as newly connected, while it should be nothing at all.
Perhaps it's easier to do a filtering step in ActivateBestChain that removes the errorneously blocks in the trace.
TheBlueMatt commented at 3:22 AM on October 26, 2016:Hmm? I dont believe so? Note that the callbacks in question are called from ABC after each ABCS call, and might happen for blocks which will be reorged out in the next ABCS call (this is the existing behavior).
sipa commented at 4:44 AM on November 23, 2016:Ok (answered elsewhere).
in src/main.cpp:None in 051b9968cf outdated
2805 | @@ -2806,11 +2806,13 @@ struct ConnectTrace { 2806 | std::vector<std::pair<CBlockIndex*, std::shared_ptr<const CBlock> > > blocksConnected; 2807 | }; 2808 | 2809 | +
sipa commented at 1:22 AM on October 26, 2016:Unintentional new lines?
in src/main.cpp:None in 5095970c97 outdated
5840 | @@ -5846,7 +5841,8 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, 5841 | BlockTransactions resp; 5842 | vRecv >> resp; 5843 | 5844 | - CBlock block; 5845 | + CBlock *pblock = new CBlock(); 5846 | + std::shared_ptr<const CBlock> shared_pblock(pblock); // pblock will be delted by the shared_ptr
sipa commented at 3:07 AM on October 26, 2016:Typo: deleted
TheBlueMatt force-pushed on Oct 26, 2016in src/main.cpp:None in 3f78bc176a outdated
2975 | if (!state.CorruptionPossible()) 2976 | InvalidChainFound(vpindexToConnect.back()); 2977 | state = CValidationState(); 2978 | fInvalidFound = true; 2979 | fContinue = false; 2980 | + // If we didn't actually connect the block, don't notify listeners about it
sipa commented at 4:27 AM on October 26, 2016:No, cs_main is held during the entirety of the attempted reorg here, and the callbacks only fire after the whole thing (I think).
TheBlueMatt commented at 2:24 PM on October 26, 2016:I'm very confused. ActivateBestChain has always locked cs_main, called ActivateBestChainStep once, and then unlocked cs_main and made callbacks before repeating. In the old code we pushed transactions to update onto the callback queue at the end of ConnectTip and never removed them (since there will never be a DisconnectTip call within the same cs_main in ActivateBestChainStep since all DisconnectTips happen before ConnectTip).
sipa commented at 5:21 PM on October 26, 2016:It seems that failed reorgs are an exception to what I said before, so ignore what I said. I think we should fix this (it could lead to GBT working on the forking point of a failed reorg), but that's outside of the scope here.
laanwj added the label Validation on Oct 26, 2016sipa commented at 3:40 PM on October 26, 2016: memberBefore headers first, reorgs were dealt with by accumulating the changes in another cache level, which was only flushed if the reorg succeeded.
Now there is much less a concept of reorgs, and only attempts to improve the tip. ABC finds what block we want and like to be the tip, and calls ABCS to make progress towards that. In case of failure, we find what other block we'd like to become the tip (which may be the previous one), and make progress towards that. cs_main is only released once we're in a strictly better state than what we started off in, as otherwise you'd briefly expose the forking point to GBT during a reorg.
EDIT: see #9014 (review) - I don't think there is an issue for this PR.
in src/main.cpp:None in 3f78bc176a outdated
2821 | - CBlock block; 2822 | if (!pblock) { 2823 | - if (!ReadBlockFromDisk(block, pindexNew, chainparams.GetConsensus())) 2824 | + std::shared_ptr<CBlock> pblockNew = std::make_shared<CBlock>(); 2825 | + connectTrace.blocksConnected.emplace_back(pindexNew, pblockNew); 2826 | + if (!ReadBlockFromDisk(*pblockNew, pindexNew, chainparams.GetConsensus()))
sipa commented at 5:29 PM on October 26, 2016:Ultranit: if you swap this line with the one above, you can pass std::move(pblockNew) to emplace_back, avoiding an atomic increment + decrement when pblockNew goes out of scope.
TheBlueMatt commented at 6:20 PM on October 26, 2016:No, because pblockNew is a std::shared_ptr<CBlock> not an std::shared_ptr<const CBlock>, so you must call the std::shared_ptr constructor to convert it.
sipa commented at 6:28 PM on October 26, 2016:I see!
JeremyRubin commented at 6:49 PM on October 26, 2016:(if anyone else gets confused, Matt meant std::shared_ptr not std::atomic above)
sipa commented at 5:57 PM on October 26, 2016: memberutACK 3f78bc176a3c612f320bce4516aadcd6234c0850. My nit is just a suggestion.
sipa commented at 10:26 PM on November 7, 2016: memberNeeds rebase.
TheBlueMatt force-pushed on Nov 7, 2016TheBlueMatt commented at 10:48 PM on November 7, 2016: memberRebased.
fanquake commented at 12:29 AM on November 8, 2016: memberTravis failures:
CXX libbitcoin_server_a-rest.o ../../src/main.cpp: In function ‘bool ProcessMessage(CNode*, std::string, CDataStream&, int64_t, const CChainParams&, CConnman&)’: ../../src/main.cpp:6094:52: error: ‘block’ was not declared in this scope forceProcessing |= MarkBlockAsReceived(block.GetHash());CXX libbitcoin_server_a-torcontrol.o ../../src/main.cpp: In function ‘bool ProcessMessage(CNode*, std::string, CDataStream&, int64_t, const CChainParams&, CConnman&)’: ../../src/main.cpp:6094:52: error: ‘block’ was not declared in this scope forceProcessing |= MarkBlockAsReceived(block.GetHash());../../src/main.cpp: In function ‘bool ProcessMessage(CNode*, std::string, CDataStream&, int64_t, const CChainParams&, CConnman&)’: ../../src/main.cpp:6094:52: error: ‘block’ was not declared in this scope forceProcessing |= MarkBlockAsReceived(block.GetHash());paveljanik commented at 7:14 AM on November 8, 2016: contributorLooks like this is not rebased correctly - GH shows 5 conflicting files (and they match my own tests).
TheBlueMatt commented at 1:19 AM on November 9, 2016: memberIt just needs rebased again, closing for now and will rebase on top of #9075 after that gets merged.
TheBlueMatt closed this on Nov 9, 2016TheBlueMatt reopened this on Nov 17, 2016TheBlueMatt force-pushed on Nov 17, 2016TheBlueMatt commented at 11:28 PM on November 17, 2016: memberRebased on #9075.
TheBlueMatt force-pushed on Nov 22, 2016TheBlueMatt commented at 2:51 AM on November 22, 2016: memberRebased after #9125 was merged. Note that #9125 should have fixed most of the performance regression here (and was likely the source of its major reorg speedup, though it should help in more real-world conditions as well), though this should still help some, and is a good step towards #9027 as well as improving callback concurrency in the future.
TheBlueMatt force-pushed on Nov 23, 2016in src/main.cpp:None in 7b717cdf7f outdated
3126 | // ... and about transactions that got confirmed: 3127 | - for (unsigned int i = 0; i < txChanged.size(); i++) 3128 | - GetMainSignals().SyncTransaction(*std::get<0>(txChanged[i]), std::get<1>(txChanged[i]), std::get<2>(txChanged[i])); 3129 | + for (const auto& pair : connectTrace.blocksConnected) { 3130 | + assert(pair.second); 3131 | + const CBlock& block = *(pair.second);
sipa commented at 5:01 AM on November 23, 2016:This loop could be rewritten as
for (const auto& ptx : pair.second->vtx) GetMainSignals().SyncTransaction(*ptx, pair.first, i);sipa commented at 5:02 AM on November 23, 2016: memberutACK
gmaxwell commented at 8:59 PM on November 24, 2016: contributorutACK
in src/main.cpp:None in eb33aa1412 outdated
3018 | @@ -3018,6 +3019,8 @@ static bool ActivateBestChainStep(CValidationState& state, const CChainParams& c 3019 | state = CValidationState(); 3020 | fInvalidFound = true; 3021 | fContinue = false; 3022 | + // If we didn't actually connect the block, don't notify listeners about it 3023 | + connectTrace.blocksConnected.pop_back();
sdaftuar commented at 2:13 AM on November 29, 2016:nit: slightly weird that the caller of ConnectTip() has to know to do this on failures. Perhaps document this somewhere?
TheBlueMatt commented at 5:26 AM on November 30, 2016:Fixed
sdaftuar commented at 2:21 AM on November 29, 2016: memberCode review ACK, will test.
sdaftuar commented at 10:33 PM on November 29, 2016: memberACK 7b717cd
Add struct to track block-connect-time-generated info for callbacks 6fdd43b968Keep blocks as shared_ptrs, instead of copying txn in ConnectTip fd9d89070aCreate a shared_ptr for the block we're connecting in ActivateBCS ae4db44d03Make the optional pblock in ActivateBestChain a shared_ptr 2736c44c8e2d6e5619afSwitch pblock in ProcessNewBlock to a shared_ptr
This (finally) fixes a performance regression in b3b3c2a5623d5c942d2b3565cc2d833c65105555
Document ConnectBlock connectTrace postconditions dd0df81ebdTheBlueMatt force-pushed on Dec 4, 2016TheBlueMatt commented at 8:21 AM on December 4, 2016: memberRebased after #9260.
reinier1991 approvedlaanwj merged this on Dec 5, 2016laanwj closed this on Dec 5, 2016laanwj referenced this in commit d04aebaec7 on Dec 5, 2016codablock referenced this in commit c4d15c113a on Jan 16, 2018codablock referenced this in commit fdbb9b6501 on Jan 16, 2018codablock referenced this in commit b5389c09b3 on Jan 17, 2018gladcow referenced this in commit 59ec8eafa9 on Mar 5, 2018gladcow referenced this in commit ad83a65b02 on Mar 8, 2018gladcow referenced this in commit 60f18b7806 on Mar 13, 2018gladcow referenced this in commit aea4ba0e85 on Mar 14, 2018gladcow referenced this in commit 9fe17f4666 on Mar 15, 2018gladcow referenced this in commit a14a312856 on Mar 15, 2018gladcow referenced this in commit 37aa7dc04f on Mar 15, 2018gladcow referenced this in commit 1f5b5c562a on Mar 15, 2018gladcow referenced this in commit f3a4879534 on Mar 24, 2018gladcow referenced this in commit ca694e861a on Apr 4, 2018UdjinM6 referenced this in commit bc45a2f87a on Apr 11, 2018andvgal referenced this in commit c8b7bc1a78 on Jan 6, 2019andvgal referenced this in commit fd5c50bc2b on Jan 6, 2019CryptoCentric referenced this in commit b13b4f0860 on Feb 25, 2019CryptoCentric referenced this in commit dd3fd51204 on Feb 28, 2019furszy referenced this in commit 8a9b92c0ed on Oct 17, 2020DrahtBot locked this on Sep 8, 2021
github-metadata-mirror
This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-04-22 09:15 UTC
This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me