This is an automated email from the ASF dual-hosted git repository.
andy pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/jena.git
The following commit(s) were added to refs/heads/main by this push:
new aff37bcad1 GH-2738: Performance improvements to CacheSimple Slight
performance improvements to org.apache.jena.atlas.lib.cache.CacheSimple: -
optimized bit-operations by using a cache size, that is always a power of 2 -
removed already BiConsumer<K, V> dropHandler), which is used nowhere - added
more tests for CacheSimple - fixed a small bug, where putting a key with the
same hash as an existing one does not overridde the existing entry, when the
values are equal. - added new mod [...]
aff37bcad1 is described below
commit aff37bcad1b7454b134859bbb08c843b22236ccc
Author: arne-bdt <[email protected]>
AuthorDate: Sat Sep 28 17:22:56 2024 +0200
GH-2738: Performance improvements to CacheSimple
Slight performance improvements to
org.apache.jena.atlas.lib.cache.CacheSimple:
- optimized bit-operations by using a cache size, that is always a power of
2
- removed already BiConsumer<K, V> dropHandler), which is used nowhere
- added more tests for CacheSimple
- fixed a small bug, where putting a key with the same hash as an existing
one does not overridde the existing entry, when the values are equal.
- added new module jena-benchmarks-shadedJena51 to be able to perform
regression tests againt the previous release
- added JHM test org.apache.jena.atlas.lib.cache.TestCaches in
jena-benchmarks-jmh
- added more detailed javadoc comments to Cache interface
Downside: The fixed cache size must always be a power of 2. If the given
size is already a power of two it will be used as fixed size for the cache,
otherwise the next larger power of two will be used. (e. g. minimumSize = 10
results in 16 as fixed size for the cache)
---
.../main/java/org/apache/jena/atlas/lib/Cache.java | 38 +++-
.../org/apache/jena/atlas/lib/CacheFactory.java | 6 +-
.../apache/jena/atlas/lib/cache/CacheSimple.java | 164 ++++++--------
.../jena/atlas/lib/cache/TestCacheSimple.java | 245 +++++++++++++++++++-
jena-benchmarks/jena-benchmarks-jmh/pom.xml | 7 +
.../apache/jena/atlas/lib/cache/TestCaches.java | 249 +++++++++++++++++++++
.../jena-benchmarks-shadedJena510/pom.xml | 127 +++++++++++
jena-benchmarks/pom.xml | 1 +
8 files changed, 730 insertions(+), 107 deletions(-)
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/Cache.java
b/jena-base/src/main/java/org/apache/jena/atlas/lib/Cache.java
index c794616df6..46dd53c63c 100644
--- a/jena-base/src/main/java/org/apache/jena/atlas/lib/Cache.java
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/Cache.java
@@ -26,6 +26,7 @@ import org.apache.jena.atlas.lib.cache.CacheInfo;
/**
* An abstraction of a cache for basic use.
+ * This cache does not support null as keys or values.
* <p>
* For more complex configuration of the
* cache, use the cache builder of the implementation of choice.
@@ -34,14 +35,23 @@ import org.apache.jena.atlas.lib.cache.CacheInfo;
*/
public interface Cache<Key, Value>
{
- /** Does the cache contain the key? */
+ /**
+ * Does the cache contain the key?
+ * @param key The key to find. The key must not be null.
+ * @return True, if the cache contains the key, otherwise false.
+ */
public boolean containsKey(Key key) ;
- /** Get from cache - or return null. */
+ /**
+ * Get from cache - or return null.
+ * @param key The key for which the value is requested. The key must not
be null.
+ * @return If the cache contains an entry for the given key, the value is
returned, otherwise null.
+ */
public Value getIfPresent(Key key) ;
/** Get from cache; if not present, call the {@link Callable}
* to try to fill the cache. This operation should be atomic.
+ * The 'key' and 'callcable' must not be null.
* @deprecated Use {@link #get(Object, Function)}
*/
@Deprecated(forRemoval = true)
@@ -55,15 +65,31 @@ public interface Cache<Key, Value>
});
}
- /** Get from cache; if not present, call the {@link Function}
- * to fill the cache slot. This operation should be atomic.
+ /**
+ * Get from cache; if not present, call the {@link Function}
+ * to fill the cache slot. This operation should be atomic.
+ * @param key The key, for which the value should be returned or
calculated. The key must not be null.
+ * @param callable If the cache does not contain the key, the callable is
called to calculate a value.
+ * If the callable returns null, the key is not associated
with hat value,
+ * as nulls are not accepted as values.
+ * The callable must not be null.
+ * @return Returns either the existing value or the calculated value.
+ * If callable is called and returns null, then null is returned.
*/
public Value get(Key key, Function<Key, Value> callable) ;
- /** Insert into the cache */
+ /**
+ * Insert into the cache
+ * @param key The key for the 'thing' to store. The key must not be null.
+ * @param thing If 'thing' is null, it will not be used as value,
+ * instead any existing entry with the same key will be
removed.
+ */
public void put(Key key, Value thing) ;
- /** Remove from cache - return true if key referenced an entry */
+ /**
+ * Remove from cache - return true if key referenced an entry
+ * @param key The key, which shall be removed along with its value. The
key must not be null.
+ */
public void remove(Key key) ;
/** Iterate over all keys. Iterating over the keys requires the caller be
thread-safe. */
diff --git
a/jena-base/src/main/java/org/apache/jena/atlas/lib/CacheFactory.java
b/jena-base/src/main/java/org/apache/jena/atlas/lib/CacheFactory.java
index 44a0945e06..7ab9f09f60 100644
--- a/jena-base/src/main/java/org/apache/jena/atlas/lib/CacheFactory.java
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/CacheFactory.java
@@ -18,9 +18,9 @@
package org.apache.jena.atlas.lib ;
-import java.util.function.BiConsumer;
+import org.apache.jena.atlas.lib.cache.*;
-import org.apache.jena.atlas.lib.cache.* ;
+import java.util.function.BiConsumer;
public class CacheFactory {
/**
@@ -80,7 +80,7 @@ public class CacheFactory {
* This cache is not thread-safe.
*/
public static <Key, Value> Cache<Key, Value> createSimpleCache(int size) {
- return new CacheSimple<>(size, null) ;
+ return new CacheSimple<>(size) ;
}
/** One slot cache */
diff --git
a/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CacheSimple.java
b/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CacheSimple.java
index b339e16440..30a8059dc2 100644
--- a/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CacheSimple.java
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CacheSimple.java
@@ -23,16 +23,14 @@ import static java.util.Arrays.asList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Objects;
-import java.util.concurrent.Callable;
-import java.util.function.BiConsumer;
import java.util.function.Function;
-import org.apache.jena.atlas.AtlasException;
import org.apache.jena.atlas.iterator.Iter;
import org.apache.jena.atlas.lib.Cache;
/**
* A simple fixed size cache that uses the hash code to address a slot.
+ * The size is always a power of two, to be able to use optimized
bit-operations.
* The clash policy is to overwrite.
* <p>
* The cache has very low overhead - there is no object creation during lookup
or insert.
@@ -42,15 +40,23 @@ import org.apache.jena.atlas.lib.Cache;
public class CacheSimple<K, V> implements Cache<K, V> {
private final V[] values;
private final K[] keys;
- private final int size;
+ private final int sizeMinusOne;
private int currentSize = 0;
- private BiConsumer<K, V> dropHandler = null;
- public CacheSimple(int size) {
- this(size, null);
- }
+ /**
+ * Constructs a fixes size cache.
+ * The size is always a power of two, to be able to use optimized
bit-operations.
+ * @param miniumSize If the size is already a power of two it will be
used as fixed size for the cache,
+ * otherwise the next larger power of two will be used.
+ * (e.g. minimumSize = 10 results in 16 as fixed size
for the cache)
+ */
+ public CacheSimple(int miniumSize) {
+ var size = Integer.highestOneBit(miniumSize);
+ if (size < miniumSize){
+ size <<= 1;
+ }
+ this.sizeMinusOne = size-1;
- public CacheSimple(int size, BiConsumer<K, V> dropHandler) {
@SuppressWarnings("unchecked")
V[] x = (V[])new Object[size];
values = x;
@@ -58,8 +64,6 @@ public class CacheSimple<K, V> implements Cache<K, V> {
@SuppressWarnings("unchecked")
K[] z = (K[])new Object[size];
keys = z;
- this.dropHandler = dropHandler;
- this.size = size;
}
@Override
@@ -73,129 +77,95 @@ public class CacheSimple<K, V> implements Cache<K, V> {
@Override
public boolean containsKey(K key) {
Objects.requireNonNull(key);
- return index(key) >= 0 ;
+ return key.equals(keys[calcIndex(key)]);
}
- // Return key index (>=0): return -(index+1) if the key slot is empty.
- private final int index(K key) {
- int x = (key.hashCode() & 0x7fffffff) % size;
- if ( key.equals(keys[x]) )
- return x;
- return -x - 1;
- }
-
- // Convert to a slot index.
- private final int decode(int x) {
- if ( x >= 0 )
- return x;
- return -(x+1);
+ private int calcIndex(K key) {
+ return key.hashCode() & sizeMinusOne;
}
@Override
public V getIfPresent(K key) {
Objects.requireNonNull(key);
- int x = index(key);
- if ( x < 0 )
- return null;
- return values[x];
+ final int idx = calcIndex(key);
+ if (key.equals(keys[idx])) {
+ return values[idx];
+ }
+ return null;
}
@Override
public V get(K key, Function<K, V> function) {
- return getOrFillNoSync(this, key, function);
- }
-
- /**
- * Implementation of getOrFill based on Cache.get and Cache.put
- * This function is not thread safe.
- */
- public static <K,V> V getOrFillNoSync(Cache<K,V> cache, K key,
Function<K,V> function) {
- V value = cache.getIfPresent(key) ;
- if ( value == null ) {
- try { value = function.apply(key) ; }
- catch (RuntimeException ex) { throw ex; }
- catch (Exception e) {
- throw new AtlasException("Exception on cache fill", e) ;
- }
- if ( value != null )
- cache.put(key, value) ;
- }
- return value ;
- }
-
- /**
- * Implementation of getOrFill based on Cache.get and Cache.put
- * This function is not thread safe.
- */
- public static <K,V> V getOrFillNoSync(Cache<K,V> cache, K key, Callable<V>
callable) {
- V value = cache.getIfPresent(key) ;
- if ( value == null ) {
- try { value = callable.call() ; }
- catch (RuntimeException ex) { throw ex; }
- catch (Exception e) {
- throw new AtlasException("Exception on cache fill", e) ;
+ Objects.requireNonNull(key);
+ Objects.requireNonNull(function);
+ final int idx = calcIndex(key);
+ final boolean isExistingKeyNotNull = keys[idx] != null;
+ if(isExistingKeyNotNull && keys[idx].equals(key)) {
+ return values[idx];
+ } else {
+ final var value = function.apply(key);
+ if(value != null) {
+ values[idx] = value;
+ if(!isExistingKeyNotNull) {
+ currentSize++;
+ }
+ keys[idx] = key;
}
- if ( value != null )
- cache.put(key, value) ;
+ return value;
}
- return value ;
}
@Override
public void put(K key, V thing) {
- // thing may be null.
- int x = index(key);
- x = decode(x);
- V old = values[x];
- // Drop the old K->V
- if ( old != null ) {
- if ( old.equals(thing) )
- // Replace like-with-like.
- return;
- if ( dropHandler != null )
- dropHandler.accept(keys[x], old);
- currentSize--;
- //keys[x] = null;
- //values[x] = null;
+ Objects.requireNonNull(key);
+ final int idx = calcIndex(key);
+ if(thing == null) { //null value causes removal of entry
+ if (keys[idx] != null) {
+ keys[idx] = null;
+ values[idx] = null;
+ currentSize--;
+ }
+ return;
}
-
- // Already decremented if we are overwriting a full slot.
- values[x] = thing;
- if ( thing == null ) {
- // put(,null) is a remove.
- keys[x] = null;
- } else {
- currentSize++;
- keys[x] = key;
+ if(!thing.equals(values[idx])) {
+ values[idx] = thing;
+ }
+ if(!key.equals(keys[idx])) {
+ if(keys[idx] == null) { //add value
+ currentSize++;
+ }
+ keys[idx] = key;
}
}
@Override
public void remove(K key) {
- put(key, null);
+ Objects.requireNonNull(key);
+ final int idx = calcIndex(key);
+ if (key.equals(keys[idx])) {
+ keys[idx] = null;
+ values[idx] = null;
+ currentSize--;
+ }
}
@Override
public long size() {
return currentSize;
-// long x = 0;
-// for ( int i = 0 ; i < size ; i++ ) {
-// K key = keys[i];
-// if ( key != null )
-// x++;
-// }
-// return x;
}
@Override
public Iterator<K> keys() {
- Iterator<K> iter = asList(keys).iterator();
- return Iter.removeNulls(iter);
+ return Iter.iter(asList(keys)).filter(Objects::nonNull);
}
@Override
public boolean isEmpty() {
return currentSize == 0;
}
+
+ int getAllocatedSize() {
+ return keys.length;
+ }
}
diff --git
a/jena-base/src/test/java/org/apache/jena/atlas/lib/cache/TestCacheSimple.java
b/jena-base/src/test/java/org/apache/jena/atlas/lib/cache/TestCacheSimple.java
index 90d7330485..81c0c0f427 100644
---
a/jena-base/src/test/java/org/apache/jena/atlas/lib/cache/TestCacheSimple.java
+++
b/jena-base/src/test/java/org/apache/jena/atlas/lib/cache/TestCacheSimple.java
@@ -36,7 +36,7 @@ public class TestCacheSimple {
*/
@Test
public void testFixedSize() {
- final int maxSize = 5;
+ final int maxSize = 8;
final int submittedEntries = 10;
final Cache<Integer, Object> testCache = new CacheSimple<>(maxSize);
rangeClosed(1, submittedEntries)
@@ -82,6 +82,249 @@ public class TestCacheSimple {
assertTrue("Equal key, expected to be found", cache.containsKey(key2));
}
+ @Test
+ public void testPutSameHashOverridesValue() {
+ CompoundKey key1 = new CompoundKey(1, 1);
+ CompoundKey key2 = new CompoundKey(1, 2);
+ assertEquals(key1.hashCode(), key2.hashCode());
+ assertNotEquals(key1, key2);
+
+ var value1 = "value1";
+ var value2 = "value2";
+
+ Cache<CompoundKey, String> cache = new CacheSimple<>(10);
+ assertEquals(0, cache.size());
+ cache.put(key1, value1);
+ assertEquals(1, cache.size());
+ assertEquals(value1, cache.getIfPresent(key1));
+ assertNull(cache.getIfPresent(key2));
+
+ //this should override the slot
+ cache.put(key2, value2);
+ assertEquals(1, cache.size());
+ assertNull(cache.getIfPresent(key1));
+ assertEquals(value2, cache.getIfPresent(key2));
+ }
+
+ @Test
+ public void testPutSameHashOverridesSameValue() {
+ CompoundKey key1 = new CompoundKey(1, 1);
+ CompoundKey key2 = new CompoundKey(1, 2);
+ assertEquals(key1.hashCode(), key2.hashCode());
+ assertNotEquals(key1, key2);
+
+ var value1 = "value";
+ var value2 = "value";
+
+ Cache<CompoundKey, String> cache = new CacheSimple<>(10);
+ assertEquals(0, cache.size());
+ cache.put(key1, value1);
+ assertEquals(1, cache.size());
+ assertEquals(value1, cache.getIfPresent(key1));
+ assertNull(cache.getIfPresent(key2));
+
+ //this should override the slot
+ cache.put(key2, value2);
+ assertEquals(1, cache.size());
+ assertNull(cache.getIfPresent(key1));
+ assertEquals(value2, cache.getIfPresent(key2));
+ }
+
+ @Test
+ public void testGetSameHashOverridesValue() {
+ CompoundKey key1 = new CompoundKey(1, 1);
+ CompoundKey key2 = new CompoundKey(1, 2);
+ assertEquals(key1.hashCode(), key2.hashCode());
+ assertNotEquals(key1, key2);
+
+ var value1 = "value1";
+ var value2 = "value2";
+
+ Cache<CompoundKey, String> cache = new CacheSimple<>(10);
+ assertEquals(0, cache.size());
+ cache.get(key1, k -> value1);
+ assertEquals(1, cache.size());
+ assertEquals(value1, cache.getIfPresent(key1));
+ assertNull(cache.getIfPresent(key2));
+
+ //this should override the slot
+ cache.get(key2, k -> value2);
+ assertEquals(1, cache.size());
+ assertNull(cache.getIfPresent(key1));
+ assertEquals(value2, cache.getIfPresent(key2));
+ }
+
+ @Test
+ public void removeKeyByPutingNullValue() {
+ Cache<String, String> cache = new CacheSimple<>(10);
+ assertEquals(0, cache.size());
+
+ {
+ final var key = "key0";
+ final var value = "value0";
+
+ cache.put(key, value);
+
+ assertTrue(cache.containsKey(key));
+ assertEquals(value, cache.getIfPresent(key));
+ assertEquals(1, cache.size());
+
+ //removing entry by writing null value
+ cache.put(key, null);
+
+ assertEquals(0, cache.size());
+ assertFalse(cache.containsKey(key));
+ assertNull(value, cache.getIfPresent(key));
+ }
+ }
+
+ @Test
+ public void testRemove() {
+ Cache<String, String> cache = new CacheSimple<>(10);
+ assertEquals(0, cache.size());
+
+ {
+ final var key = "key0";
+ final var value = "value0";
+
+ //trying to remove non-existing key
+ cache.remove(key);
+ assertEquals(0, cache.size());
+ assertFalse(cache.containsKey(key));
+ assertNull(value, cache.getIfPresent(key));
+
+ cache.put(key, value);
+
+ assertTrue(cache.containsKey(key));
+ assertEquals(value, cache.getIfPresent(key));
+ assertEquals(1, cache.size());
+
+ //removing entry by writing null value
+ cache.remove(key);
+
+ assertEquals(0, cache.size());
+ assertFalse(cache.containsKey(key));
+ assertNull(value, cache.getIfPresent(key));
+ }
+ }
+
+ @Test
+ public void testGet() {
+ Cache<String, String> cache = new CacheSimple<>(10);
+ assertEquals(0, cache.size());
+
+ final var key = "key0";
+ final var value = "value0";
+
+ assertFalse(cache.containsKey(key));
+ assertNull(cache.getIfPresent(key));
+
+ assertEquals(value, cache.get(key, k -> value));
+
+ assertTrue(cache.containsKey(key));
+ assertEquals(value, cache.getIfPresent(key));
+ assertEquals(1, cache.size());
+ }
+
+ @Test
+ public void testGetWithSameValue() {
+ Cache<String, String> cache = new CacheSimple<>(10);
+ assertEquals(0, cache.size());
+
+ final var key = "key1";
+ final var value = "value1";
+
+ assertFalse(cache.containsKey(key));
+ assertNull(cache.getIfPresent(key));
+
+ cache.put(key, value);
+ //get with same value
+ assertEquals(value, cache.get(key, k -> value));
+
+ assertTrue(cache.containsKey(key));
+ assertEquals(value, cache.getIfPresent(key));
+ assertEquals(1, cache.size());
+ }
+
+ @Test
+ public void testGetWithDifferentValue() {
+ Cache<String, String> cache = new CacheSimple<>(10);
+ assertEquals(0, cache.size());
+
+ final var key = "key2";
+ final var value = "value2";
+ final var differentValue = "differentValue2";
+
+ assertFalse(cache.containsKey(key));
+ assertNull(cache.getIfPresent(key));
+
+ cache.put(key, value);
+ //get with different value should not override existing value
+ assertEquals(value, cache.get(key, k -> differentValue));
+
+ assertTrue(cache.containsKey(key));
+ assertEquals(value, cache.getIfPresent(key));
+ assertEquals(1, cache.size());
+ }
+
+ @Test
+ public void testGetExistingWithNullValue() {
+ Cache<String, String> cache = new CacheSimple<>(10);
+ assertEquals(0, cache.size());
+
+ final var key = "key3";
+ final var value = "value3";
+ assertFalse(cache.containsKey(key));
+ assertNull(cache.getIfPresent(key));
+
+ cache.put(key, value);
+
+ assertEquals(1, cache.size());
+ //returning null should not change anyting
+ assertEquals(value, cache.get(key, k -> null));
+ assertEquals(1, cache.size());
+
+ assertTrue(cache.containsKey(key));
+ assertEquals(value, cache.getIfPresent(key));
+ }
+
+ @Test
+ public void testGetNonExistingWithNullValue() {
+ Cache<String, String> cache = new CacheSimple<>(10);
+ assertEquals(0, cache.size());
+
+ final var key = "key4";
+
+ assertFalse(cache.containsKey(key));
+ assertNull(cache.getIfPresent(key));
+ assertEquals(0, cache.size());
+ assertNull(cache.get(key, k -> null));
+ assertEquals(0, cache.size());
+ assertFalse(cache.containsKey(key));
+ assertNull(cache.getIfPresent(key));
+ }
+
+ @Test
+ public void testAllocatedSize() {
+ var cache = new CacheSimple<>(2);
+ assertEquals(2, cache.getAllocatedSize());
+
+ cache = new CacheSimple<>(3);
+ assertEquals(4, cache.getAllocatedSize());
+
+ cache = new CacheSimple<>(4);
+ assertEquals(4, cache.getAllocatedSize());
+
+ cache = new CacheSimple<>(6);
+ assertEquals(8, cache.getAllocatedSize());
+
+ cache = new CacheSimple<>(8);
+ assertEquals(8, cache.getAllocatedSize());
+
+ cache = new CacheSimple<>(10);
+ assertEquals(16, cache.getAllocatedSize());
+ }
+
// Compound key for tests.
private static final class CompoundKey {
private final int a;
diff --git a/jena-benchmarks/jena-benchmarks-jmh/pom.xml
b/jena-benchmarks/jena-benchmarks-jmh/pom.xml
index df68195caa..39891d5f4a 100644
--- a/jena-benchmarks/jena-benchmarks-jmh/pom.xml
+++ b/jena-benchmarks/jena-benchmarks-jmh/pom.xml
@@ -86,6 +86,13 @@
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.jena</groupId>
+ <artifactId>jena-benchmarks-shadedJena510</artifactId>
+ <version>5.2.0-SNAPSHOT</version>
+ <scope>test</scope>
+ </dependency>
+
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
diff --git
a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/atlas/lib/cache/TestCaches.java
b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/atlas/lib/cache/TestCaches.java
new file mode 100644
index 0000000000..08d37f4b54
--- /dev/null
+++
b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/atlas/lib/cache/TestCaches.java
@@ -0,0 +1,249 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.jena.atlas.lib.cache;
+
+import org.apache.jena.atlas.lib.Cache;
+import org.apache.jena.atlas.lib.CacheFactory;
+import org.apache.jena.graph.Graph;
+import org.apache.jena.graph.Node;
+import org.apache.jena.mem.graph.helper.JMHDefaultOptions;
+import org.apache.jena.riot.RDFDataMgr;
+import org.apache.jena.sparql.graph.GraphFactory;
+import org.junit.Assert;
+import org.junit.Test;
+import org.openjdk.jmh.annotations.*;
+import org.openjdk.jmh.runner.Runner;
+
+import java.util.Iterator;
+import java.util.function.Function;
+
+@State(Scope.Benchmark)
+public class TestCaches {
+
+ final static int DEFAULT_CACHE_SIZE = 1_024_000;
+
+ @Param({
+ "../testing/cheeses-0.1.ttl",
+ "../testing/pizza.owl.rdf",
+// "../testing/BSBM/bsbm-5m.nt.gz",
+ })
+ public String param0_GraphUri;
+
+ @Param({
+ "Caffeine",
+ "Simple",
+ "Jena510.Caffeine",
+ "Jena510.Simple"
+ })
+ public String param1_Cache;
+
+ private Graph graph;
+
+ private Cache<String, Node> cache;
+
+ private static int calculateRealCacheSize(int minCacheSize) {
+ // the cache size is a power of 2 --> that is a requirement for the
CacheSimpleFast
+ // to start with fair conditions for the caches, we use the same size
for all caches
+ var cacheSize = Integer.highestOneBit(minCacheSize);
+ if (cacheSize < minCacheSize) {
+ cacheSize <<= 1;
+ }
+ return cacheSize;
+ }
+
+ private static Cache<String, Node> createCache(String cacheName) {
+ var cacheSize = calculateRealCacheSize(DEFAULT_CACHE_SIZE);
+ switch (cacheName) {
+ case "Caffeine":
+ return CacheFactory.createCache(cacheSize);
+ case "Simple":
+ return CacheFactory.createSimpleCache(cacheSize);
+ case "Jena510.Caffeine":
+ return new
CacheFromJena510Wrapped<>(org.apache.shadedJena510.atlas.lib.CacheFactory.createCache(cacheSize));
+ case "Jena510.Simple":
+ return new
CacheFromJena510Wrapped<>(org.apache.shadedJena510.atlas.lib.CacheFactory.createSimpleCache(cacheSize));
+ default:
+ throw new IllegalArgumentException("Unknown Cache: " +
cacheName);
+ }
+ }
+
+ @Benchmark
+ public int getFromFilledCache() {
+ final int[] hash = {0};
+ graph.find().forEachRemaining(t -> {
+ if(t.getSubject().isURI()) {
+ hash[0] += cache.get(t.getSubject().getURI(),
+ s -> t.getSubject()).getURI().hashCode();
+
+ }
+ if(t.getPredicate().isURI()) {
+ hash[0] += cache.get(t.getPredicate().getURI(),
+ s -> t.getPredicate()).getURI().hashCode();
+
+ }
+ if(t.getObject().isURI()) {
+ hash[0] += cache.get(t.getObject().getURI(),
+ s -> t.getObject()).getURI().hashCode();
+
+ }
+ });
+ return hash[0];
+ }
+
+ @Benchmark
+ public int getIfPresentFromFilledCache() {
+ final int[] hash = {0};
+ graph.find().forEachRemaining(t -> {
+ if(t.getSubject().isURI()) {
+ final var value = cache.getIfPresent(t.getSubject().getURI());
+ if(value != null)
+ hash[0] += value.getURI().hashCode();
+
+ }
+ if(t.getPredicate().isURI()) {
+ final var value =
cache.getIfPresent(t.getPredicate().getURI());
+ if(value != null)
+ hash[0] += value.getURI().hashCode();
+ }
+ if(t.getObject().isURI()) {
+ final var value = cache.getIfPresent(t.getObject().getURI());
+ if(value != null)
+ hash[0] += value.getURI().hashCode();
+ }
+ });
+ return hash[0];
+ }
+
+ @Benchmark
+ public Cache<String, Node> createAndFillCacheByGet() {
+ var c = createCache(param1_Cache);
+ fillCacheByGet(c, graph);
+ return c;
+ }
+
+ @Benchmark
+ public Cache<String, Node> createAndFillCacheByPut() {
+ var c = createCache(param1_Cache);
+ fillCacheByPut(c, graph);
+ return c;
+ }
+
+ private static void fillCacheByGet(Cache<String, Node> cacheToFill, Graph
g) {
+ g.find().forEachRemaining(t -> {
+ if(t.getSubject().isURI()) {
+ cacheToFill.get(t.getSubject().getURI(), s -> t.getSubject());
+ }
+ if(t.getPredicate().isURI()) {
+ cacheToFill.get(t.getPredicate().getURI(), s ->
t.getPredicate());
+ }
+ if(t.getObject().isURI()) {
+ cacheToFill.get(t.getObject().getURI(), s -> t.getObject());
+ }
+ });
+ }
+
+ private static void fillCacheByPut(Cache<String, Node> cacheToFill, Graph
g) {
+ g.find().forEachRemaining(t -> {
+ if(t.getSubject().isURI()) {
+ cacheToFill.put(t.getSubject().getURI(), t.getSubject());
+ }
+ if(t.getPredicate().isURI()) {
+ cacheToFill.put(t.getPredicate().getURI(), t.getPredicate());
+ }
+ if(t.getObject().isURI()) {
+ cacheToFill.put(t.getObject().getURI(), t.getObject());
+ }
+ });
+ }
+
+ private static class CacheFromJena510Wrapped<K, V> implements Cache<K, V> {
+
+ private final org.apache.shadedJena510.atlas.lib.Cache<K, V>
wrappedCache;
+
+ public
CacheFromJena510Wrapped(org.apache.shadedJena510.atlas.lib.Cache<K, V>
cacheFromJena510) {
+ this.wrappedCache = cacheFromJena510;
+ }
+
+
+ @Override
+ public boolean containsKey(K k) {
+ return wrappedCache.containsKey(k);
+ }
+
+ @Override
+ public V getIfPresent(K k) {
+ return wrappedCache.getIfPresent(k);
+ }
+
+ @Override
+ public V get(K k, Function<K, V> callable) {
+ return wrappedCache.get(k, callable);
+ }
+
+ @Override
+ public void put(K k, V thing) {
+ wrappedCache.put(k, thing);
+ }
+
+ @Override
+ public void remove(K k) {
+ wrappedCache.remove(k);
+ }
+
+ @Override
+ public Iterator<K> keys() {
+ return wrappedCache.keys();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return wrappedCache.isEmpty();
+ }
+
+ @Override
+ public void clear() {
+ wrappedCache.clear();
+ }
+
+ @Override
+ public long size() {
+ return wrappedCache.size();
+ }
+ }
+
+ @Setup(Level.Trial)
+ public void setupTrial() throws Exception {
+ this.graph = GraphFactory.createGraphMem();
+ RDFDataMgr.read(this.graph, this.param0_GraphUri);
+ }
+
+ @Setup(Level.Iteration)
+ public void setupIteration() {
+ this.cache = createCache(param1_Cache);
+ fillCacheByGet(this.cache, this.graph);
+ }
+
+ @Test
+ public void benchmark() throws Exception {
+ var opt = JMHDefaultOptions.getDefaults(this.getClass())
+ .build();
+ var results = new Runner(opt).run();
+ Assert.assertNotNull(results);
+ }
+}
diff --git a/jena-benchmarks/jena-benchmarks-shadedJena510/pom.xml
b/jena-benchmarks/jena-benchmarks-shadedJena510/pom.xml
new file mode 100644
index 0000000000..e2f8cf9da8
--- /dev/null
+++ b/jena-benchmarks/jena-benchmarks-shadedJena510/pom.xml
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.jena</groupId>
+ <artifactId>jena-benchmarks</artifactId>
+ <version>5.2.0-SNAPSHOT</version>
+ </parent>
+
+ <name>Apache Jena - Benchmarks Shaded Jena 5.1.0</name>
+ <artifactId>jena-benchmarks-shadedJena510</artifactId>
+ <description>Shaded Apache Jena 5.1.0 for regression
benchmarks</description>
+ <packaging>jar</packaging>
+
+ <url>https://jena.apache.org/</url>
+
+ <organization>
+ <name>Apache Jena</name>
+ <url>https://jena.apache.org/</url>
+ </organization>
+
+ <licenses>
+ <license>
+ <name>Apache 2.0 License</name>
+ <url>https://www.apache.org/licenses/LICENSE-2.0</url>
+ </license>
+ </licenses>
+
+ <properties>
+ <build.time.xsd>${maven.build.timestamp}</build.time.xsd>
+
<automatic.module.name>org.apache.jena.benchmarks.shadedJena480</automatic.module.name>
+ <maven.test.skip>true</maven.test.skip>
+ <scope>test</scope>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.jena</groupId>
+ <artifactId>jena-base</artifactId>
+ <version>5.1.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.jena</groupId>
+ <artifactId>jena-iri</artifactId>
+ <version>5.1.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.jena</groupId>
+ <artifactId>jena-core</artifactId>
+ <version>5.1.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.jena</groupId>
+ <artifactId>jena-arq</artifactId>
+ <version>5.1.0</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-shade-plugin</artifactId>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>shade</goal>
+ </goals>
+ <configuration>
+
<createDependencyReducedPom>false</createDependencyReducedPom>
+ <transformers>
+ <transformer
implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"
/>
+ <transformer
implementation="org.apache.maven.plugins.shade.resource.ApacheLicenseResourceTransformer"
/>
+ <transformer
implementation="org.apache.maven.plugins.shade.resource.ApacheNoticeResourceTransformer">
+ <addHeader>false</addHeader>
+ </transformer>
+ </transformers>
+ <artifactSet>
+ <includes>
+ <include>*:*</include>
+ </includes>
+ </artifactSet>
+ <relocations>
+ <relocation>
+ <pattern>org.apache.jena</pattern>
+
<shadedPattern>org.apache.shadedJena510</shadedPattern>
+ </relocation>
+ </relocations>
+ <filters>
+ <filter>
+ <artifact>*:*</artifact>
+ <excludes>
+ <exclude>META-INF/*.SF</exclude>
+ <exclude>META-INF/*.DSA</exclude>
+ <exclude>META-INF/*.RSA</exclude>
+
<exclude>META-INF/DEPENDENCIES</exclude>
+ <exclude>META-INF/MANIFEST.MF</exclude>
+ <exclude>**/module-info.class</exclude>
+ </excludes>
+ </filter>
+ </filters>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
\ No newline at end of file
diff --git a/jena-benchmarks/pom.xml b/jena-benchmarks/pom.xml
index b7dacaa83c..81226cd19f 100644
--- a/jena-benchmarks/pom.xml
+++ b/jena-benchmarks/pom.xml
@@ -49,6 +49,7 @@
<modules>
<module>jena-benchmarks-jmh</module>
<module>jena-benchmarks-shadedJena480</module>
+ <module>jena-benchmarks-shadedJena510</module>
</modules>
</project>