/* * 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 com.addthis.basis.chars; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; import static com.addthis.basis.chars.AbstractReadOnlyUtfBuf.cacheCharIndex; import static com.addthis.basis.chars.AbstractReadOnlyUtfBuf.cacheByteOffset; import static com.addthis.basis.chars.AbstractReadOnlyUtfBuf.packIndexCache; public class SharedCacheTests { private static int packAndCheck(int chars, int bytes) { int cache = packIndexCache((short) chars, bytes); Assert.assertEquals(chars, cacheByteOffset(cache)); Assert.assertEquals(bytes, cacheCharIndex(cache)); return cache; } @Test public void cachePersist() { packAndCheck(-12, 54); packAndCheck(0, 0); packAndCheck(-1, 0); packAndCheck(0, 1); packAndCheck(-2, 1); } @Test public void negativeShortcut() { Assert.assertTrue(packAndCheck(-12, 12) < 0); Assert.assertTrue(packAndCheck(-2, 0) < 0); Assert.assertTrue(packAndCheck(-2, 1) < 0); Assert.assertTrue(packAndCheck(0, 12) >= 0); Assert.assertTrue(packAndCheck(0, 0) >= 0); Assert.assertTrue(packAndCheck(0, 999) >= 0); } private static class SharedCache { protected int packedIndexCache; // upper half of packedIndexCache; must use a locally stored copy of the cache value to be thread safe // it is packed as a negative value counting down from zero. this lets us do lazier ascii purity queries // nb. it is okay to invert before shifting, but not okay to add due to packed short interactions protected static short cacheCharCount(int cacheInstance) { return (short) ((~cacheInstance >> 16) + 1); } // lower half of packedIndexCache; must use a locally stored copy of the cache value to be thread safe protected static short cacheByteIndex(int cacheInstance) { return (short) cacheInstance; } protected static int packIndexCache(short charCount, short byteIndex) { int newCache = ~((int) charCount) + 1; newCache <<= 16; newCache |= byteIndex; return newCache; } } // Ignored because it doesn't really test anything that can be failed, it is just // here to enable testing of the concurrent behavior of the packed cache if desired. @Test @Ignore public void cacheSharing() throws Throwable { final int iterations = 30000; byte[] bytey = new byte[iterations]; for (int i = 0; i < iterations; i++) { bytey[i] = (byte) (Math.random() * 255); } ExecutorService executor = Executors.newCachedThreadPool(); cacheSharing0(executor, bytey); System.out.println(" "); cacheSharing0(executor, bytey); System.out.println(" "); cacheSharing0(executor, bytey); executor.shutdown(); boolean result = executor.awaitTermination(30, TimeUnit.SECONDS); Assert.assertTrue("executor did not shut down in 30 seconds", result); } public static void cacheSharing0(ExecutorService executor, final byte[] bytey) throws Throwable { final SharedCache sharedCache = new SharedCache(); Future<Long>[] futures = new Future[10]; for (int i = 0; i < futures.length; i++) { futures[i] = executor.submit(new Callable<Long>() { public Long call() throws Exception { int deltas = 0; int totalDeltas = 0; for (int j = 0; j < bytey.length; j++) { int cacheInstance = sharedCache.packedIndexCache; short byteIndex = SharedCache.cacheByteIndex(cacheInstance); short charCount = SharedCache.cacheCharCount(cacheInstance); int delta = j - byteIndex; if (delta == 0) { // totalDeltas += delta; sharedCache.packedIndexCache = packAndCheck((int) charCount, j + 1); } else if (delta > 0) { deltas += 1; totalDeltas += delta; if (bytey[j] < 0) { charCount += 1; } sharedCache.packedIndexCache = packAndCheck((int) charCount, j + 1); } else { deltas += 1; delta = -delta; if (delta > j) { delta = j; } totalDeltas += delta; if (bytey[j] < 0) { charCount += 1; } sharedCache.packedIndexCache = packAndCheck((int) charCount, j + 1); } } long out = deltas; out <<= 32; out |= totalDeltas; return out; } }); } try { for (Future<Long> future : futures) { long pack = future.get(); int deltas = (int) (pack >> 32); int total = (int) pack; double avg = (double) total / deltas; System.out.println("deltas " + deltas + " totalDelta " + total + " avg " + avg); } } catch (ExecutionException ex) { throw ex.getCause(); } } }