Optimize SelectCoinsBnB by tracking the selection by index rather than by position #13226

pull Empact wants to merge 3 commits into bitcoin:master from Empact:select-bnb-by-index changing 1 files +28 −32
  1. Empact commented at 11:02 AM on May 13, 2018: member

    This is prompted by #13167 and presented as a friendly alternative to it.

    IMO you can improve code readability and performance by about 20% by tracking the selected utxos by index, rather than by position. This reduces the storage access complexity from roughly O(utxo_size) to O(selection_size).

    On my machine (median of 5 trials):

    BnBExhaustion, 5, 650, 2.2564, 0.000672999, 0.000711565, 0.000693112 - master
    BnBExhaustion, 5, 650, 1.76232, 0.000528563, 0.000568806, 0.000539147 - this PR
    
  2. fanquake added the label Wallet on May 13, 2018
  3. Empact commented at 6:11 AM on May 14, 2018: member

    /cc @achow101 this is the last of my BnB series

  4. fanquake requested review from achow101 on May 14, 2018
  5. in src/wallet/coinselection.cpp:105 in d74e5837f1 outdated
     103 |              // explore any more UTXOs to avoid burning money like that.
     104 | -            if (curr_waste <= best_waste) {
     105 | +            if (candidate_waste <= best_waste) {
     106 |                  best_selection = curr_selection;
     107 | -                best_waste = curr_waste;
     108 | +                best_waste = curr_waste = candidate_waste;
    


    achow101 commented at 3:22 PM on May 14, 2018:

    I believe this is incorrect. This will set curr_waste to candidate_waste which we don't want.


    Empact commented at 5:54 PM on May 14, 2018:

    Fixed.

  6. Empact force-pushed on May 14, 2018
  7. DrahtBot cross-referenced this on Jul 22, 2018 from issue Remove redundant variables, statements and forward declarations by practicalswift
  8. DrahtBot added the label Needs rebase on Jul 22, 2018
  9. Empact force-pushed on Jul 23, 2018
  10. Empact commented at 1:19 AM on July 23, 2018: member

    Rebased for #13691

  11. Empact force-pushed on Jul 23, 2018
  12. Empact commented at 1:34 AM on July 23, 2018: member

    Dropped the second commit as insignificant, updated the description / benchmarks.

  13. Empact force-pushed on Jul 30, 2018
  14. DrahtBot removed the label Needs rebase on Jul 30, 2018
  15. in src/wallet/coinselection.cpp:136 in 4c24e8ed78 outdated
     145 | -            if (!curr_selection.empty() && !curr_selection.back() &&
     146 | -                utxo.effective_value == utxo_pool.at(curr_selection.size() - 1).effective_value &&
     147 | -                utxo.fee == utxo_pool.at(curr_selection.size() - 1).fee) {
     148 | -                curr_selection.push_back(false);
     149 | -            } else {
     150 | +            size_t prev_index = curr_index - 1;
    


    practicalswift commented at 9:23 AM on October 7, 2018:

    This will lead to an unsigned integer overflow in the case of curr_index == 0. Could change logic and ordering to avoid that?


    Empact commented at 5:01 AM on October 8, 2018:

    Fair enough, I moved the subtraction inline, which avoids storing the overflowed value in the 0 case, and is more consistent with prior implementation. Note the behavior is not changed as the 0 case also corresponded to curr_selection being empty.

  16. Empact force-pushed on Oct 8, 2018
  17. Empact commented at 9:09 PM on May 28, 2019: member

    Closing this given #13167 is closed, and optimizing coin selection perf isn't much of a priority.

  18. Empact closed this on May 28, 2019

  19. Empact commented at 1:18 AM on June 3, 2021: member

    @Xekyo take a look

  20. Empact reopened this on Jun 3, 2021

  21. DrahtBot added the label Needs rebase on Jun 3, 2021
  22. Empact force-pushed on Jun 6, 2021
  23. Empact force-pushed on Jun 6, 2021
  24. DrahtBot removed the label Needs rebase on Jun 6, 2021
  25. DrahtBot cross-referenced this on Jun 6, 2021 from issue wallet: Introduce SelectionResult for encapsulating a coin selection solution by achow101
  26. DrahtBot commented at 3:45 AM on June 7, 2021: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

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

    <!--174a7506f384e20aa4161008e828411d-->

    Conflicts

    No conflicts as of last run.

  27. in src/wallet/coinselection.cpp:121 in 7714444c08 outdated
     122 | +                break;
     123 |              }
     124 |  
     125 | -            if (curr_selection.empty()) { // We have walked back to the first utxo and no branch is untraversed. All solutions searched
     126 | -                break;
     127 | +            // Walk backwards to find the last included UTXO that still needs to have its omission branch traversed.
    


    murchandamus commented at 7:25 PM on June 11, 2021:

    Optional: Please update comment to "add omitted UTXOs back to lookahead before traversing the omission branch of last included UTXO"


  28. in src/wallet/coinselection.cpp:145 in 7714444c08 outdated
     155 | +                (curr_index - 1) == curr_selection.back() ||
     156 | +                utxo.GetSelectionAmount() != utxo_pool.at(curr_index - 1).GetSelectionAmount() ||
     157 | +                utxo.fee != utxo_pool.at(curr_index - 1).fee) {
     158 |                  // Inclusion branch first (Largest First Exploration)
     159 | -                curr_selection.push_back(true);
     160 | +                curr_selection.push_back(curr_index);
    


    murchandamus commented at 7:39 PM on June 11, 2021:

    Took me a bit to verify whether this is equivalent, but this does look correct to me.

    Optional: The comment about "fee to long term fee" threw me off at first, perhaps it would actually be clearer to drop the second sentence of the comment before the if-block.


    Empact commented at 12:30 PM on February 28, 2022:

    Yeah - as an aid to the reviewer, I'm applying De Morgan's law here.

    I left the comment but moved it to above the relevant check.

  29. in src/wallet/coinselection.cpp:91 in 7714444c08 outdated
      88 | +    std::vector<size_t> best_selection;
      89 |      CAmount best_waste = MAX_MONEY;
      90 |  
      91 |      // Depth First search loop for choosing the UTXOs
      92 | -    for (size_t i = 0; i < TOTAL_TRIES; ++i) {
      93 | +    for (size_t i = 0, curr_index = 0; i < TOTAL_TRIES; ++i, ++curr_index) {
    


    murchandamus commented at 7:45 PM on June 11, 2021:

    Optional: given that we now have two indices incremented in this for-loop, perhaps rename i to try and curr_index to utxo_pool_depth.


    Empact commented at 12:31 PM on February 28, 2022:

    Done, though I went with utxo_pool_index, and curr_try as try is a keyword.

  30. murchandamus commented at 7:51 PM on June 11, 2021: contributor

    Code review ACK 7714444c08ce103297508bd1fb4b08bc67f6f637

    Regarding the commit message, AFAIU the complexity reduction applies to the vector of the selection index and the assembly of the best_selection. I'm surprised that's good for such a speedup. Am I missing something why this is so much faster?

  31. murchandamus commented at 7:53 PM on August 27, 2021: contributor

    Hi @Empact, are you still making progress on this, or would you be open to the idea of someone else adopting the PR? I was just considering amending my prior commentary after not looking at this for a while, but I would be happy to take a look at the failing check instead, if you aren't working on this anymore.

  32. Empact commented at 1:50 AM on August 28, 2021: member

    Hey Mark, yes I'd like to pick it back up again - have been otherwise engaged for a while but I expect to have time next week.

  33. DrahtBot cross-referenced this on Oct 27, 2021 from issue Optimize coin selection by dropping BnB upper limit by S3RK
  34. DrahtBot added the label Needs rebase on Dec 9, 2021
  35. Empact force-pushed on Feb 28, 2022
  36. Empact force-pushed on Feb 28, 2022
  37. Empact force-pushed on Feb 28, 2022
  38. DrahtBot removed the label Needs rebase on Feb 28, 2022
  39. achow101 commented at 12:17 PM on March 2, 2022: member

    ACK e6b45f39457f5d52232faf5b9395b7420a52bdc5

  40. Empact requested review from practicalswift on Mar 2, 2022
  41. fanquake requested review from glozow on Mar 10, 2022
  42. in src/wallet/coinselection.cpp:71 in cec31e08ca outdated
      66 | @@ -67,7 +67,7 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo
      67 |      SelectionResult result(selection_target);
      68 |      CAmount curr_value = 0;
      69 |  
      70 | -    std::vector<bool> curr_selection; // select the utxo at this index
      71 | +    std::vector<size_t> curr_selection; // selected utxo indexes
      72 |      curr_selection.reserve(utxo_pool.size());
    


    glozow commented at 3:30 PM on March 10, 2022:

    in cec31e08ca64168fd084bc71ec250d8eaca99537:

    Given that a big motivation here is to not allocate memory for every UTXO, we should probably remove this reserve(utxo_pool.size()) line

  43. in src/wallet/coinselection.cpp:91 in 33e8d4b4b8 outdated
      88 | @@ -89,7 +89,7 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo
      89 |      CAmount best_waste = MAX_MONEY;
      90 |  
      91 |      // Depth First search loop for choosing the UTXOs
      92 | -    for (size_t curr_try = 0, curr_index = 0; curr_try < TOTAL_TRIES; ++curr_try, ++curr_index) {
      93 | +    for (size_t curr_try = 0, utxo_pool_index = 0; curr_try < TOTAL_TRIES; ++curr_try, ++utxo_pool_index) {
    


    glozow commented at 1:38 PM on March 11, 2022:

    33e8d4b4b8999d5f80227fa20bed7271ab0976bd should be squashed into cec31e08ca64168fd084bc71ec250d8eaca99537, it was confusing to me that we're introducing a new curr_index variable in one commit, and then renaming it 2 commits later

  44. in src/wallet/coinselection.cpp:127 in e6b45f3945 outdated
     132 | +            }
     133 | +
     134 |              // Output was included on previous iterations, try excluding now.
     135 | -            curr_selection.back() = false;
     136 | -            OutputGroup& utxo = utxo_pool.at(curr_selection.size() - 1);
     137 | +            OutputGroup& utxo = utxo_pool.at(utxo_pool_index);
    


    glozow commented at 1:41 PM on March 11, 2022:

    in cec31e08ca64168fd084bc71ec250d8eaca99537: could be worth adding this line as a sanity check?

                assert(utxo_pool_index == curr_selection.back());
                OutputGroup& utxo = utxo_pool.at(utxo_pool_index);
    
  45. in src/wallet/coinselection.cpp:144 in cec31e08ca outdated
     154 | +            if (curr_selection.empty() ||
     155 | +                // The previous index is included and therefore not relevant for exclusion shortcut
     156 | +                (curr_index - 1) == curr_selection.back() ||
     157 | +                utxo.GetSelectionAmount() != utxo_pool.at(curr_index - 1).GetSelectionAmount() ||
     158 | +                utxo.fee != utxo_pool.at(curr_index - 1).fee)
     159 | +            {
    


    glozow commented at 1:42 PM on March 11, 2022:

    In cec31e08ca64168fd084bc71ec250d8eaca99537: I actually think the original condition for exclusion shortcut was clearer, and don't really see a reason to DeMorgans it


    Empact commented at 12:18 PM on March 14, 2022:

    don't really see a reason to DeMorgans it

    The reason is that one of the cases is no longer acted on, since we don't track excluded utxos in curr_selection, so we only have a no-op in the "positive" case, hence the desire to invert the condition.


    glozow commented at 12:37 PM on March 14, 2022:

    Okay, makes sense

  46. achow101 requested review from murchandamus on Mar 11, 2022
  47. refactor: Track BnB selection by index
    This is a performance optimization - rather than track all visited values
    in a bool vector, track the selected index in a vector. This results in a
    complexity reduction of O(utxo_size) to O(selection_size).
    1dd0923677
  48. refactor: Rename i to curr_try in SelectCoinsBnB
    Clarifies purpose and removes name collisions with other indicies.
    def43a4d88
  49. doc: Revise comments and whitespace to clarify 9d2005285c
  50. Empact force-pushed on Mar 14, 2022
  51. glozow commented at 1:36 PM on March 14, 2022: member

    code review ACK 9d2005285c77f6c4148bccfa0b8b9135abfa021c

    My bench results on master: | ns/op | op/s | err% | total | benchmark |--------------------:|--------------------:|--------:|----------:|:---------- | 2,468,488.00 | 405.11 | 0.5% | 0.03 | BnBExhaustion

    on this branch: | ns/op | op/s | err% | total | benchmark |--------------------:|--------------------:|--------:|----------:|:---------- | 1,396,491.00 | 716.08 | 0.4% | 0.02 | BnBExhaustion

  52. achow101 commented at 8:29 PM on March 18, 2022: member

    reACK 9d2005285c77f6c4148bccfa0b8b9135abfa021c

  53. murchandamus commented at 9:31 PM on March 21, 2022: contributor

    reACK 9d2005285c77f6c4148bccfa0b8b9135abfa021c

  54. achow101 merged this on Mar 21, 2022
  55. achow101 closed this on Mar 21, 2022

  56. Empact deleted the branch on Apr 15, 2022
  57. bitcoin locked this on Apr 15, 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: 2026-05-19 14:54 UTC

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