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