From ca83f07b5480c46f74447ca21bf16ed6baf179e7 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Sat, 13 Jul 2013 15:17:21 -0400 Subject: [PATCH] Simpler variant on multicast propagation algorithm seems to perform better by being less deterministic. May also be faster. --- node/Multicaster.hpp | 140 ++++++++++++++++--------------------------- 1 file changed, 50 insertions(+), 90 deletions(-) diff --git a/node/Multicaster.hpp b/node/Multicaster.hpp index 187054f0a..1addf42d0 100644 --- a/node/Multicaster.hpp +++ b/node/Multicaster.hpp @@ -119,19 +119,6 @@ public: return id.verifySignature(digest,signature,siglen); } - /** - * Update the most recent LIKE time for an address in a given multicast group on a given network - * - * @param nwid Network ID - * @param mg Multicast group - * @param addr Address that likes group on given network - * @param now Current timestamp - */ - inline void likesMulticastGroup(const uint64_t nwid,const MulticastGroup &mg,const Address &addr,const uint64_t now) - { - _multicastMemberships[MulticastChannel(nwid,mg)][addr] = now; - } - /** * Compute the CRC64 code for multicast deduplication * @@ -194,6 +181,27 @@ public: _multicastHistory[mhi][1] = now; } + /** + * Update the most recent LIKE time for an address in a given multicast group on a given network + * + * @param nwid Network ID + * @param mg Multicast group + * @param addr Address that likes group on given network + * @param now Current timestamp + */ + inline void likesMulticastGroup(const uint64_t nwid,const MulticastGroup &mg,const Address &addr,const uint64_t now) + { + Mutex::Lock _l(_multicastMemberships_m); + std::vector &memberships = _multicastMemberships[MulticastChannel(nwid,mg)]; + for(std::vector::iterator mm(memberships.begin());mm!=memberships.end();++mm) { + if (mm->first == addr) { + mm->second = now; + return; + } + } + memberships.push_back(MulticastMembership(addr,now)); + } + /** * Choose peers to send a propagating multicast to * @@ -223,100 +231,50 @@ public: P *peers, uint64_t now) { - P toConsider[ZT_MULTICAST_PICK_MAX_SAMPLE_SIZE]; - unsigned int sampleSize = 0; + typename std::set< P,_PeerPropagationPrioritySortOrder

> toConsider; - // Decay a few random bits in bloom filter to probabilistically eliminate - // false positives as we go. The odds of decaying an already-set bit - // increases as the bloom filter saturates, so in the early hops of - // propagation this likely won't have any effect. This allows peers with - // bloom filter collisions to be reconsidered, but at positions on the - // network graph likely to be hops away from the original origin of the + // Pick up to ZT_MULTICAST_PICK_MAX_SAMPLE_SIZE peers that have + // subscribed to this channel and that are not in bloom filter. + // Pick randomly from subscribers, but place into a set that is + // sorted in descending order of time of most recent unicast + // frame transfer. (Implicit social ordering.) Also ignore original + // submitter and upstream, since we know these have seen this // message. - for(unsigned int i=0;i >::iterator channelMembers(_multicastMemberships.find(MulticastChannel(nwid,mg))); - if ((channelMembers != _multicastMemberships.end())&&(!channelMembers->second.empty())) { - unsigned long numEntriesPermittedToSkip = (channelMembers->second.size() > ZT_MULTICAST_PICK_MAX_SAMPLE_SIZE) ? (unsigned long)(channelMembers->second.size() - ZT_MULTICAST_PICK_MAX_SAMPLE_SIZE) : (unsigned long)0; - double skipWhatFraction = (double)numEntriesPermittedToSkip / (double)channelMembers->second.size(); - - std::map::iterator channelMemberEntry(channelMembers->second.begin()); - - while (channelMemberEntry != channelMembers->second.end()) { - // Auto-clean the channel members map if their LIKEs are expired. This will - // technically skew the random distribution of chosen members just a little, but - // it's unlikely that enough will expire in any single pick to make much of a - // difference overall. - if ((now - channelMemberEntry->second) > ZT_MULTICAST_LIKE_EXPIRE) { - channelMembers->second.erase(channelMemberEntry++); - continue; + std::map< MulticastChannel,std::vector >::iterator mm(_multicastMemberships.find(MulticastChannel(nwid,mg))); + if ((mm != _multicastMemberships.end())&&(!mm->second.empty())) { + for(unsigned int stries=0;striessecond[prng.next32() % mm->second.size()]; + if (((now - m.second) < ZT_MULTICAST_LIKE_EXPIRE)&&(!bf.contains(m.first.sum()))&&(m.first != originalSubmitter)&&(m.first != upstream)) { + P peer(topology.getPeer(m.first)); + if (peer) + toConsider.insert(peer); } - - // Skip some fraction of entries so that our sampling will be randomly distributed, - // since there is no other good way to sample randomly from a map. - if (numEntriesPermittedToSkip) { - if (prng.nextDouble() <= skipWhatFraction) { - --numEntriesPermittedToSkip; - ++channelMemberEntry; - continue; - } - } - - // If it's not expired and it's from our random sample, add it to the set of peers - // to consider. Exclude immediate upstream and original submitter, since we know for - // a fact they've already seen this. Also exclude things in the bloom filter. - if ((channelMemberEntry->first != originalSubmitter)&&(channelMemberEntry->first != upstream)) { - if (!bf.contains(channelMemberEntry->first.sum())) { - P peer = topology.getPeer(channelMemberEntry->first); - if ((peer)&&(peer->hasActiveDirectPath(now))) { - toConsider[sampleSize++] = peer; - if (sampleSize >= ZT_MULTICAST_PICK_MAX_SAMPLE_SIZE) - break; // abort if we have enough candidates - } - } - } - ++channelMemberEntry; } - - // Auto-clean: erase whole map if there are no more LIKEs for this channel - if (channelMembers->second.empty()) - _multicastMemberships.erase(channelMembers); } } - // Sort in descending order of most recent direct unicast frame, picking - // peers with whom we have recently communicated. This is "implicit social - // switching." - std::sort(toConsider,toConsider + sampleSize,PeerPropagationPrioritySortOrder

()); + // The first peers in toConsider will be the 'best' + unsigned int chosen = 0; + for(typename std::set< P,_PeerPropagationPrioritySortOrder

>::iterator i(toConsider.begin());((i!=toConsider.end())&&(chosen < max));++i) + bf.set((peers[chosen++] = *i)->address().sum()); - // Pick the best N peers - unsigned int picked = 0; - for(unsigned int i=0;((iaddress().sum()); - } - - // Add a supernode if there's nowhere else to go. Supernodes know of all multicast - // LIKEs and so can act to bridge sparse multicast groups. - if (!picked) { + // Add a supernode if there are fewer than the desired + // number of recipients. + if (chosen < max) { P peer = topology.getBestSupernode(&originalSubmitter,1,true); if (peer) - peers[picked++] = peer; + peers[chosen++] = peer; } - return picked; + return chosen; } private: // Sort order for chosen propagation peers template - struct PeerPropagationPrioritySortOrder + struct _PeerPropagationPrioritySortOrder { inline bool operator()(const P &p1,const P &p2) const { @@ -355,8 +313,10 @@ private: // network ID and a multicast group within that network. typedef std::pair MulticastChannel; - // Address and time of last LIKE, by network ID and multicast group - std::map< MulticastChannel,std::map > _multicastMemberships; + // Address and time of last LIKE + typedef std::pair MulticastMembership; + + std::map< MulticastChannel,std::vector > _multicastMemberships; Mutex _multicastMemberships_m; };