Remove LowMemorySet (Fixes #394)

This set, while aiming to save memory, has heavy implications on performance
with larger sets

This set
This commit is contained in:
Shane Freeder 2019-07-29 01:01:23 +01:00
parent 0766f50f5b
commit 9cdf9ddbfd
No known key found for this signature in database
GPG Key ID: A3F61EA5A085289C

View File

@ -1,4 +1,4 @@
From eeaf6ec7c4ce8b15447399baffe5b63675ea7358 Mon Sep 17 00:00:00 2001
From a584905bd02b9c01d14a04a69666bb41a80ae1ba Mon Sep 17 00:00:00 2001
From: Techcable <Techcable@techcable.net>
Date: Mon, 25 Apr 2016 23:46:00 -0700
Subject: [PATCH] Reduce the overhead of lots and lots of teams with the same
@ -7,195 +7,12 @@ Subject: [PATCH] Reduce the overhead of lots and lots of teams with the same
Featherboard (and other bad plugins) use persistent scoreboards (scoreboard.dat), causing every team ever to be sent to waterfall. This is bad, and takes tons of memory.
Uses String.intern() to avoid duplicating strings
Uses a sorted array to avoid the overhead of the hashset in a team.
diff --git a/api/src/main/java/io/github/waterfallmc/waterfall/utils/LowMemorySet.java b/api/src/main/java/io/github/waterfallmc/waterfall/utils/LowMemorySet.java
new file mode 100644
index 00000000..6c006923
--- /dev/null
+++ b/api/src/main/java/io/github/waterfallmc/waterfall/utils/LowMemorySet.java
@@ -0,0 +1,151 @@
+package io.github.waterfallmc.waterfall.utils;
+
+import java.util.AbstractSet;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * A set that uses a <a href=https://en.wikipedia.org/wiki/Binary_search_algorithm>binary search</a> to find objects in a <a href=https://en.wikipedia.org/wiki/Sorted_array>sorted array</a>.
+ * Avoids the memory cost of {@link java.util.HashSet}, while maintaining reasonable {@link Set#contains}
+ * <b>Insertions are O(N)!</b>
+ */
+public class LowMemorySet<T extends Comparable<T>> extends AbstractSet<T> implements Set<T> {
+ private final List<T> backing;
+
+ private LowMemorySet(List<T> list) {
+ this.backing = checkNotNull(list, "Null list");
+ this.sort(); // We have to sort any initial elements
+ }
+
+ public static <T extends Comparable<T>> LowMemorySet<T> create() {
+ return new LowMemorySet<>(new ArrayList<T>());
+ }
+
+ public static <T extends Comparable<T>> LowMemorySet<T> copyOf(Collection<T> c) {
+ return new LowMemorySet<>(new ArrayList<>(c));
+ }
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ private int indexOf(Object o) {
+ return Collections.binarySearch((List) backing, o);
+ }
+
+ private void sort() {
+ backing.sort(null);
+ }
+
+ @Override
+ public int size() {
+ return backing.size();
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ return indexOf(o) >= 0;
+ }
+
+ @Override
+ public Iterator<T> iterator() {
+ Iterator<T> backing = this.backing.iterator();
+ return new Iterator<T>() {
+ @Override
+ public boolean hasNext() {
+ return backing.hasNext();
+ }
+
+ @Override
+ public T next() {
+ return backing.next();
+ }
+
+ @Override
+ public void remove() {
+ backing.remove();
+ }
+
+ @Override
+ public void forEachRemaining(Consumer<? super T> action) {
+ backing.forEachRemaining(action);
+ }
+ };
+ }
+
+ @Override
+ public Object[] toArray() {
+ return backing.toArray();
+ }
+
+ @Override
+ public <T1> T1[] toArray(T1[] a) {
+ return backing.toArray(a);
+ }
+
+ @Override
+ public boolean add(T t) {
+ if (contains(t)) return false;
+ backing.add(t);
+ this.sort();
+ return true;
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ int index = indexOf(o);
+ if (index < 0) return false;
+ T old = backing.remove(index);
+ assert old == o;
+ return old != null;
+ }
+
+ @Override
+ public boolean removeAll(Collection<?> c) {
+ return backing.removeIf(c::contains);
+ }
+
+ @Override
+ public boolean retainAll(Collection<?> c) {
+ return backing.removeIf((o) -> !c.contains(o));
+ }
+
+ @Override
+ public boolean addAll(Collection<? extends T> c) {
+ if (containsAll(c)) return false;
+ backing.addAll(c);
+ this.sort();
+ return true;
+ }
+
+ @Override
+ public void clear() {
+ backing.clear();
+ }
+
+ @Override
+ public void forEach(Consumer<? super T> action) {
+ backing.forEach(action);
+ }
+
+ @Override
+ public Stream<T> stream() {
+ return backing.stream();
+ }
+
+ @Override
+ public Stream<T> parallelStream() {
+ return backing.parallelStream();
+ }
+
+ @Override
+ public boolean removeIf(Predicate<? super T> filter) {
+ return backing.removeIf(filter);
+ }
+}
diff --git a/api/src/main/java/net/md_5/bungee/api/score/Team.java b/api/src/main/java/net/md_5/bungee/api/score/Team.java
index 849ba1cf..25526320 100644
index 849ba1cf..39f81fd1 100644
--- a/api/src/main/java/net/md_5/bungee/api/score/Team.java
+++ b/api/src/main/java/net/md_5/bungee/api/score/Team.java
@@ -1,11 +1,12 @@
package net.md_5.bungee.api.score;
+import lombok.*;
+
import java.util.Collection;
import java.util.Collections;
-import java.util.HashSet;
import java.util.Set;
-import lombok.Data;
-import lombok.NonNull;
+
+import io.github.waterfallmc.waterfall.utils.LowMemorySet;
@Data
public class Team
@@ -20,7 +21,7 @@ public class Team
private String nameTagVisibility;
private String collisionRule;
private int color;
- private Set<String> players = new HashSet<>();
+ private Set<String> players = LowMemorySet.create();
public Collection<String> getPlayers()
{
@@ -29,7 +30,7 @@ public class Team
@@ -29,7 +29,7 @@ public class Team
public void addPlayer(String name)
{
@ -204,68 +21,6 @@ index 849ba1cf..25526320 100644
}
public void removePlayer(String name)
diff --git a/api/src/test/java/io/github/waterfallmc/waterfall/utils/LowMemorySetTest.java b/api/src/test/java/io/github/waterfallmc/waterfall/utils/LowMemorySetTest.java
new file mode 100644
index 00000000..5aa306a1
--- /dev/null
+++ b/api/src/test/java/io/github/waterfallmc/waterfall/utils/LowMemorySetTest.java
@@ -0,0 +1,56 @@
+package io.github.waterfallmc.waterfall.utils;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class LowMemorySetTest {
+
+ private static final ImmutableList<String> ELEMENTS = ImmutableList.of("test", "bob", "road", "food", "sleep", "sore-thought", "pain");
+
+ @Test
+ public void testContains() {
+ LowMemorySet<String> set = LowMemorySet.copyOf(ELEMENTS);
+ assertTrue(set.contains("test"));
+ assertTrue(set.contains("bob"));
+ assertFalse(set.contains("stupid"));
+ assertFalse(set.contains("head"));
+ }
+
+ @Test
+ public void testRemove() {
+ LowMemorySet<String> set = LowMemorySet.copyOf(ELEMENTS);
+ assertTrue(set.contains("test"));
+ set.remove("test");
+ assertFalse(set.contains("test"));
+ assertTrue(set.contains("bob"));
+ set.remove("bob");
+ assertFalse(set.contains("bob"));
+ assertTrue(ELEMENTS.size() - set.size() == 2);
+ assertTrue(set.contains("road"));
+ assertTrue(set.contains("food"));
+ assertTrue(set.contains("pain"));
+ set.removeAll(ImmutableList.of("road", "food", "pain"));
+ assertFalse(set.contains("road"));
+ assertFalse(set.contains("food"));
+ assertFalse(set.contains("pain"));
+ assertTrue(ELEMENTS.size() - set.size() == 5);
+ }
+
+ @Test
+ public void testAdd() {
+ LowMemorySet<String> set = LowMemorySet.copyOf(ELEMENTS);
+ assertFalse(set.contains("Techcable"));
+ set.add("Techcable");
+ assertTrue(set.contains("Techcable"));
+ set.addAll(ImmutableList.of("Techcable", "PhanaticD", "Dragonslayer293", "Aikar"));
+ assertTrue(set.contains("Techcable"));
+ assertTrue(set.contains("PhanaticD"));
+ assertTrue(set.contains("Aikar"));
+ assertFalse(set.contains("md_5"));
+ }
+
+}
--
2.21.0
2.22.0