All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.simiacryptus.mindseye.lang.RecycleBin Maven / Gradle / Ivy

/*
 * Copyright (c) 2018 by Andrew Charneski.
 *
 * The author 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 com.simiacryptus.mindseye.lang;

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.*;
import java.util.function.Supplier;

import static com.simiacryptus.mindseye.lang.PersistanceMode.WEAK;

/**
 * This is a recycling mechanism to reuse short-term-lifecycle T objects of regular length. It is a convenience
 * mechanism to optimize tight loops which would otherwise require careful and complex coding to minimize allocations
 * and avoid excessive GC load
 *
 * @param  the type parameter
 */
public abstract class RecycleBin {

  /**
   * The constant DOUBLES.
   */
  public static final RecycleBin DOUBLES = new RecycleBin() {
    @Override
    protected void free(double[] obj) {
    }

    @Override
    public double[] create(final long length) {
      return new double[(int) length];
    }

    @Override
    public void reset(@Nonnull final double[] data, long size) {
      assert data.length == size;
      Arrays.fill(data, 0);
    }
  }.setPersistanceMode(CoreSettings.INSTANCE().getDoubleCacheMode());
  /**
   * The constant logger.
   */
  protected static final Logger logger = LoggerFactory.getLogger(RecycleBin.class);
  private static volatile ScheduledExecutorService garbageTruck;
  private final Map> buckets = new ConcurrentHashMap<>();
  private final StackCounter allocations = new StackCounter();
  private final StackCounter frees = new StackCounter();
  private final StackCounter recycle_put = new StackCounter();
  private final StackCounter recycle_get = new StackCounter();
  private int purgeFreq;
  private int profilingThreshold = Integer.MAX_VALUE;
  private PersistanceMode persistanceMode = WEAK;
  private int minLengthPerBuffer = 16;
  private double maxLengthPerBuffer = 1e9;
  private int maxItemsPerBuffer = 100;

  /**
   * Instantiates a new Recycle bin.
   */
  protected RecycleBin() {
    super();
    purgeFreq = 10;
    RecycleBin.getGarbageTruck().scheduleAtFixedRate(() -> {
      buckets.forEach((k, v) -> {
        ObjectWrapper poll;
        ArrayList young = new ArrayList<>();
        while (null != (poll = v.poll())) {
          if (poll.age() > purgeFreq) {
            T obj = poll.obj.get();
            if (obj != null) {
              freeItem(obj, k);
            }
          } else {
            young.add(poll);
          }
        }
        v.addAll(young);
      });
    }, purgeFreq, purgeFreq, TimeUnit.SECONDS);
  }

  /**
   * Gets garbage truck.
   *
   * @return the garbage truck
   */
  public static ScheduledExecutorService getGarbageTruck() {
    if (null == RecycleBin.garbageTruck) {
      synchronized (RecycleBin.class) {
        if (null == RecycleBin.garbageTruck) {
          RecycleBin.garbageTruck = Executors.newScheduledThreadPool(1, new ThreadFactoryBuilder().setDaemon(true).build());
        }
      }
    }
    return RecycleBin.garbageTruck;
  }

  /**
   * Equals boolean.
   *
   * @param a the a
   * @param b the b
   * @return the boolean
   */
  public static boolean equals(@Nullable Object a, @Nullable Object b) {
    if (a == b) return true;
    if (a == null || b == null) return false;
    return a.equals(b);
  }

  /**
   * Clear.
   *
   * @return the long
   */
  public long clear() {
    return buckets.keySet().stream().mapToLong(length -> buckets.remove(length).stream().mapToLong(ref -> {
      return freeItem(ref.obj.get(), length);
    }).sum()).sum();
  }

  /**
   * Free.
   *
   * @param obj  the obj
   * @param size the size
   * @return the long
   */
  protected long freeItem(T obj, long size) {
    @Nullable StackCounter stackCounter = getFrees(size);
    if (null != stackCounter) {
      stackCounter.increment(size);
    }
    if (null != obj) free(obj);
    return size;
  }

  /**
   * Free.
   *
   * @param obj the obj
   */
  protected abstract void free(T obj);

  /**
   * Obtain double [ ].
   *
   * @param length the length
   * @return the double [ ]
   */
  public T obtain(final long length) {
    final ConcurrentLinkedDeque bin = buckets.get(length);
    @Nullable StackCounter stackCounter = getRecycle_get(length);
    if (null != stackCounter) {
      stackCounter.increment(length);
    }
    if (null != bin) {
      final ObjectWrapper ref = bin.poll();
      if (null != ref) {
        final T data = ref.obj.get();
        if (null != data) {
          reset(data, length);
          return data;
        }
      }
    }
    return create(length, 1);
  }

  /**
   * Copy of double [ ].
   *
   * @param original the original
   * @param size     the size
   * @return the double [ ]
   */
  @Nullable
  public T copyOf(@Nullable final T original, long size) {
    if (null == original) return null;
    final T copy = obtain(size);
    System.arraycopy(original, 0, copy, 0, (int) size);
    return copy;
  }

  /**
   * Create t.
   *
   * @param length the length
   * @return the t
   */
  @Nonnull
  public abstract T create(long length);

  /**
   * Gets allocations.
   *
   * @param length the length
   * @return the allocations
   */
  @Nullable
  public StackCounter getAllocations(final long length) {
    if (!isProfiling(length)) return null;
    return allocations;
  }

  /**
   * Gets frees.
   *
   * @param length the length
   * @return the frees
   */
  @Nullable
  public StackCounter getFrees(final long length) {
    if (!isProfiling(length)) return null;
    return frees;
  }

  /**
   * Gets recycle get.
   *
   * @param length the length
   * @return the recycle get
   */
  @Nullable
  public StackCounter getRecycle_put(final long length) {
    if (!isProfiling(length)) return null;
    return recycle_put;
  }

  /**
   * Gets recycle submit.
   *
   * @param length the length
   * @return the recycle submit
   */
  @Nullable
  public StackCounter getRecycle_get(final long length) {
    if (!isProfiling(length)) return null;
    return recycle_get;
  }

  /**
   * Is profiling boolean.
   *
   * @param length the length
   * @return the boolean
   */
  public boolean isProfiling(final long length) {
    return length > profilingThreshold;
  }

  /**
   * Print all profiling.
   *
   * @param out the out
   */
  public void printAllProfiling(@Nonnull final PrintStream out) {
    printDetailedProfiling(out);
    printNetProfiling(out);
  }

  /**
   * Print detailed profiling.
   *
   * @param out the out
   */
  public void printDetailedProfiling(@Nonnull final PrintStream out) {
    if (null != allocations) {
      out.println("Memory Allocation Profiling:\n\t" + allocations.toString().replaceAll("\n", "\n\t"));
    }
    if (null != frees) {
      out.println("Freed Memory Profiling:\n\t" + frees.toString().replaceAll("\n", "\n\t"));
    }
    if (null != recycle_put) {
      out.println("Recycle Bin (Put) Profiling:\n\t" + recycle_put.toString().replaceAll("\n", "\n\t"));
    }
    if (null != recycle_get) {
      out.println("Recycle Bin (Get) Profiling:\n\t" + recycle_get.toString().replaceAll("\n", "\n\t"));
    }
  }

  /**
   * Print net profiling.
   *
   * @param out the out
   */
  public void printNetProfiling(@Nullable final PrintStream out) {
    if (null != out && null != recycle_put && null != recycle_get) {
      out.println("Recycle Bin (Net) Profiling:\n\t" +
          StackCounter.toString(recycle_put, recycle_get, (a, b) -> a.getSum() - b.getSum())
              .replaceAll("\n", "\n\t"));
    }
  }

  /**
   * Recycle.
   *
   * @param data the data
   * @param size the size
   */
  public void recycle(@Nullable final T data, long size) {
    if (null != data && size >= getMinLengthPerBuffer() && size <= getMaxLengthPerBuffer()) {
      @Nullable StackCounter stackCounter = getRecycle_put(size);
      if (null != stackCounter) {
        stackCounter.increment(size);
      }
      ConcurrentLinkedDeque bin = getBin(size);
      if (bin.size() < Math.min(Math.max(1, (int) (getMaxLengthPerBuffer() / size)), getMaxItemsPerBuffer())) {
        synchronized (bin) {
          if (!bin.stream().filter(x -> equals(x.obj.get(), data)).findAny().isPresent()) {
            bin.add(new ObjectWrapper(wrap(data)));
            return;
          }
        }
      }
    }
    freeItem(data, size);
  }

  /**
   * Create t.
   *
   * @param length  the length
   * @param retries the retries
   * @return the t
   */
  @Nonnull
  public T create(long length, int retries) {
    try {
      @Nonnull T result = create(length);
      @Nullable StackCounter stackCounter = getAllocations(length);
      if (null != stackCounter) {
        stackCounter.increment(length);
      }
      return result;
    } catch (@Nonnull final OutOfMemoryError e) {
      if (retries <= 0) throw e;
    }
    clearMemory(length);
    return create(length, retries - 1);
  }

  private void clearMemory(long length) {
    long max = Runtime.getRuntime().maxMemory();
    long previous = Runtime.getRuntime().freeMemory();
    long size = getSize();
    logger.warn(String.format("Allocation of length %d failed; %s/%s used memory; %s items in recycle buffer; Clearing memory", length, previous, max, size));
    clear();
    System.gc();
    long after = Runtime.getRuntime().freeMemory();
    logger.warn(String.format("Clearing memory freed %s/%s bytes", previous - after, max));
  }

  /**
   * Gets size.
   *
   * @return the size
   */
  public long getSize() {
    return this.buckets.entrySet().stream().mapToLong(e -> e.getKey() * e.getValue().size()).sum();
  }

  /**
   * Want boolean.
   *
   * @param size the size
   * @return the boolean
   */
  public boolean want(long size) {
    if (size < getMinLengthPerBuffer()) return false;
    if (size > getMaxLengthPerBuffer()) return false;
    @Nullable StackCounter stackCounter = getRecycle_put(size);
    if (null != stackCounter) {
      stackCounter.increment(size);
    }
    ConcurrentLinkedDeque bin = getBin(size);
    return bin.size() < Math.min(Math.max(1, (int) (getMaxLengthPerBuffer() / size)), getMaxItemsPerBuffer());
  }

  /**
   * Gets bin.
   *
   * @param size the size
   * @return the bin
   */
  protected ConcurrentLinkedDeque getBin(long size) {
    return buckets.computeIfAbsent(size, x -> new ConcurrentLinkedDeque<>());
  }

  /**
   * Gets purge freq.
   *
   * @return the purge freq
   */
  public int getPurgeFreq() {
    return purgeFreq;
  }

  /**
   * Sets purge freq.
   *
   * @param purgeFreq the purge freq
   * @return the purge freq
   */
  public RecycleBin setPurgeFreq(int purgeFreq) {
    this.purgeFreq = purgeFreq;
    return this;
  }

  /**
   * Gets persistance mode.
   *
   * @return the persistance mode
   */
  public PersistanceMode getPersistanceMode() {
    return persistanceMode;
  }

  /**
   * Sets persistance mode.
   *
   * @param persistanceMode the persistance mode
   * @return the persistance mode
   */
  @Nonnull
  public RecycleBin setPersistanceMode(PersistanceMode persistanceMode) {
    this.persistanceMode = persistanceMode;
    return this;
  }

  /**
   * New ref reference.
   *
   * @param data the data
   * @return the reference
   */
  @Nullable
  protected Supplier wrap(T data) {
    return persistanceMode.wrap(data);
  }

  /**
   * Reset.
   *
   * @param data the data
   * @param size the size
   */
  public abstract void reset(T data, long size);

  /**
   * Sets profiling.
   *
   * @param threshold the threshold
   * @return the profiling
   */
  @Nonnull
  public RecycleBin setProfiling(final int threshold) {
    this.profilingThreshold = threshold;
    return this;
  }

  /**
   * Gets min bytes per buffer.
   *
   * @return the min bytes per buffer
   */
  public int getMinLengthPerBuffer() {
    return minLengthPerBuffer;
  }

  /**
   * Sets min bytes per buffer.
   *
   * @param minLengthPerBuffer the min bytes per buffer
   * @return the min bytes per buffer
   */
  @Nonnull
  public RecycleBin setMinLengthPerBuffer(int minLengthPerBuffer) {
    this.minLengthPerBuffer = minLengthPerBuffer;
    return this;
  }

  /**
   * Gets max bytes per buffer.
   *
   * @return the max bytes per buffer
   */
  public double getMaxLengthPerBuffer() {
    return maxLengthPerBuffer;
  }

  /**
   * Sets max bytes per buffer.
   *
   * @param maxLengthPerBuffer the max bytes per buffer
   * @return the max bytes per buffer
   */
  @Nonnull
  public RecycleBin setMaxLengthPerBuffer(double maxLengthPerBuffer) {
    this.maxLengthPerBuffer = maxLengthPerBuffer;
    return this;
  }

  /**
   * Gets max items per buffer.
   *
   * @return the max items per buffer
   */
  public int getMaxItemsPerBuffer() {
    return maxItemsPerBuffer;
  }

  /**
   * Sets max items per buffer.
   *
   * @param maxItemsPerBuffer the max items per buffer
   * @return the max items per buffer
   */
  @Nonnull
  public RecycleBin setMaxItemsPerBuffer(int maxItemsPerBuffer) {
    this.maxItemsPerBuffer = maxItemsPerBuffer;
    return this;
  }

  private class ObjectWrapper {
    /**
     * The Obj.
     */
    public final Supplier obj;
    /**
     * The Created at.
     */
    public final long createdAt = System.nanoTime();

    private ObjectWrapper(final Supplier obj) {
      this.obj = obj;
    }

    /**
     * Age double.
     *
     * @return the double
     */
    public final double age() {
      return (System.nanoTime() - createdAt) / 1e9;
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy