/******************************************************************************* * Copyright 2015 Analog Devices, Inc. * * 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.analog.lyric.collect; import java.util.Arrays; import java.util.concurrent.Semaphore; import net.jcip.annotations.GuardedBy; import net.jcip.annotations.ThreadSafe; import org.eclipse.jdt.annotation.Nullable; /** * A cache of double[] for temporary use. * <p> * Use this to manage double arrays to avoid allocating and gc'ing many temporary * arrays. * <p> * Currently, this cache only supports allocating arrays with a minimum size, not an exact size. * This allows it to simply return the appropriate power-of-two sized array, which it can look * up very quickly. * <p> * @since 0.08 * @author Christopher Barber * @see IntArrayCache */ @ThreadSafe public final class DoubleArrayCache { private static final double[] SENTINEL = new double[0]; /** * The number of power-of-two slots supported by the cache. */ private static int N_SLOTS = 31; /** * The default maximum number of instances of each array size held in the cache. */ public static int DEFAULT_MAX_INSTANCES = 3; /** * Holds cached array instances of a single size. */ @ThreadSafe private static class CacheSlot { /** * Lock for synchronization. * <p> * We use semaphore with spin locking instead of Java synchronization to avoid overhead of reentrant * locking and implicit try/finally block. */ private final Semaphore _lock; /** * The length of arrays held by this object. */ private final int _arrayLength; /** * The maximum number of cached arrays that can be held by this object. */ private final int _maxSize; /** * The cached array instances. */ @GuardedBy("_lock") private final double[][] _arrays; @GuardedBy("_lock") private int _size; private CacheSlot(int arrayLength, int maxSize) { _lock = new Semaphore(1); _arrayLength = arrayLength; _maxSize = maxSize; // Set initial size to max size but create array instances lazily using empty array as sentinel. _size = maxSize; _arrays = new double[maxSize][]; Arrays.fill(_arrays, SENTINEL); } /** * Grab an array from the cache slot, if one is available. */ private @Nullable double[] pop() { final Semaphore lock = _lock; final double[][] arrays = _arrays; double[] array = null; { lock.acquireUninterruptibly(); int size = _size; if (size > 0) { _size = --size; array = arrays[size]; arrays[size] = null; } lock.release(); } if (array == SENTINEL) { // Lazily create array instance if necessary. array = new double[_arrayLength]; } return array; } /** * Return array to cache slot if there is room and it has the right size. */ private void push(double[] array) { if (array.length == _arrayLength) { final Semaphore lock = _lock; lock.acquireUninterruptibly(); int size = _size; if (size < _maxSize) { _arrays[size] = array; _size = size + 1; } lock.release(); } } } /** * Holds cached array instances indexed by power-of-two. */ private final CacheSlot[] _arrays; /*-------------- * Construction */ /** * Construct new cache with specified max number of instances per each size. * @param maxInstances a positive number specifying the maximum number of arrays of a single * size that may be held in the cache at the same time. * @since 0.08 */ public DoubleArrayCache(int maxInstances) { _arrays = new CacheSlot[N_SLOTS]; for (int i = 0; i < N_SLOTS; ++i) { _arrays[i] = new CacheSlot(1<<i, maxInstances); } } /** * Construct a new cache with default number of instances per size. * <p> * Same as {@link #DoubleArrayCache(int)} with {@code maxInstances} set to {@link #DEFAULT_MAX_INSTANCES}. * @since 0.08 */ public DoubleArrayCache() { this(DEFAULT_MAX_INSTANCES); } /*-------------------------- * DoubleArrayCache methods */ /** * Returns new array of at least {@code minSize} from cache or returns a newly allocated one. * <p> * The caller must not assume that the array has been zeroed out! * <p> * @since 0.08 */ public double[] allocateAtLeast(int minSize) { if (minSize <= 0) { return ArrayUtil.EMPTY_DOUBLE_ARRAY; } double[] array = _arrays[slotForLength(minSize)].pop(); if (array != null) { return array; } else { return new double[minSize]; } } /** * The maximum number of instances of arrays of the same size that can be held by the cache. * @since 0.08 */ public int maxInstancesPerSize() { return _arrays[0]._maxSize; } /** * Returns an array to the cache for reuse. * @since 0.08 */ public void release(double[] array) { final int size = array.length; _arrays[slotForLength(size)].push(array); } /*----------------- * Private methods */ private static int slotForLength(int minLength) { return 32 - Integer.numberOfLeadingZeros(minLength - 1); } }