From 212c87ffd8b43dfc21af6d8d3570a963f07341c4 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sun, 9 Jan 2011 01:02:17 +0000 Subject: [PATCH] * DataHelper: Speed up and annotate sortStructures() * RouterInfo: - Don't cache byteified data by default, to save ~1.5 MB - Don't create empty peers Set, to save ~100KB --- core/java/src/net/i2p/data/DataHelper.java | 43 +++++++++++---- core/java/src/net/i2p/data/RouterAddress.java | 7 ++- core/java/src/net/i2p/data/RouterInfo.java | 55 ++++++++++++++----- 3 files changed, 79 insertions(+), 26 deletions(-) diff --git a/core/java/src/net/i2p/data/DataHelper.java b/core/java/src/net/i2p/data/DataHelper.java index bd6bf260d9..2e74adce3a 100644 --- a/core/java/src/net/i2p/data/DataHelper.java +++ b/core/java/src/net/i2p/data/DataHelper.java @@ -31,6 +31,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.Date; import java.util.Iterator; import java.util.List; @@ -1040,25 +1041,45 @@ public class DataHelper { } /** - * Sort based on the Hash of the DataStructure + * Sort based on the Hash of the DataStructure. * Warning - relatively slow. - * Only used by RouterInfo - * Why? Just because it has to be consistent so signing will work? + * WARNING - this sort order must be consistent network-wide, so while the order is arbitrary, + * it cannot be changed. + * Why? Just because it has to be consistent so signing will work. * How to spec as returning the same type as the param? + * DEPRECATED - Only used by RouterInfo. */ public static List<? extends DataStructure> sortStructures(Collection<? extends DataStructure> dataStructures) { if (dataStructures == null) return Collections.EMPTY_LIST; - ArrayList<DataStructure> rv = new ArrayList(dataStructures.size()); - TreeMap<String, DataStructure> tm = new TreeMap(); - for (DataStructure struct : dataStructures) { - tm.put(struct.calculateHash().toString(), struct); - } - for (DataStructure struct : tm.values()) { - rv.add(struct); - } + + // This used to use Hash.toString(), which is insane, since a change to toString() + // would break the whole network. Now use Hash.toBase64(). + // Note that the Base64 sort order is NOT the same as the raw byte sort order, + // despite what you may read elsewhere. + + //ArrayList<DataStructure> rv = new ArrayList(dataStructures.size()); + //TreeMap<String, DataStructure> tm = new TreeMap(); + //for (DataStructure struct : dataStructures) { + // tm.put(struct.calculateHash().toString(), struct); + //} + //for (DataStructure struct : tm.values()) { + // rv.add(struct); + //} + ArrayList<DataStructure> rv = new ArrayList(dataStructures); + Collections.sort(rv, new DataStructureComparator()); return rv; } + /** + * See sortStructures() comments. + * @since 0.8.3 + */ + private static class DataStructureComparator implements Comparator<DataStructure> { + public int compare(DataStructure l, DataStructure r) { + return l.calculateHash().toBase64().compareTo(r.calculateHash().toBase64()); + } + } + /** * NOTE: formatDuration2() recommended in most cases for readability */ diff --git a/core/java/src/net/i2p/data/RouterAddress.java b/core/java/src/net/i2p/data/RouterAddress.java index 1b4d6438f5..647104574a 100644 --- a/core/java/src/net/i2p/data/RouterAddress.java +++ b/core/java/src/net/i2p/data/RouterAddress.java @@ -131,10 +131,13 @@ public class RouterAddress extends DataStructureImpl { && DataHelper.eq(_transportStyle, addr.getTransportStyle()); } - /** the style should be sufficient, for speed */ + /** + * Just use style and hashCode for speed (expiration is always null). + * If we add multiple addresses of the same style, this may need to be changed. + */ @Override public int hashCode() { - return DataHelper.hashCode(_transportStyle); + return DataHelper.hashCode(_transportStyle) ^ _cost; } /** diff --git a/core/java/src/net/i2p/data/RouterInfo.java b/core/java/src/net/i2p/data/RouterInfo.java index 9c6db01f91..b4aae94b18 100644 --- a/core/java/src/net/i2p/data/RouterInfo.java +++ b/core/java/src/net/i2p/data/RouterInfo.java @@ -14,6 +14,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Collection; +import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.Iterator; @@ -39,7 +40,8 @@ public class RouterInfo extends DatabaseEntry { private RouterIdentity _identity; private volatile long _published; private final Set<RouterAddress> _addresses; - private final Set<Hash> _peers; + /** may be null to save memory, no longer final */ + private Set<Hash> _peers; private /* FIXME final FIXME */ Properties _options; private volatile boolean _validated; private volatile boolean _isValid; @@ -47,6 +49,10 @@ public class RouterInfo extends DatabaseEntry { private volatile byte _byteified[]; private volatile int _hashCode; private volatile boolean _hashCodeInitialized; + /** should we cache the byte and string versions _byteified ? **/ + private boolean _shouldCache; + /** maybe we should check if we are floodfill? */ + private static final boolean CACHE_ALL = Runtime.getRuntime().maxMemory() > 128*1024*1024l; public static final String PROP_NETWORK_ID = "netId"; public static final String PROP_CAPABILITIES = "caps"; @@ -58,7 +64,6 @@ public class RouterInfo extends DatabaseEntry { public RouterInfo() { _addresses = new HashSet(2); - _peers = new HashSet(0); _options = new OrderedProperties(); } @@ -70,6 +75,7 @@ public class RouterInfo extends DatabaseEntry { setPeers(old.getPeers()); setOptions(old.getOptions()); setSignature(old.getSignature()); + // copy over _byteified? } public long getDate() { @@ -105,6 +111,11 @@ public class RouterInfo extends DatabaseEntry { public void setIdentity(RouterIdentity ident) { _identity = ident; resetCache(); + // We only want to cache the bytes for our own RI, which is frequently written. + // To cache for all RIs doubles the RI memory usage. + // setIdentity() is only called when we are creating our own RI. + // Otherwise, the data is populated with readBytes(). + _shouldCache = true; } /** @@ -159,6 +170,8 @@ public class RouterInfo extends DatabaseEntry { * @deprecated Implemented here but unused elsewhere */ public Set<Hash> getPeers() { + if (_peers == null) + return Collections.EMPTY_SET; return _peers; } @@ -169,9 +182,15 @@ public class RouterInfo extends DatabaseEntry { * @deprecated Implemented here but unused elsewhere */ public void setPeers(Set<Hash> peers) { + if (peers == null || peers.isEmpty()) { + _peers = null; + return; + } + if (_peers == null) + _peers = new HashSet(2); synchronized (_peers) { _peers.clear(); - if (peers != null) _peers.addAll(peers); + _peers.addAll(peers); } resetCache(); } @@ -223,7 +242,6 @@ public class RouterInfo extends DatabaseEntry { if (_byteified != null) return _byteified; if (_identity == null) throw new DataFormatException("Router identity isn't set? wtf!"); if (_addresses == null) throw new DataFormatException("Router addressess isn't set? wtf!"); - if (_peers == null) throw new DataFormatException("Router peers isn't set? wtf!"); if (_options == null) throw new DataFormatException("Router options isn't set? wtf!"); long before = Clock.getInstance().now(); @@ -239,6 +257,9 @@ public class RouterInfo extends DatabaseEntry { DataHelper.writeLong(out, 1, sz); Collection<RouterAddress> addresses = _addresses; if (sz > 1) + // WARNING this sort algorithm cannot be changed, as it must be consistent + // network-wide. The signature is not checked at readin time, but only + // later, and the addresses are stored in a Set, not a List. addresses = (Collection<RouterAddress>) DataHelper.sortStructures(addresses); for (RouterAddress addr : addresses) { addr.writeBytes(out); @@ -248,12 +269,14 @@ public class RouterInfo extends DatabaseEntry { // answer: they're always empty... they're a placeholder for one particular // method of trusted links, which isn't implemented in the router // at the moment, and may not be later. - // fixme to reduce objects - allow _peers == null - int psz = _peers.size(); + int psz = _peers == null ? 0 : _peers.size(); DataHelper.writeLong(out, 1, psz); if (psz > 0) { Collection<Hash> peers = _peers; if (psz > 1) + // WARNING this sort algorithm cannot be changed, as it must be consistent + // network-wide. The signature is not checked at readin time, but only + // later, and the hashes are stored in a Set, not a List. peers = (Collection<Hash>) DataHelper.sortStructures(peers); for (Hash peerHash : peers) { peerHash.writeBytes(out); @@ -266,7 +289,8 @@ public class RouterInfo extends DatabaseEntry { byte data[] = out.toByteArray(); long after = Clock.getInstance().now(); _log.debug("getBytes() took " + (after - before) + "ms"); - _byteified = data; + if (CACHE_ALL || _shouldCache) + _byteified = data; return data; } @@ -466,10 +490,15 @@ public class RouterInfo extends DatabaseEntry { _addresses.add(address); } int numPeers = (int) DataHelper.readLong(in, 1); - for (int i = 0; i < numPeers; i++) { - Hash peerIdentityHash = new Hash(); - peerIdentityHash.readBytes(in); - _peers.add(peerIdentityHash); + if (numPeers == 0) { + _peers = null; + } else { + _peers = new HashSet(numPeers); + for (int i = 0; i < numPeers; i++) { + Hash peerIdentityHash = new Hash(); + peerIdentityHash.readBytes(in); + _peers.add(peerIdentityHash); + } } _options = DataHelper.readProperties(in); _signature = new Signature(); @@ -504,7 +533,7 @@ public class RouterInfo extends DatabaseEntry { && _published == info.getPublished() && DataHelper.eq(_addresses, info.getAddresses()) && DataHelper.eq(_options, info.getOptions()) - && DataHelper.eq(_peers, info.getPeers()); + && DataHelper.eq(getPeers(), info.getPeers()); } @Override @@ -530,7 +559,7 @@ public class RouterInfo extends DatabaseEntry { RouterAddress addr = (RouterAddress) iter.next(); buf.append("\n\t\tAddress: ").append(addr); } - Set peers = _peers; // getPeers() + Set peers = getPeers(); buf.append("\n\tPeers: #: ").append(peers.size()); for (Iterator iter = peers.iterator(); iter.hasNext();) { Hash hash = (Hash) iter.next(); -- GitLab