From 5bc47dbf98a91b217cc34ebcc07e329d91e1c755 Mon Sep 17 00:00:00 2001 From: Collin Smith Date: Wed, 21 Jul 2021 15:08:46 -0700 Subject: [PATCH] Impl of pooling for arrays and buckets of arrays of sizes --- .../java/com/riiablo/map2/util/ArrayPool.java | 49 ++++++++++ .../com/riiablo/map2/util/BucketPool.java | 98 +++++++++++++++++++ .../com/riiablo/map2/util/BucketPoolTest.java | 77 +++++++++++++++ 3 files changed, 224 insertions(+) create mode 100644 core/src/main/java/com/riiablo/map2/util/ArrayPool.java create mode 100644 core/src/main/java/com/riiablo/map2/util/BucketPool.java create mode 100644 core/src/test/java/com/riiablo/map2/util/BucketPoolTest.java diff --git a/core/src/main/java/com/riiablo/map2/util/ArrayPool.java b/core/src/main/java/com/riiablo/map2/util/ArrayPool.java new file mode 100644 index 00000000..236ce130 --- /dev/null +++ b/core/src/main/java/com/riiablo/map2/util/ArrayPool.java @@ -0,0 +1,49 @@ +package com.riiablo.map2.util; + +import java.lang.reflect.Array; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import com.badlogic.gdx.utils.Pool; + +public class ArrayPool extends Pool { + static E create(Class clazz, int length) { + return clazz.cast(Array.newInstance(clazz.getComponentType(), length)); + } + + public static ArrayPool get(Class clazz, int length) { + return new ArrayPool<>(clazz, length, 16, 16); + } + + public static Pool get( + Class clazz, + int length, + int initialCapacity, + int maxCapacity + ) { + return new ArrayPool<>(clazz, length, initialCapacity, maxCapacity); + } + + final Class clazz; + final int length; + + ArrayPool(Class clazz, int length, int initialCapacity, int maxCapacity) { + super(initialCapacity, maxCapacity); + this.clazz = clazz; + this.length = length; + } + + @Override + protected E newObject() { + return create(clazz, length); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("clazz", clazz.getSimpleName()) + .append("length", length) + .append("max", max) + .append("peak", peak) + .toString(); + } +} diff --git a/core/src/main/java/com/riiablo/map2/util/BucketPool.java b/core/src/main/java/com/riiablo/map2/util/BucketPool.java new file mode 100644 index 00000000..711776bf --- /dev/null +++ b/core/src/main/java/com/riiablo/map2/util/BucketPool.java @@ -0,0 +1,98 @@ +package com.riiablo.map2.util; + +import java.util.Arrays; +import java.util.NoSuchElementException; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.IntArray; +import com.badlogic.gdx.utils.Pool; + +import com.riiablo.logger.LogManager; +import com.riiablo.logger.Logger; + +public class BucketPool { + private static final Logger log = LogManager.getLogger(BucketPool.class); + + final Class clazz; + final Array> pools; + + BucketPool(Class clazz, int[] lengths, int offset, int length) { + this.clazz = clazz; + Arrays.sort(lengths, offset, offset + length); + pools = new Array<>(ArrayPool.class); + for (int i = offset, s = i + length; i < s; i++) { + pools.add(ArrayPool.get(clazz, lengths[i])); + } + } + + public Pool get(int length) { + for (ArrayPool pool : pools) { + if (length <= pool.length) { + return pool; + } + } + + throw new NoSuchElementException("No bucket big enough for length: " + length); + } + + public E obtain(int length) { + try { + Pool pool = get(length); + return pool.obtain(); + } catch (NoSuchElementException t) { + E instance = ArrayPool.create(clazz, length); + log.debugf("obtain custom-sized array instance: 0x%h %s length %d", + System.identityHashCode(instance), + clazz.getSimpleName(), + length); + return instance; + } + } + + public void free(E o) { + int length = ArrayUtils.getLength(o); + if (length <= 0) return; + try { + Pool pool = get(length); + pool.free(o); + } catch (NoSuchElementException t) { + log.debugf("free custom-sized array instance: 0x%h %s length %d", + System.identityHashCode(o), + clazz.getSimpleName(), + length); + } + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("pools", pools) + .toString(); + } + + public static Builder builder(Class clazz) { + if (!clazz.isArray()) throw new IllegalArgumentException("clazz must be an array type"); + return new Builder<>(clazz); + } + + public static class Builder { + final Class clazz; + final IntArray lengths; + + Builder(Class clazz) { + this.clazz = clazz; + lengths = new IntArray(4); + } + + public Builder add(int length) { + lengths.add(length); + return this; + } + + public BucketPool build() { + return new BucketPool<>(clazz, lengths.items, 0, lengths.size); + } + } +} diff --git a/core/src/test/java/com/riiablo/map2/util/BucketPoolTest.java b/core/src/test/java/com/riiablo/map2/util/BucketPoolTest.java new file mode 100644 index 00000000..ec0454d7 --- /dev/null +++ b/core/src/test/java/com/riiablo/map2/util/BucketPoolTest.java @@ -0,0 +1,77 @@ +package com.riiablo.map2.util; + +import org.junit.jupiter.api.*; +import static org.junit.jupiter.api.Assertions.*; + +import java.util.NoSuchElementException; + +import com.riiablo.logger.Level; +import com.riiablo.logger.LogManager; +import com.riiablo.logger.OutputStreamAppender; + +class BucketPoolTest { + @BeforeAll + static void before() { + LogManager.getRootLogger().addAppender(new OutputStreamAppender(System.out)); + LogManager.setLevel("com.riiablo.map2.util", Level.TRACE); + } + + static BucketPool newByteInstance() { + return BucketPool + .builder(byte[].class) + .add(16) + .add(32) + .add(64) + .build(); + } + + @Test + void byte_array_pool() { + BucketPool buckets = newByteInstance(); + assertEquals(3, buckets.pools.size); + assertEquals(16, buckets.pools.get(0).length); + assertEquals(32, buckets.pools.get(1).length); + assertEquals(64, buckets.pools.get(2).length); + } + + @Test + void byte_array_pool_get() { + BucketPool buckets = newByteInstance(); + assertEquals(buckets.get(15), buckets.pools.get(0)); + assertEquals(buckets.get(16), buckets.pools.get(0)); + assertEquals(buckets.get(31), buckets.pools.get(1)); + assertEquals(buckets.get(32), buckets.pools.get(1)); + assertEquals(buckets.get(63), buckets.pools.get(2)); + assertEquals(buckets.get(64), buckets.pools.get(2)); + } + + @Test + void byte_array_pool_get_high() { + BucketPool buckets = newByteInstance(); + assertThrows(NoSuchElementException.class, () -> buckets.get(65)); + } + + @Test + void byte_array_pool_obtain_15() { + BucketPool buckets = newByteInstance(); + byte[] data = buckets.obtain(15); + assertTrue(data.length >= 15); + buckets.free(data); + } + + @Test + void byte_array_pool_obtain_16() { + BucketPool buckets = newByteInstance(); + byte[] data = buckets.obtain(16); + assertTrue(data.length >= 16); + buckets.free(data); + } + + @Test + void byte_array_pool_obtain_65() { + BucketPool buckets = newByteInstance(); + byte[] data = buckets.obtain(65); + assertTrue(data.length >= 65); + buckets.free(data); + } +}