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):

    0BnBExhaustion, 5, 650, 2.2564, 0.000672999, 0.000711565, 0.000693112 - master
    1BnBExhaustion, 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 added the label Needs rebase on Jul 22, 2018
  8. Empact force-pushed on Jul 23, 2018
  9. Empact commented at 1:19 am on July 23, 2018: member
    Rebased for #13691
  10. Empact force-pushed on Jul 23, 2018
  11. Empact commented at 1:34 am on July 23, 2018: member
    Dropped the second commit as insignificant, updated the description / benchmarks.
  12. Empact force-pushed on Jul 30, 2018
  13. DrahtBot removed the label Needs rebase on Jul 30, 2018
  14. 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.
  15. Empact force-pushed on Oct 8, 2018
  16. 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.
  17. Empact closed this on May 28, 2019

  18. Empact commented at 1:18 am on June 3, 2021: member
    @Xekyo take a look
  19. Empact reopened this on Jun 3, 2021

  20. DrahtBot added the label Needs rebase on Jun 3, 2021
  21. Empact force-pushed on Jun 6, 2021
  22. Empact force-pushed on Jun 6, 2021
  23. DrahtBot removed the label Needs rebase on Jun 6, 2021
  24. DrahtBot commented at 3:45 am on June 7, 2021: member

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

    Conflicts

    No conflicts as of last run.

  25. 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.
    


    Xekyo 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”

  26. 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);
    


    Xekyo 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.

  27. 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) {
    


    Xekyo 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.
  28. Xekyo commented at 7:51 pm on June 11, 2021: member

    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?

  29. Xekyo commented at 7:53 pm on August 27, 2021: member
    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.
  30. 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.
  31. DrahtBot added the label Needs rebase on Dec 9, 2021
  32. Empact force-pushed on Feb 28, 2022
  33. Empact force-pushed on Feb 28, 2022
  34. Empact force-pushed on Feb 28, 2022
  35. DrahtBot removed the label Needs rebase on Feb 28, 2022
  36. achow101 commented at 12:17 pm on March 2, 2022: member
    ACK e6b45f39457f5d52232faf5b9395b7420a52bdc5
  37. Empact requested review from practicalswift on Mar 2, 2022
  38. fanquake requested review from glozow on Mar 10, 2022
  39. 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

  40. 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
  41. 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?

    0            assert(utxo_pool_index == curr_selection.back());
    1            OutputGroup& utxo = utxo_pool.at(utxo_pool_index);
    
  42. 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
  43. achow101 requested review from Xekyo on Mar 11, 2022
  44. 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
  45. refactor: Rename i to curr_try in SelectCoinsBnB
    Clarifies purpose and removes name collisions with other indicies.
    def43a4d88
  46. doc: Revise comments and whitespace to clarify 9d2005285c
  47. Empact force-pushed on Mar 14, 2022
  48. 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
  49. achow101 commented at 8:29 pm on March 18, 2022: member
    reACK 9d2005285c77f6c4148bccfa0b8b9135abfa021c
  50. Xekyo commented at 9:31 pm on March 21, 2022: member
    reACK 9d2005285c77f6c4148bccfa0b8b9135abfa021c
  51. achow101 merged this on Mar 21, 2022
  52. achow101 closed this on Mar 21, 2022

  53. Empact deleted the branch on Apr 15, 2022
  54. DrahtBot 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: 2024-10-05 01:12 UTC

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