From 292f0d92e945162e63dd07d893bb5f1764c0acdf Mon Sep 17 00:00:00 2001 From: juku Date: Fri, 7 Dec 2018 00:27:01 +0100 Subject: [PATCH 1/4] Added memory pools to reduce GC pressure for better performance --- .../memory/MemoryAllocatorException.java | 9 ++++ .../illarion/common/memory/MemoryPool.java | 33 +++++++++++++ .../illarion/common/memory/MemoryPools.java | 48 +++++++++++++++++++ 3 files changed, 90 insertions(+) create mode 100644 illacommon/src/main/java/illarion/common/memory/MemoryAllocatorException.java create mode 100644 illacommon/src/main/java/illarion/common/memory/MemoryPool.java create mode 100644 illacommon/src/main/java/illarion/common/memory/MemoryPools.java diff --git a/illacommon/src/main/java/illarion/common/memory/MemoryAllocatorException.java b/illacommon/src/main/java/illarion/common/memory/MemoryAllocatorException.java new file mode 100644 index 000000000..f35965342 --- /dev/null +++ b/illacommon/src/main/java/illarion/common/memory/MemoryAllocatorException.java @@ -0,0 +1,9 @@ +package illarion.common.memory; + +public class MemoryAllocatorException extends RuntimeException { + + public MemoryAllocatorException (Class cls, Throwable e) { + super("Couldn't create a new instance of class " + cls.getCanonicalName() + ", maybe constructor is not accessible (private / protected)?", e); + } + +} diff --git a/illacommon/src/main/java/illarion/common/memory/MemoryPool.java b/illacommon/src/main/java/illarion/common/memory/MemoryPool.java new file mode 100644 index 000000000..ff49475c2 --- /dev/null +++ b/illacommon/src/main/java/illarion/common/memory/MemoryPool.java @@ -0,0 +1,33 @@ +package illarion.common.memory; + +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + +public class MemoryPool { + + protected Queue freeObjs = new ConcurrentLinkedQueue<>(); + + protected MemoryPool() { + // + } + + public T get (Class cls) { + T obj = freeObjs.poll(); + + if (obj == null) { + //there is no free object in queue --> allocate a new one + try { + obj = cls.newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + throw new MemoryAllocatorException(cls, e); + } + } + + return obj; + } + + public void free (T obj) { + freeObjs.add(obj); + } + +} diff --git a/illacommon/src/main/java/illarion/common/memory/MemoryPools.java b/illacommon/src/main/java/illarion/common/memory/MemoryPools.java new file mode 100644 index 000000000..f58c3c47a --- /dev/null +++ b/illacommon/src/main/java/illarion/common/memory/MemoryPools.java @@ -0,0 +1,48 @@ +package illarion.common.memory; + +import java.util.HashMap; +import java.util.Map; + +public class MemoryPools { + + protected static Map,MemoryPool> pools = new HashMap<>(); + + protected MemoryPools() { + // + } + + /** + * get reused object from memory pool + * + * @param cls object type (class) + * + * @return instance of class from memory pool + */ + public static T get(Class cls) { + MemoryPool pool = getPool(cls); + return pool.get(cls); + } + + /** + * add object to memory pool again, so it can reused + * + * @param obj object + */ + public static void free(T obj) { + MemoryPool pool = (MemoryPool) getPool(obj.getClass()); + pool.free(obj); + } + + protected static MemoryPool getPool (Class cls) { + MemoryPool pool = (MemoryPool) pools.get(cls); + + if (pool == null) { + // Pool doesn't exists, because it wasn't used before, so create a new one + pool = new MemoryPool<>(); + pools.put(cls, pool); + } + + return pool; + } + +} From 355002ad7464c0c29fce5d94684a8e0818677351 Mon Sep 17 00:00:00 2001 From: juku Date: Fri, 7 Dec 2018 01:04:20 +0100 Subject: [PATCH 2/4] Improved memory usage, avoid large GC pressure --- .../client/graphics/MapDisplayManager.java | 5 +++- .../input/AbstractMouseLocationEvent.java | 25 ++++++++++++++++--- .../input/CurrentMouseLocationEvent.java | 18 +++++++++++++ .../illarion/client/input/InputReceiver.java | 5 +++- .../illarion/client/input/MoveOnMapEvent.java | 19 ++++++++++++++ .../memory/MemoryAllocatorException.java | 2 +- .../illarion/common/memory/MemoryPool.java | 22 +++++++++++++++- .../java/illarion/common/memory/Poolable.java | 10 ++++++++ .../engine/backend/shared/AbstractScene.java | 4 +++ 9 files changed, 103 insertions(+), 7 deletions(-) create mode 100644 illacommon/src/main/java/illarion/common/memory/Poolable.java diff --git a/illaclient/src/main/java/illarion/client/graphics/MapDisplayManager.java b/illaclient/src/main/java/illarion/client/graphics/MapDisplayManager.java index cb648d862..991b9dfc2 100644 --- a/illaclient/src/main/java/illarion/client/graphics/MapDisplayManager.java +++ b/illaclient/src/main/java/illarion/client/graphics/MapDisplayManager.java @@ -19,6 +19,7 @@ import illarion.client.input.CurrentMouseLocationEvent; import illarion.client.world.World; import illarion.client.world.characters.CharacterAttribute; +import illarion.common.memory.MemoryPools; import illarion.common.types.DisplayCoordinate; import org.illarion.engine.Engine; import org.illarion.engine.EngineException; @@ -148,7 +149,9 @@ public void update(@Nonnull GameContainer container, int delta) { Camera.getInstance().setViewport(-offX, -offY, container.getWidth(), container.getHeight()); Input engineInput = container.getEngine().getInput(); - gameScene.publishEvent(new CurrentMouseLocationEvent(engineInput.getMouseX(), engineInput.getMouseY())); + CurrentMouseLocationEvent event = MemoryPools.get(CurrentMouseLocationEvent.class); + event.set(engineInput.getMouseX(), engineInput.getMouseY()); + gameScene.publishEvent(event); gameScene.update(container, delta); updateFog(container); updateDeadView(container); diff --git a/illaclient/src/main/java/illarion/client/input/AbstractMouseLocationEvent.java b/illaclient/src/main/java/illarion/client/input/AbstractMouseLocationEvent.java index 3ffa11a87..e74907da5 100644 --- a/illaclient/src/main/java/illarion/client/input/AbstractMouseLocationEvent.java +++ b/illaclient/src/main/java/illarion/client/input/AbstractMouseLocationEvent.java @@ -15,6 +15,7 @@ */ package illarion.client.input; +import illarion.common.memory.Poolable; import org.illarion.engine.graphic.SceneEvent; import javax.annotation.Nonnull; @@ -24,16 +25,16 @@ * * @author Martin Karing <nitram@illarion.org> */ -public abstract class AbstractMouseLocationEvent implements SceneEvent { +public abstract class AbstractMouseLocationEvent implements SceneEvent, Poolable { /** * The x coordinate on the screen where the click occurred. */ - private final int x; + private int x; /** * The y coordinate on the screen where the click occurred. */ - private final int y; + private int y; /** * Create and initialize such an event. @@ -74,8 +75,26 @@ public int getY() { return y; } + /** + * set x and y coordinate + * + * @param x the x coordinate on the screen where the click occurred. + * @param y the y coordinate on the screen where the click occurred. + */ + protected void set (int x, int y) { + this.x = x; + this.y = y; + } + @Override public void notHandled() { // nothing } + + @Override + public void reset () { + this.x = 0; + this.y = 0; + } + } diff --git a/illaclient/src/main/java/illarion/client/input/CurrentMouseLocationEvent.java b/illaclient/src/main/java/illarion/client/input/CurrentMouseLocationEvent.java index 91a311e27..b64817a55 100644 --- a/illaclient/src/main/java/illarion/client/input/CurrentMouseLocationEvent.java +++ b/illaclient/src/main/java/illarion/client/input/CurrentMouseLocationEvent.java @@ -37,6 +37,17 @@ public CurrentMouseLocationEvent(int x, int y) { super(x, y); } + /** + * Create and initialize such an event. + */ + public CurrentMouseLocationEvent () { + super(0, 0); + } + + public void set (int x, int y) { + super.set(x, y); + } + /** * Create and initialize such an event. * @@ -53,4 +64,11 @@ public boolean isHighlightHandled() { public void setHighlightHandled(boolean highlightHandled) { this.highlightHandled = highlightHandled; } + + @Override + public void reset () { + super.reset(); + highlightHandled = false; + } + } diff --git a/illaclient/src/main/java/illarion/client/input/InputReceiver.java b/illaclient/src/main/java/illarion/client/input/InputReceiver.java index aabecaaa6..efd463031 100644 --- a/illaclient/src/main/java/illarion/client/input/InputReceiver.java +++ b/illaclient/src/main/java/illarion/client/input/InputReceiver.java @@ -18,6 +18,7 @@ import illarion.client.IllaClient; import illarion.client.world.World; import illarion.common.gui.AbstractMultiActionHelper; +import illarion.common.memory.MemoryPools; import org.bushe.swing.event.EventBus; import org.illarion.engine.input.*; import org.slf4j.Logger; @@ -273,7 +274,9 @@ public void mouseMoved(int mouseX, int mouseY) { if (enabled) { pointAtHelper.setInputData(mouseX, mouseY); pointAtHelper.pulse(); - EventBus.publish(new MoveOnMapEvent(mouseX, mouseY)); + MoveOnMapEvent event = MemoryPools.get(MoveOnMapEvent.class); + event.set(mouseX, mouseY); + EventBus.publish(event); } } diff --git a/illaclient/src/main/java/illarion/client/input/MoveOnMapEvent.java b/illaclient/src/main/java/illarion/client/input/MoveOnMapEvent.java index 9e6b78181..168ca5106 100644 --- a/illaclient/src/main/java/illarion/client/input/MoveOnMapEvent.java +++ b/illaclient/src/main/java/illarion/client/input/MoveOnMapEvent.java @@ -30,4 +30,23 @@ public final class MoveOnMapEvent extends AbstractMouseLocationEvent { public MoveOnMapEvent(int x, int y) { super(x, y); } + + /** + * Create and initialize such an event. + */ + public MoveOnMapEvent() { + super(0, 0); + } + + /** + * Initialize such an event. + * + * @param x the x coordinate of the click + * @param y the y coordinate of the click + */ + @Override + public void set (int x, int y) { + super.set(x, y); + } + } \ No newline at end of file diff --git a/illacommon/src/main/java/illarion/common/memory/MemoryAllocatorException.java b/illacommon/src/main/java/illarion/common/memory/MemoryAllocatorException.java index f35965342..b5e1a567f 100644 --- a/illacommon/src/main/java/illarion/common/memory/MemoryAllocatorException.java +++ b/illacommon/src/main/java/illarion/common/memory/MemoryAllocatorException.java @@ -3,7 +3,7 @@ public class MemoryAllocatorException extends RuntimeException { public MemoryAllocatorException (Class cls, Throwable e) { - super("Couldn't create a new instance of class " + cls.getCanonicalName() + ", maybe constructor is not accessible (private / protected)?", e); + super("Couldn't create a new instance of class " + cls.getCanonicalName() + ", maybe constructor is not accessible (private / protected) or there isn't a default constructor?", e); } } diff --git a/illacommon/src/main/java/illarion/common/memory/MemoryPool.java b/illacommon/src/main/java/illarion/common/memory/MemoryPool.java index ff49475c2..e9947fb62 100644 --- a/illacommon/src/main/java/illarion/common/memory/MemoryPool.java +++ b/illacommon/src/main/java/illarion/common/memory/MemoryPool.java @@ -1,11 +1,20 @@ package illarion.common.memory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; +/** +* A pool of objects that can be reused to avoid allocation +*/ public class MemoryPool { + private static final Logger LOGGER = LoggerFactory.getLogger(MemoryPool.class); + protected Queue freeObjs = new ConcurrentLinkedQueue<>(); + protected final int MAX_OBJECTS = 20; protected MemoryPool() { // @@ -15,7 +24,7 @@ public T get (Class cls) { T obj = freeObjs.poll(); if (obj == null) { - //there is no free object in queue --> allocate a new one + // There is no free object in queue --> allocate a new one try { obj = cls.newInstance(); } catch (InstantiationException | IllegalAccessException e) { @@ -27,6 +36,17 @@ public T get (Class cls) { } public void free (T obj) { + // Reset object, if possible + if (obj instanceof Poolable) { + ((Poolable) obj).reset(); + } + + if (freeObjs.size() >= MAX_OBJECTS) { + // Queue is full, don't add another object, else memory usage will increase drastically + LOGGER.warn("memory pool of type '" + obj.getClass().getSimpleName() + "' is full, cannot recycle object."); + return; + } + freeObjs.add(obj); } diff --git a/illacommon/src/main/java/illarion/common/memory/Poolable.java b/illacommon/src/main/java/illarion/common/memory/Poolable.java new file mode 100644 index 000000000..f585fde2e --- /dev/null +++ b/illacommon/src/main/java/illarion/common/memory/Poolable.java @@ -0,0 +1,10 @@ +package illarion.common.memory; + +public interface Poolable { + + /** + * resets the object for reuse. + */ + public void reset (); + +} diff --git a/illagameengine/src/main/java/org/illarion/engine/backend/shared/AbstractScene.java b/illagameengine/src/main/java/org/illarion/engine/backend/shared/AbstractScene.java index 56bbf156c..6c6041c24 100644 --- a/illagameengine/src/main/java/org/illarion/engine/backend/shared/AbstractScene.java +++ b/illagameengine/src/main/java/org/illarion/engine/backend/shared/AbstractScene.java @@ -15,6 +15,7 @@ */ package org.illarion.engine.backend.shared; +import illarion.common.memory.MemoryPools; import org.illarion.engine.GameContainer; import org.illarion.engine.graphic.Graphics; import org.illarion.engine.graphic.Scene; @@ -192,6 +193,9 @@ protected final void updateScene(@Nonnull GameContainer container, int delta) { if (!processed) { event.notHandled(); } + + // Recycle event so it can be reused + MemoryPools.free(event); } for (int i = 0; i < sceneElementCount; i++) { From fab8bcbec807dc3b3dcd1ff19c5679905b54c868 Mon Sep 17 00:00:00 2001 From: juku Date: Fri, 7 Dec 2018 01:11:44 +0100 Subject: [PATCH 3/4] Improved memory usage, removed memory leak --- .../illarion/client/input/InputReceiver.java | 4 +++- .../illarion/client/input/PointOnMapEvent.java | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/illaclient/src/main/java/illarion/client/input/InputReceiver.java b/illaclient/src/main/java/illarion/client/input/InputReceiver.java index efd463031..80deafacc 100644 --- a/illaclient/src/main/java/illarion/client/input/InputReceiver.java +++ b/illaclient/src/main/java/illarion/client/input/InputReceiver.java @@ -132,7 +132,9 @@ public void setInputData(int posX, int posY) { @Override public void executeAction(int count) { - EventBus.publish(new PointOnMapEvent(x, y)); + PointOnMapEvent event = MemoryPools.get(PointOnMapEvent.class); + event.set(x, y); + EventBus.publish(event); } } diff --git a/illaclient/src/main/java/illarion/client/input/PointOnMapEvent.java b/illaclient/src/main/java/illarion/client/input/PointOnMapEvent.java index 11f2a159a..6c38849d0 100644 --- a/illaclient/src/main/java/illarion/client/input/PointOnMapEvent.java +++ b/illaclient/src/main/java/illarion/client/input/PointOnMapEvent.java @@ -31,4 +31,21 @@ public final class PointOnMapEvent extends AbstractMouseLocationEvent { public PointOnMapEvent(int x, int y) { super(x, y); } + + /** + * Create and initialize such an event. + */ + public PointOnMapEvent() { + super(0, 0); + } + + /** + * Initialize such an event. + * + * @param x the x coordinate of the click + * @param y the y coordinate of the click + */ + public void set (int x, int y) { + super.set(x, y); + } } \ No newline at end of file From 8cb71048f55b89ebd02dfefb41459b0ae2c0a55a Mon Sep 17 00:00:00 2001 From: juku Date: Fri, 7 Dec 2018 01:29:25 +0100 Subject: [PATCH 4/4] Improved memory usage, removed memory leak --- .../client/input/AbstractMouseOnMapEvent.java | 29 ++++++++++++++++++- .../client/input/ClickOnMapEvent.java | 18 ++++++++++++ .../illarion/client/input/InputReceiver.java | 8 +++-- 3 files changed, 52 insertions(+), 3 deletions(-) diff --git a/illaclient/src/main/java/illarion/client/input/AbstractMouseOnMapEvent.java b/illaclient/src/main/java/illarion/client/input/AbstractMouseOnMapEvent.java index fdc8812e7..9aebae4e1 100644 --- a/illaclient/src/main/java/illarion/client/input/AbstractMouseOnMapEvent.java +++ b/illaclient/src/main/java/illarion/client/input/AbstractMouseOnMapEvent.java @@ -15,6 +15,7 @@ */ package illarion.client.input; +import illarion.common.memory.Poolable; import org.illarion.engine.input.Button; import javax.annotation.Nonnull; @@ -29,7 +30,7 @@ public abstract class AbstractMouseOnMapEvent extends AbstractMouseLocationEvent * The mouse key that was clicked. */ @Nonnull - private final Button key; + private Button key; /** * Create and initialize such an event. @@ -43,6 +44,13 @@ protected AbstractMouseOnMapEvent(@Nonnull Button key, int x, int y) { this.key = key; } + /** + * Create and initialize such an event. + */ + protected AbstractMouseOnMapEvent() { + super(0, 0); + } + /** * The copy constructor. * @@ -53,6 +61,18 @@ protected AbstractMouseOnMapEvent(@Nonnull AbstractMouseOnMapEvent org) { key = org.key; } + /** + * Create and initialize such an event. + * + * @param key the mouse key that was clicked + * @param x the x coordinate of the click + * @param y the y coordinate of the click + */ + public void set (@Nonnull Button key, int x, int y) { + super.set(x, y); + this.key = key; + } + /** * Get the key that was clicked on the mouse. * @@ -62,4 +82,11 @@ protected AbstractMouseOnMapEvent(@Nonnull AbstractMouseOnMapEvent org) { public Button getKey() { return key; } + + @Override + public void reset () { + super.reset(); + this.key = null; + } + } diff --git a/illaclient/src/main/java/illarion/client/input/ClickOnMapEvent.java b/illaclient/src/main/java/illarion/client/input/ClickOnMapEvent.java index d4ddd522f..4cc9851cd 100644 --- a/illaclient/src/main/java/illarion/client/input/ClickOnMapEvent.java +++ b/illaclient/src/main/java/illarion/client/input/ClickOnMapEvent.java @@ -37,6 +37,24 @@ public final class ClickOnMapEvent extends AbstractMouseOnMapEvent { super(key, x, y); } + /** + * Create and initialize such an event. + */ + public ClickOnMapEvent() { + super(); + } + + /** + * Initialize such an event. + * + * @param key the mouse key that was clicked + * @param x the x coordinate of the click + * @param y the y coordinate of the click + */ + public void set (@Nonnull Button key, int x, int y) { + super.set(key, x, y); + } + @Override public void notHandled() { if (getKey() == Button.Right) { diff --git a/illaclient/src/main/java/illarion/client/input/InputReceiver.java b/illaclient/src/main/java/illarion/client/input/InputReceiver.java index 80deafacc..c3b6f947a 100644 --- a/illaclient/src/main/java/illarion/client/input/InputReceiver.java +++ b/illaclient/src/main/java/illarion/client/input/InputReceiver.java @@ -86,7 +86,9 @@ public void executeAction(int count) { if (log.isDebugEnabled()) { log.debug("Raising single click event for {} button at {} {}", key.name(), x, y); } - EventBus.publish(new ClickOnMapEvent(key, x, y)); + ClickOnMapEvent event = MemoryPools.get(ClickOnMapEvent.class); + event.set(key, x, y); + EventBus.publish(event); break; case 2: if (log.isDebugEnabled()) { @@ -258,7 +260,9 @@ public void buttonClicked(int mouseX, int mouseY, @Nonnull Button button, int co log.debug("Received {} mouse clicked {} times at {}, {}", button, count, mouseX, mouseY); switch (count) { case 1: - World.getMapDisplay().getGameScene().publishEvent(new ClickOnMapEvent(button, mouseX, mouseY)); + ClickOnMapEvent event = MemoryPools.get(ClickOnMapEvent.class); + event.set(button, mouseX, mouseY); + World.getMapDisplay().getGameScene().publishEvent(event); break; case 2: World.getMapDisplay().getGameScene()