Reduce memory allocations for RCLCensorshipDetector

This commit is contained in:
Howard Hinnant
2019-01-30 19:51:05 -05:00
committed by Nik Bougalis
parent 08371ba2c4
commit 296469f5fe
5 changed files with 171 additions and 64 deletions

View File

@@ -20,80 +20,71 @@
#ifndef RIPPLE_APP_CONSENSUS_RCLCENSORSHIPDETECTOR_H_INCLUDED
#define RIPPLE_APP_CONSENSUS_RCLCENSORSHIPDETECTOR_H_INCLUDED
#include <ripple/basics/algorithm.h>
#include <ripple/shamap/SHAMap.h>
#include <algorithm>
#include <map>
#include <utility>
#include <vector>
namespace ripple {
template <class TxID, class Sequence>
class RCLCensorshipDetector
{
private:
std::map<TxID, Sequence> tracker_;
/** Removes all elements satisfying specific criteria from the tracker
@param pred A predicate which returns true for tracking entries that
should be removed. The predicate must be callable as:
bool pred(TxID const&, Sequence)
It must return true for entries that should be removed.
@note This could be replaced with std::erase_if when it becomes part
of the standard. For example:
prune ([](TxID const& id, Sequence seq)
{
return id.isZero() || seq == 314159;
});
would become:
std::erase_if(tracker_.begin(), tracker_.end(),
[](auto const& e)
{
return e.first.isZero() || e.second == 314159;
}
*/
template <class Predicate>
void prune(Predicate&& pred)
public:
struct TxIDSeq
{
auto t = tracker_.begin();
TxID txid;
Sequence seq;
while (t != tracker_.end())
{
if (pred(t->first, t->second))
t = tracker_.erase(t);
else
t = std::next(t);
}
TxIDSeq (TxID const& txid_, Sequence const& seq_)
: txid (txid_)
, seq (seq_)
{ }
};
friend bool operator< (TxIDSeq const& lhs, TxIDSeq const& rhs)
{
if (lhs.txid != rhs.txid)
return lhs.txid < rhs.txid;
return lhs.seq < rhs.seq;
}
friend bool operator< (TxIDSeq const& lhs, TxID const& rhs)
{
return lhs.txid < rhs;
}
friend bool operator< (TxID const& lhs, TxIDSeq const& rhs)
{
return lhs < rhs.txid;
}
using TxIDSeqVec = std::vector<TxIDSeq>;
private:
TxIDSeqVec tracker_;
public:
RCLCensorshipDetector() = default;
/** Add transactions being proposed for the current consensus round.
@param seq The sequence number of the ledger being built.
@param proposed The set of transactions that we are initially proposing
for this round.
*/
void propose(
Sequence seq,
std::vector<TxID> proposed)
void propose(TxIDSeqVec proposed)
{
std::sort (proposed.begin(), proposed.end());
// We want to remove any entries that we proposed in a previous round
// that did not make it in yet if we are no longer proposing them.
prune ([&proposed](TxID const& id, Sequence seq)
{
return !std::binary_search(proposed.begin(), proposed.end(), id);
});
// Track the entries that we are proposing in this round.
for (auto const& p : proposed)
tracker_.emplace(p, seq); // FIXME C++17: use try_emplace
// And we also want to preserve the Sequence of entries that we proposed
// in the last round and want to propose again.
std::sort(proposed.begin(), proposed.end());
generalized_set_intersection(proposed.begin(), proposed.end(),
tracker_.cbegin(), tracker_.cend(),
[](auto& x, auto const& y) {x.seq = y.seq;},
[](auto const& x, auto const& y) {return x.txid < y.txid;});
tracker_ = std::move(proposed);
}
/** Determine which transactions made it and perform censorship detection.
@@ -114,17 +105,18 @@ public:
std::vector<TxID> accepted,
Predicate&& pred)
{
std::sort (accepted.begin(), accepted.end());
auto acceptTxid = accepted.begin();
auto const ae = accepted.end();
std::sort(acceptTxid, ae);
// We want to remove all tracking entries for transactions that were
// accepted as well as those which match the predicate.
prune ([&pred, &accepted](TxID const& id, Sequence seq)
{
if (std::binary_search(accepted.begin(), accepted.end(), id))
return true;
return pred(id, seq);
});
auto i = remove_if_intersect_or_match(tracker_.begin(), tracker_.end(),
accepted.begin(), accepted.end(),
[&pred](auto const& x) {return pred(x.txid, x.seq);},
std::less<void>{});
tracker_.erase(i, tracker_.end());
}
/** Removes all elements from the tracker

View File

@@ -330,15 +330,16 @@ RCLConsensus::Adaptor::onClose(
if (!wrongLCL)
{
std::vector<TxID> proposed;
LedgerIndex const seq = prevLedger->info().seq + 1;
RCLCensorshipDetector<TxID, LedgerIndex>::TxIDSeqVec proposed;
initialSet->visitLeaves(
[&proposed](std::shared_ptr<SHAMapItem const> const& item)
[&proposed, seq](std::shared_ptr<SHAMapItem const> const& item)
{
proposed.push_back(item->key());
proposed.emplace_back(item->key(), seq);
});
censorshipDetector_.propose(prevLedger->info().seq + 1, std::move(proposed));
censorshipDetector_.propose(std::move(proposed));
}
// Needed because of the move below.

View File

@@ -0,0 +1,110 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2019 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef RIPPLE_ALGORITHM_H_INCLUDED
#define RIPPLE_ALGORITHM_H_INCLUDED
#include <utility>
namespace ripple {
// Requires: [first1, last1) and [first2, last2) are ordered ranges according to
// comp.
// Effects: For each pair of elements {i, j} in the intersection of the sorted
// sequences [first1, last1) and [first2, last2), perform action(i, j).
// Note: This algorithm is evolved from std::set_intersection.
template <class InputIter1, class InputIter2, class Action, class Comp>
void
generalized_set_intersection(InputIter1 first1, InputIter1 last1,
InputIter2 first2, InputIter2 last2,
Action action, Comp comp)
{
while (first1 != last1 && first2 != last2)
{
if (comp(*first1, *first2)) // if *first1 < *first2
++first1; // then reduce first range
else
{
if (!comp(*first2, *first1)) // if *first1 == *first2
{ // then this is an intersection
action(*first1, *first2); // do the action
++first1; // reduce first range
}
++first2; // Reduce second range because *first2 <= *first1
}
}
}
// Requires: [first1, last1) and [first2, last2) are ordered ranges according to
// comp.
// Effects: Eliminates all the elements i in the range [first1, last1) which are
// equivalent to some value in [first2, last2) or for which pred(i) returns true.
// Returns: A FwdIter1 E such that [first1, E) is the range of elements not
// removed by this algorithm.
// Note: This algorithm is evolved from std::remove_if and std::set_intersection.
template <class FwdIter1, class InputIter2, class Pred, class Comp>
FwdIter1
remove_if_intersect_or_match(FwdIter1 first1, FwdIter1 last1,
InputIter2 first2, InputIter2 last2,
Pred pred, Comp comp)
{
// [original-first1, current-first1) is the set of elements to be preserved.
// [current-first1, i) is the set of elements that have been removed.
// [i, last1) is the set of elements not tested yet.
// Test each *i in [first1, last1) against [first2, last2) and pred
for (auto i = first1; i != last1;)
{
// if (*i is not in [first2, last2)
if (first2 == last2 || comp(*i, *first2))
{
if (!pred(*i))
{
// *i should not be removed, so append it to the preserved set
if (i != first1)
*first1 = std::move(*i);
++first1;
}
// *i has been fully tested, prepare for next i by
// appending i to the removed set, whether or not
// it has been moved from above.
++i;
}
else // *i might be in [first2, last2) because *i >= *first2
{
if (!comp(*first2, *i)) // if *i == *first2
++i; // then append *i to the removed set
// All elements in [i, last1) are known to be greater than *first2,
// so reduce the second range.
++first2;
}
// Continue to test *i against [first2, last2) and pred
}
return first1;
}
} // ripple
#endif

View File

@@ -33,7 +33,10 @@ class RCLCensorshipDetector_test : public beast::unit_test::suite
std::vector<int> remain, std::vector<int> remove)
{
// Begin tracking what we're proposing this round
cdet.propose(round, std::move(proposed));
RCLCensorshipDetector<int, int>::TxIDSeqVec proposal;
for (auto const& i : proposed)
proposal.emplace_back(i, round);
cdet.propose(std::move(proposal));
// Finalize the round, by processing what we accepted; then
// remove anything that needs to be removed and ensure that
@@ -68,7 +71,7 @@ public:
RCLCensorshipDetector<int, int> cdet;
int round = 0;
// proposed accepted remain remove
test(cdet, ++round, { }, { }, { }, { });
test(cdet, ++round, { 10, 11, 12, 13 }, { 11, 2 }, { 10, 13 }, { });
test(cdet, ++round, { 10, 13, 14, 15 }, { 14 }, { 10, 13, 15 }, { });