From 09591c2492635b6b23fbf3472e62c6f389b4d9fe Mon Sep 17 00:00:00 2001
From: Christian Wulf <chw@informatik.uni-kiel.de>
Date: Fri, 27 Jun 2014 11:37:17 +0200
Subject: [PATCH] added performance test for circular access

---
 .../workstealing/CircularArray.java           |   9 +-
 .../workstealing/CircularIntArray.java        |  88 ++++++++++
 .../workstealing/CircularModIntArray.java     |  89 ++++++++++
 .../java/teetime/util/list/CircularList.java  |  33 ++++
 .../teetime/util/CircularCollectionsTest.java | 161 ++++++++++++++++++
 5 files changed, 379 insertions(+), 1 deletion(-)
 create mode 100644 src/main/java/teetime/util/concurrent/workstealing/CircularIntArray.java
 create mode 100644 src/main/java/teetime/util/concurrent/workstealing/CircularModIntArray.java
 create mode 100644 src/main/java/teetime/util/list/CircularList.java
 create mode 100644 src/test/java/teetime/util/CircularCollectionsTest.java

diff --git a/src/main/java/teetime/util/concurrent/workstealing/CircularArray.java b/src/main/java/teetime/util/concurrent/workstealing/CircularArray.java
index 60a3991e..e48177ab 100644
--- a/src/main/java/teetime/util/concurrent/workstealing/CircularArray.java
+++ b/src/main/java/teetime/util/concurrent/workstealing/CircularArray.java
@@ -28,11 +28,12 @@ import java.util.Arrays;
  * 
  * @param <T>
  */
-public class CircularArray<T> {
+public final class CircularArray<T> {
 
 	private final long logSize;
 	private final T[] segment;
 	private final long mask;
+	private long currentIndex;
 
 	/**
 	 * 
@@ -54,6 +55,12 @@ public class CircularArray<T> {
 		return this.segment[(int) (i & this.mask)]; // risk of overflow
 	}
 
+	public T getNext() {
+		long index = this.currentIndex;
+		this.currentIndex = (this.currentIndex + 1) & this.mask;
+		return this.segment[(int) index];
+	}
+
 	public void put(final long i, final T o) {
 		this.segment[(int) (i & this.mask)] = o; // risk of overflow
 	}
diff --git a/src/main/java/teetime/util/concurrent/workstealing/CircularIntArray.java b/src/main/java/teetime/util/concurrent/workstealing/CircularIntArray.java
new file mode 100644
index 00000000..cb1a52f8
--- /dev/null
+++ b/src/main/java/teetime/util/concurrent/workstealing/CircularIntArray.java
@@ -0,0 +1,88 @@
+/***************************************************************************
+ * Copyright 2014 Kieker Project (http://kieker-monitoring.net)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ ***************************************************************************/
+
+package teetime.util.concurrent.workstealing;
+
+import java.util.Arrays;
+
+/**
+ * 
+ * @author Christian Wulf
+ * 
+ * @see "Dynamic Circular WorkStealing Deque"
+ * 
+ * @since 1.10
+ * 
+ * @param <T>
+ */
+public final class CircularIntArray<T> {
+
+	private final int logSize;
+	private final T[] segment;
+	private final int mask;
+	private int currentIndex;
+
+	/**
+	 * 
+	 * @param logSize
+	 *            The initial size of this array in log2, i.e., the number of bits to use
+	 */
+	@SuppressWarnings("unchecked")
+	public CircularIntArray(final int logSize) {
+		this.logSize = logSize;
+		this.segment = (T[]) new Object[1 << this.logSize];
+		this.mask = this.getCapacity() - 1; // mask = 0..01..1
+	}
+
+	public int getCapacity() {
+		return this.segment.length;
+	}
+
+	public T get(final int i) {
+		return this.segment[i & this.mask]; // risk of overflow
+	}
+
+	public T getNext() {
+		int index = this.currentIndex;
+		this.currentIndex = (this.currentIndex + 1) & this.mask;
+		return this.segment[index];
+	}
+
+	public void put(final int i, final T o) {
+		this.segment[i & this.mask] = o; // risk of overflow
+	}
+
+	public CircularIntArray<T> grow(final int b, final int t) {
+		final CircularIntArray<T> a = new CircularIntArray<T>(this.logSize + 1);
+		for (int i = t; i < b; i++) {
+			a.put(i, this.get(i));
+		}
+		return a;
+	}
+
+	public CircularIntArray<T> shrink(final int b, final int t) {
+		final CircularIntArray<T> a = new CircularIntArray<T>(this.logSize - 1);
+		for (int i = t; i < b; i++) {
+			a.put(i, this.get(i));
+		}
+		return a;
+	}
+
+	@Override
+	public String toString() {
+		return Arrays.toString(this.segment);
+	}
+}
diff --git a/src/main/java/teetime/util/concurrent/workstealing/CircularModIntArray.java b/src/main/java/teetime/util/concurrent/workstealing/CircularModIntArray.java
new file mode 100644
index 00000000..990cd38f
--- /dev/null
+++ b/src/main/java/teetime/util/concurrent/workstealing/CircularModIntArray.java
@@ -0,0 +1,89 @@
+/***************************************************************************
+ * Copyright 2014 Kieker Project (http://kieker-monitoring.net)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ ***************************************************************************/
+
+package teetime.util.concurrent.workstealing;
+
+import java.util.Arrays;
+
+/**
+ * 
+ * @author Christian Wulf
+ * 
+ * @see "Dynamic Circular WorkStealing Deque"
+ * 
+ * @since 1.10
+ * 
+ * @param <T>
+ */
+public final class CircularModIntArray<T> {
+
+	private final int logSize;
+	private final T[] segment;
+	private final int size;
+
+	private int currentIndex;
+
+	/**
+	 * 
+	 * @param logSize
+	 *            The initial size of this array in log2, i.e., the number of bits to use
+	 */
+	@SuppressWarnings("unchecked")
+	public CircularModIntArray(final int logSize) {
+		this.logSize = logSize;
+		this.segment = (T[]) new Object[1 << this.logSize];
+		this.size = this.segment.length;
+	}
+
+	public int getCapacity() {
+		return this.segment.length;
+	}
+
+	public T get(final int i) {
+		return this.segment[i % this.size]; // risk of overflow
+	}
+
+	public T getNext() {
+		int index = this.currentIndex;
+		this.currentIndex = (this.currentIndex + 1) % this.size;
+		return this.segment[index];
+	}
+
+	public void put(final int i, final T o) {
+		this.segment[i % this.size] = o; // risk of overflow
+	}
+
+	public CircularModIntArray<T> grow(final int b, final int t) {
+		final CircularModIntArray<T> a = new CircularModIntArray<T>(this.logSize + 1);
+		for (int i = t; i < b; i++) {
+			a.put(i, this.get(i));
+		}
+		return a;
+	}
+
+	public CircularModIntArray<T> shrink(final int b, final int t) {
+		final CircularModIntArray<T> a = new CircularModIntArray<T>(this.logSize - 1);
+		for (int i = t; i < b; i++) {
+			a.put(i, this.get(i));
+		}
+		return a;
+	}
+
+	@Override
+	public String toString() {
+		return Arrays.toString(this.segment);
+	}
+}
diff --git a/src/main/java/teetime/util/list/CircularList.java b/src/main/java/teetime/util/list/CircularList.java
new file mode 100644
index 00000000..b283ba4a
--- /dev/null
+++ b/src/main/java/teetime/util/list/CircularList.java
@@ -0,0 +1,33 @@
+package teetime.util.list;
+
+public final class CircularList<T> {
+
+	private static final class Node<T> {
+		T value;
+		Node<T> next;
+	}
+
+	private Node<T> headNode;
+	private Node<T> lastNode;
+
+	private Node<T> currentNode;
+
+	public void add(final T value) {
+		Node<T> newNode = new Node<T>();
+		newNode.value = value;
+
+		if (this.headNode == null) { // newNode is the first node
+			this.headNode = this.lastNode = newNode;
+			this.currentNode = newNode;
+		}
+
+		this.lastNode.next = newNode;
+		newNode.next = this.headNode;
+	}
+
+	public T getNext() {
+		T value = this.currentNode.value;
+		this.currentNode = this.currentNode.next;
+		return value;
+	}
+}
diff --git a/src/test/java/teetime/util/CircularCollectionsTest.java b/src/test/java/teetime/util/CircularCollectionsTest.java
new file mode 100644
index 00000000..f58607b8
--- /dev/null
+++ b/src/test/java/teetime/util/CircularCollectionsTest.java
@@ -0,0 +1,161 @@
+package teetime.util;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.lessThan;
+import static org.junit.Assert.assertThat;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.FixMethodOrder;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+import org.junit.runners.MethodSorters;
+
+import teetime.util.concurrent.workstealing.CircularArray;
+import teetime.util.concurrent.workstealing.CircularIntArray;
+import teetime.util.concurrent.workstealing.CircularModIntArray;
+import teetime.util.list.CircularList;
+
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class CircularCollectionsTest {
+
+	private static final int NUM_OBJECTS_TO_CREATE_IN_POW2 = 2;
+	private static final int NUM_OBJECTS_TO_CREATE = (int) Math.pow(2, NUM_OBJECTS_TO_CREATE_IN_POW2);
+	private static final int NUM_ACCESSES = 100000000;
+	private static final int WARMUP_ITERATIONS = 3;
+
+	private static final Map<String, Long> durations = new LinkedHashMap<String, Long>();
+
+	private StopWatch stopWatch;
+	protected Description description;
+
+	@Before
+	public void setup() {
+		this.stopWatch = new StopWatch();
+	}
+
+	@Rule
+	public final TestRule watcher = new TestWatcher() {
+		@Override
+		protected void starting(final Description description) {
+			CircularCollectionsTest.this.description = description;
+		}
+	};
+
+	@After
+	public void tearDown() {
+		CircularCollectionsTest.durations.put(this.description.getDisplayName(), this.stopWatch.getDurationInNs());
+	}
+
+	@Test
+	public void testCircularIntArray() throws Exception {
+		CircularIntArray<Object> circularArray = new CircularIntArray<Object>(NUM_OBJECTS_TO_CREATE_IN_POW2);
+		for (int i = 0; i < NUM_OBJECTS_TO_CREATE; i++) {
+			circularArray.put(i, new Object());
+		}
+
+		int warmupIterations = WARMUP_ITERATIONS;
+		while (warmupIterations-- > 0) {
+			for (int i = 0; i < NUM_ACCESSES; i++) {
+				circularArray.getNext().toString();
+			}
+		}
+
+		this.stopWatch.start();
+		for (int i = 0; i < NUM_ACCESSES; i++) {
+			circularArray.getNext().toString();
+		}
+		this.stopWatch.end();
+	}
+
+	@Test
+	public void testCircularModIntArray() throws Exception {
+		CircularModIntArray<Object> circularArray = new CircularModIntArray<Object>(NUM_OBJECTS_TO_CREATE_IN_POW2);
+		for (int i = 0; i < NUM_OBJECTS_TO_CREATE; i++) {
+			circularArray.put(i, new Object());
+		}
+
+		int warmupIterations = WARMUP_ITERATIONS;
+		while (warmupIterations-- > 0) {
+			for (int i = 0; i < NUM_ACCESSES; i++) {
+				circularArray.getNext().toString();
+			}
+		}
+
+		this.stopWatch.start();
+		for (int i = 0; i < NUM_ACCESSES; i++) {
+			circularArray.getNext().toString();
+		}
+		this.stopWatch.end();
+	}
+
+	@Test
+	public void testCircularList() throws Exception {
+		CircularList<Object> circularList = new CircularList<Object>();
+		for (int i = 0; i < NUM_OBJECTS_TO_CREATE; i++) {
+			circularList.add(new Object());
+		}
+
+		int warmupIterations = WARMUP_ITERATIONS;
+		while (warmupIterations-- > 0) {
+			for (int i = 0; i < NUM_ACCESSES; i++) {
+				circularList.getNext().toString();
+			}
+		}
+
+		this.stopWatch.start();
+		for (int i = 0; i < NUM_ACCESSES; i++) {
+			circularList.getNext().toString();
+		}
+		this.stopWatch.end();
+	}
+
+	@Test
+	public void testCircularLongArray() throws Exception {
+		CircularArray<Object> circularArray = new CircularArray<Object>(NUM_OBJECTS_TO_CREATE_IN_POW2);
+		for (int i = 0; i < NUM_OBJECTS_TO_CREATE; i++) {
+			circularArray.put(i, new Object());
+		}
+
+		int warmupIterations = WARMUP_ITERATIONS;
+		while (warmupIterations-- > 0) {
+			for (int i = 0; i < NUM_ACCESSES; i++) {
+				circularArray.getNext().toString();
+			}
+		}
+
+		this.stopWatch.start();
+		for (int i = 0; i < NUM_ACCESSES; i++) {
+			circularArray.getNext().toString();
+		}
+		this.stopWatch.end();
+	}
+
+	@AfterClass
+	public static void afterClass() {
+		Long circularIntArrayInNs = durations.get("testCircularIntArray(teetime.util.CircularCollectionsTest)");
+		Long circularModIntArrayInNs = durations.get("testCircularModIntArray(teetime.util.CircularCollectionsTest)");
+		Long circularListInNs = durations.get("testCircularList(teetime.util.CircularCollectionsTest)");
+		Long circularLongArrayInNs = durations.get("testCircularLongArray(teetime.util.CircularCollectionsTest)");
+
+		for (Entry<String, Long> entry : durations.entrySet()) {
+			System.out.println(entry.getKey() + ": " + TimeUnit.NANOSECONDS.toMillis(entry.getValue()) + " ms");
+		}
+
+		assertThat(circularListInNs, is(lessThan(circularModIntArrayInNs)));
+
+		// testCircularIntArray(teetime.util.CircularCollectionsTest): 13202 ms
+		// testCircularList(teetime.util.CircularCollectionsTest): 13957 ms
+		// testCircularLongArray(teetime.util.CircularCollectionsTest): 12620 ms
+		// testCircularModIntArray(teetime.util.CircularCollectionsTest): 14015 ms
+	}
+}
-- 
GitLab