Accurately account for mempool index memory #18086

pull sipa wants to merge 12 commits into bitcoin:master from sipa:201910_accounting_allocator changing 7 files +166 −38
  1. sipa commented at 2:21 am on February 7, 2020: member

    This introduces a C++ allocator class (memusage::AccountingAllocator) which enables containers that accurately account for all their memory allocations in a tracker variable.

    This is then used to replace the heuristics in the mempool to guess memory usage.

  2. fanquake added the label Mempool on Feb 7, 2020
  3. fanquake added the label Resource usage on Feb 7, 2020
  4. fanquake requested review from JeremyRubin on Feb 7, 2020
  5. sipa commented at 2:23 am on February 7, 2020: member
    @sdaftuar You may be interested in this for #18044 - it should be easy to use this to determine how much an extra index adds.
  6. sipa force-pushed on Feb 7, 2020
  7. DrahtBot commented at 3:07 am on February 7, 2020: contributor

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #19478 (Remove CTxMempool::mapLinks data structure member by JeremyRubin)
    • #19306 (refactor: Replace RecursiveMutex with Mutex in CTxMemPool by hebasto)
    • #18191 (Change UpdateForDescendants to use Epochs by JeremyRubin)
    • #18044 (Use wtxid for transaction relay by sdaftuar)
    • #18017 (txmempool: split epoch logic into class by ajtowns)
    • #10443 (Add fee_est tool for debugging fee estimation code by ryanofsky)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

  8. JeremyRubin commented at 3:26 am on February 7, 2020: contributor

    @sipa can you take a look at https://github.com/JeremyRubin/bitcoin/pull/7?

    It looks like when we get rid of mapTxLinks we’d then want to use an accounting allocator on the new child sets and get some DynamicMemoryUsage added to the cached inner usage?

    At some point I also want to better break down the cachedInnerUsage tracking into buckets as it makes it easier to trace bugs.

  9. sipa force-pushed on Feb 7, 2020
  10. sipa commented at 3:45 am on February 7, 2020: member

    I’m happy to rebase this on top of a removal of mapLinks, if that’d go in first. It looks pretty like I’d just need to remove mapLinks from this as well.

    I don’t know if breaking down cachedInnerUsage is all that meaningful, as the Check() function can verify its correctness in aggregate.

    There are plenty more options of shuffling things with AccountingAllocators… I don’t have any particular preference apart from minimizing code changes.

  11. JeremyRubin commented at 3:53 am on February 7, 2020: contributor
    Don’t you need to make TxLinks sets use accounting allocators in this patch too?
  12. sipa commented at 4:04 am on February 7, 2020: member
    @JeremyRubin Ah, that seems reasonable. My priority was the things that use a boost multiindex as guessing memory usage there is very hard, but no reason why it can’t be extended to also the sets there.
  13. JeremyRubin commented at 4:45 am on February 7, 2020: contributor
    Do you know if we were over or underestimating before?
  14. sipa commented at 4:47 am on February 7, 2020: member
    In mapTx we were overestimating slightly for realistic mempool size, but underestimating for near-empty ones.
  15. JeremyRubin commented at 5:16 am on February 7, 2020: contributor

    Ok the near empty case doesn’t really matter… w.r.t. the realistic size one, here’s a question just to be detailed/pedantic with what this change does:

    Given that we were previously overestimating, if I set my mempool to 100MB then I actually was getting (let’s say) 99 MB, if the “slightly” was by 1%. If after this change, I am now actually using a little bit more memory is this enough to be concerned? E.g., are we going to see a bunch of raspberry pi users complaining of OOMs?

  16. sipa commented at 5:33 am on February 7, 2020: member

    Our memory accounting has never been more accurate than in the orders of 10s of MBs. A tiny change like this shouldn’t matter.

    And probably on the long term, it helps: it’s likely the case that in some scenarios we were off more than others (e.g. many tiny transactions vs a few big ones) - better estimations means that experimentally-determined max memory limits need less margin.

  17. JeremyRubin commented at 5:57 am on February 7, 2020: contributor

    The mempool tests happen to be very fragile – I’ve noticed that sometimes when I made the mempool more memory efficient (e.g., in removing mapLinks) there are a bunch of failures because the tests depend on evictions happening because it’s out of space.

    I’m not positive that’s why your tests are failing, but it could be related.

  18. sipa commented at 6:02 am on February 7, 2020: member
    Yes, they’re failing because the mempool limits now work slightly differently. The test makes rather silly assumptions on the menory usage of transaction entries.
  19. sipa force-pushed on Feb 11, 2020
  20. DrahtBot added the label Needs rebase on Mar 5, 2020
  21. laanwj commented at 3:33 pm on March 27, 2020: member
    Concept ACK, I think this is an elegant way to track accurate usage.
  22. hebasto commented at 5:40 am on May 3, 2020: member
    Concept ACK.
  23. in src/memusage.h:35 in 77637b8d1e outdated
    31+ * memory.
    32+ */
    33+template<typename T>
    34+class AccountingAllocator : public std::allocator<T>
    35+{
    36+    size_t* m_allocated; //!< Pointer to accounting variable, cannot be nullptr
    


    hebasto commented at 4:34 pm on May 14, 2020:
    Shouldn’t be documented here that the accounting variable must be atomic or protected by mutex?

    sipa commented at 11:04 pm on July 8, 2020:
    I’ve added a bunch of comments.
  24. hebasto commented at 5:46 pm on May 14, 2020: member

    In mapTx we were overestimating slightly for realistic mempool size, but underestimating for near-empty ones.

    As unit tests are based on “near-empty” containers the difference of estimations seems broke them.

    Testing ed1c5b1b98a19b6751d739f86cdc305bb3cff283 with mempool_tests/MempoolSizeLimitTest: the difference in CTxMemPool::DynamicMemoryUsage() is ~700 bytes.

  25. hebasto commented at 2:59 pm on May 15, 2020: member
    Mind looking into https://github.com/hebasto/bitcoin/commits/test18086 ? In that branch the first two commits eliminate the influence of memory allocated amount for the empty memory pool in unit tests. Then all the unit and functional tests passed.
  26. sipa force-pushed on Jul 8, 2020
  27. sipa commented at 11:08 pm on July 8, 2020: member

    @hebasto That looks great. I’ve included your test/annotation improvements.

    I also added some comments, and made a change so that when a copy of a container is made, that copy is unaccounted. I think this makes sense conceptually (copies tend to be short-lived and not account for permanent storage), as well as reduce locking issues (otherwise you’d always need a lock on the original accounting variable when modifying the copy…).

  28. DrahtBot removed the label Needs rebase on Jul 8, 2020
  29. sipa force-pushed on Jul 9, 2020
  30. sipa force-pushed on Jul 9, 2020
  31. in src/memusage.h:62 in 65fc229fab outdated
    45+    //! Default constructor constructs a non-accounting allocator.
    46+    AccountingAllocator() noexcept : m_allocated(nullptr) {}
    47+
    48+    /** Construct an allocator that increments/decrements 'allocated' on allocate/free.
    49+     *
    50+     * In a multithreaded environment, the variable needs to be protected by the same
    


    hebasto commented at 8:41 am on July 9, 2020:

    65fc229fabbc7889baca9c50ee35507543a6682e

    0     * In a multithreaded environment, the accounting variable needs to be protected by the same
    
  32. in src/memusage.h:32 in 65fc229fab outdated
    24@@ -25,6 +25,64 @@ namespace memusage
    25 /** Compute the total memory used by allocating alloc bytes. */
    26 static size_t MallocUsage(size_t alloc);
    27 
    28+/** Wrapping allocator that accounts accurately.
    29+ *
    30+ * To use accounting, the AccountAllocator(size_t&) constructor must be used.
    31+ * Any containers using such an allocator, and containers moved constructed or
    32+ * assigned from such containers will then increment/decrement size_t when
    


    hebasto commented at 8:45 am on July 9, 2020:

    65fc229fabbc7889baca9c50ee35507543a6682e nit:

    0 * Any containers using such an allocator, and containers move constructed or
    1 * move assigned from such containers will then increment/decrement size_t when
    
  33. in src/test/allocator_tests.cpp:256 in 65fc229fab outdated
    251+            container2.insert(3);
    252+            container2.erase(2);
    253+            BOOST_CHECK(total > 0);
    254+            size_t old = total;
    255+            container1 = std::move(container2); // container1 is now accounted for in 'total'
    256+            BOOST_CHECK(total >= old);
    


    hebasto commented at 12:44 pm on July 9, 2020:

    65fc229fabbc7889baca9c50ee35507543a6682e

    0            BOOST_CHECK(total == old);
    
  34. in src/test/allocator_tests.cpp:248 in 65fc229fab outdated
    243+    {
    244+        typedef std::unordered_set<int, std::hash<int>, std::equal_to<int>, memusage::AccountingAllocator<int>> container_type;
    245+        container_type container1({}, 2, std::hash<int>(), std::equal_to<int>(), memusage::AccountingAllocator<int>(dummy)); // container1 is accounted for in 'dummy'
    246+        container1.insert(6);
    247+        {
    248+            container_type container2(1, std::hash<int>(), std::equal_to<int>(), memusage::AccountingAllocator<int>(total)); // container2 is accounted for in 'total'
    


    hebasto commented at 12:46 pm on July 9, 2020:
    65fc229fabbc7889baca9c50ee35507543a6682e nit - dedicated constructors could be used:
  35. hebasto changes_requested
  36. sipa force-pushed on Jul 9, 2020
  37. hebasto commented at 5:32 am on July 10, 2020: member

    From the IRC discussion:

    <cfields> sipa: I suppose the old gcc issue is that the propagate_on_container_* aren’t respected ? <sipa> cfields: possibly <sipa> but it looks like that <cfields> sipa: from libstdc++ 4.8 release docs “23.2.1 General container requirements Partial Only vector and forward_list meet the requirements relating to allocator use and propagation.” <sipa> cfields: ah <cfields> sipa: if it makes you feel any better, looks like support for all standard containers didn’t exist until 6.x. <sipa> cfields: seems i may just need to give up on the no-propagate-on-copy, and add a big disclaimer

  38. in src/memusage.h:67 in fc0ba65125 outdated
    50+     * In a multithreaded environment, the variable needs to be protected by the same
    51+     * lock as the container(s) that use this allocator. */
    52+    explicit AccountingAllocator(size_t& allocated) noexcept : m_allocated(&allocated) {}
    53+
    54+    //! A copy-constructed container will be non-accounting.
    55+    static AccountingAllocator select_on_container_copy_construction() { return AccountingAllocator(); }
    


    hebasto commented at 6:50 am on July 10, 2020:

    fc0ba65125ccc0fe8bf7e897c6f928ae5ae565e7 From the docs it follows that:

    0    AccountingAllocator select_on_container_copy_construction() const { return AccountingAllocator(); }
    
  39. test: make mempool_tests/MempoolSizeLimitTest allocation-neutral 0c03c6155e
  40. test: make validation_flush_tests/getcoinscachesizestate allocation-neutral f48ba0b038
  41. Add AccountingAllocator to help with memusage tracking 8bad146c65
  42. Make AccountingAllocator not inherent from std::allocator eace374a00
  43. Accurate mapTx counting 073e60cfe3
  44. Account mapDeltas accurately 277d2efe0f
  45. Account mapLinks accurately
    Includes thread safety annotations added by Hennadii Stepanov.
    844bf4f0a0
  46. Account vTxHashes accurately 8ef608a0e8
  47. Accurately account for queuedTx in DisconnectedBlockTransactions ce9ad71828
  48. Add allocator support to indirectmap e088ce8425
  49. Account for mapNextTx accurately fc2aed7b85
  50. Account mapLinks.{children,parents} accurately 6ad6e54541
  51. sipa force-pushed on Jul 21, 2020
  52. DrahtBot added the label Needs rebase on Jul 22, 2020
  53. DrahtBot commented at 8:16 pm on July 22, 2020: contributor

    🐙 This pull request conflicts with the target branch and needs rebase.

  54. jonatack commented at 3:04 pm on July 24, 2020: contributor

    Concept ACK, light pre-rebase ACK.

    Feel free to ignore: commit message eace374a00a2 s/inherent/inherit/; the 2 test commits both do essentially the same thing in under 4 lines, could be squashed.

  55. adamjonas commented at 1:31 pm on August 14, 2020: member

    It looks like this will need a rebase anyway, but I’m having issues compiling eace374a0, which looks consistent with the CI.

     0./memusage.h:92:64: error: cannot initialize a parameter of type 'std::__1::allocator<std::__1::__hash_node<int, void *> >::pointer' (aka 'std::__1::__hash_node<int, void *> *') with an lvalue of type 'int *'
     1    template<typename P> void destroy(P* ptr) { m_base.destroy(ptr); }
     2                                                               ^~~
     3/Library/Developer/CommandLineTools/usr/include/c++/v1/memory:1742:18: note: in instantiation of function template specialization 'memusage::AccountingAllocator<std::__1::__hash_node<int, void *> >::destroy<int>' requested here
     4            {__a.destroy(__p);}
     5                 ^
     6/Library/Developer/CommandLineTools/usr/include/c++/v1/memory:1595:14: note: in instantiation of function template specialization 'std::__1::allocator_traits<memusage::AccountingAllocator<std::__1::__hash_node<int, void *> > >::__destroy<int>' requested here
     7            {__destroy(__has_destroy<allocator_type, _Tp*>(), __a, __p);}
     8             ^
     9/Library/Developer/CommandLineTools/usr/include/c++/v1/__hash_table:845:29: note: in instantiation of function template specialization 'std::__1::allocator_traits<memusage::AccountingAllocator<std::__1::__hash_node<int, void *> > >::destroy<int>' requested here
    10            __alloc_traits::destroy(__na_, _NodeTypes::__get_ptr(__p->__value_));
    11                            ^
    12/Library/Developer/CommandLineTools/usr/include/c++/v1/memory:2648:7: note: in instantiation of member function 'std::__1::__hash_node_destructor<memusage::AccountingAllocator<std::__1::__hash_node<int, void *> > >::operator()' requested here
    13      __ptr_.second()(__tmp);
    14      ^
    15/Library/Developer/CommandLineTools/usr/include/c++/v1/memory:2602:19: note: in instantiation of member function 'std::__1::unique_ptr<std::__1::__hash_node<int, void *>, std::__1::__hash_node_destructor<memusage::AccountingAllocator<std::__1::__hash_node<int, void *> > > >::reset' requested here
    16  ~unique_ptr() { reset(); }
    17                  ^
    18/Library/Developer/CommandLineTools/usr/include/c++/v1/__hash_table:2034:29: note: in instantiation of member function 'std::__1::unique_ptr<std::__1::__hash_node<int, void *>, std::__1::__hash_node_destructor<memusage::AccountingAllocator<std::__1::__hash_node<int, void *> > > >::~unique_ptr' requested here
    19        __node_holder __h = __construct_node_hash(__hash, _VSTD::forward<_Args>(__args)...);
    20                            ^
    21/Library/Developer/CommandLineTools/usr/include/c++/v1/__hash_table:1146:16: note: in instantiation of function template specialization 'std::__1::__hash_table<int, std::__1::hash<int>, std::__1::equal_to<int>, memusage::AccountingAllocator<int> >::__emplace_unique_key_args<int, const int &>' requested here
    22        return __emplace_unique_key_args(_NodeTypes::__get_key(__x), __x);
    23               ^
    24/Library/Developer/CommandLineTools/usr/include/c++/v1/unordered_set:864:18: note: in instantiation of member function 'std::__1::__hash_table<int, std::__1::hash<int>, std::__1::equal_to<int>, memusage::AccountingAllocator<int> >::__insert_unique' requested here
    25        __table_.__insert_unique(*__first);
    26                 ^
    27/Library/Developer/CommandLineTools/usr/include/c++/v1/unordered_set:831:5: note: in instantiation of function template specialization 'std::__1::unordered_set<int, std::__1::hash<int>, std::__1::equal_to<int>, memusage::AccountingAllocator<int> >::insert<const int *>' requested here
    28    insert(__il.begin(), __il.end());
    29    ^
    30test/allocator_tests.cpp:245:24: note: in instantiation of member function 'std::__1::unordered_set<int, std::__1::hash<int>, std::__1::equal_to<int>, memusage::AccountingAllocator<int> >::unordered_set' requested here
    31        container_type container1({}, 2, std::hash<int>(), std::equal_to<int>(), memusage::AccountingAllocator<int>(dummy)); // container1 is accounted for in 'dummy'
    32                       ^
    33/Library/Developer/CommandLineTools/usr/include/c++/v1/memory:1880:52: note: passing argument to parameter '__p' here
    34    _LIBCPP_INLINE_VISIBILITY void destroy(pointer __p) {__p->~_Tp();}
    35                                                   ^
    361 error generated.
    
  56. hebasto commented at 6:20 pm on October 13, 2020: member

    @sipa @adamjonas Mind looking into https://github.com/hebasto/bitcoin/commits/201013-build18086 ?

    That branch is rebased and:

    • dropped “Make AccountingAllocator not inherent from std::allocator” commit
    • integrated my suggestion
    • improved tests: s/BOOST_CHECK/BOOST_CHECK_EQUAL/ (was very useful for me during debugging)
    • added a commit with the test for std::map as it is the container we actually use with the AccountingAllocator (among std::vector and boost::multi_index_container)

    CI CentOS build fails due to the old GCC 4.8 which, I think, just lacks support of the std::allocator_traits<Alloc>::select_on_container_copy_construction. It even lacks map( const Allocator& alloc ) constructor.

  57. sipa commented at 7:30 pm on October 13, 2020: member
    I’ll get back to this after the 0.21 branching.
  58. DrahtBot commented at 11:22 am on December 15, 2021: contributor
    • Is it still relevant? ➡️ Please solve the conflicts to make it ready for review and to ensure the CI passes.
    • Is it no longer relevant? ➡️ Please close.
    • Did the author lose interest or time to work on this? ➡️ Please close it and mark it ‘Up for grabs’ with the label, so that it can be picked up in the future.
  59. DrahtBot commented at 1:07 pm on March 21, 2022: contributor
    • Is it still relevant? ➡️ Please solve the conflicts to make it ready for review and to ensure the CI passes.
    • Is it no longer relevant? ➡️ Please close.
    • Did the author lose interest or time to work on this? ➡️ Please close it and mark it ‘Up for grabs’ with the label, so that it can be picked up in the future.
  60. fanquake added the label Up for grabs on Mar 22, 2022
  61. fanquake commented at 10:56 am on March 22, 2022: member

    I’ll get back to this after the 0.21 branching.

    I’m going to close this, and mark “Up for Grabs”. @martinus you might be interested here given #22702?

  62. fanquake closed this on Mar 22, 2022

  63. hebasto commented at 10:51 pm on November 30, 2022: member

    I’ll get back to this after the 0.21 branching.

    I’m going to close this, and mark “Up for Grabs”.

    #26614.

  64. maflcko removed the label Up for grabs on Dec 1, 2022
  65. maflcko removed the label Needs rebase on Dec 1, 2022
  66. bitcoin locked this on Dec 1, 2023

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: 2024-12-26 12:12 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me