package org.cache2k.benchmark.util; /* * #%L * Benchmarks: utilities * %% * Copyright (C) 2013 - 2017 headissue GmbH, Munich * %% * 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. * #L% */ import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.channels.FileChannel; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Random; import java.util.Set; /** * A finite list of requests to a cache with some calculated properties. * * @author Jens Wilke; created: 2013-11-14 */ public class AccessTrace implements Iterable<Integer> { AccessPattern pattern; private int[] trace = null; private Integer[] objectTrace = null; int valueCount = -1; int lowValue = -Integer.MAX_VALUE; int highValue = Integer.MIN_VALUE; HashMap<Integer, Integer> size2opt = new HashMap<>(); HashMap<Integer, Integer> size2random = new HashMap<>(); /** * Read in access trace from file. The file format is binary integer * values (4 bytes) in sequence, the order is big endian. */ public AccessTrace(File f) throws IOException { FileChannel in = new FileInputStream(f).getChannel(); ByteBuffer buf = in.map(FileChannel.MapMode.READ_ONLY, 0, in.size()); trace = new int[(int) (in.size() / 4)]; buf.order(ByteOrder.BIG_ENDIAN); buf.asIntBuffer().get(getTrace()); in.close(); } /** * Read in access trace. The format is binary integer * values (4 bytes) in sequence, the order is big endian. */ public AccessTrace(InputStream in) throws IOException { byte[] ba = readToByteArray(in); ByteBuffer buf = ByteBuffer.wrap(ba); trace = new int[(int) (ba.length / 4)]; buf.order(ByteOrder.BIG_ENDIAN); buf.asIntBuffer().get(getTrace()); in.close(); } private byte[] readToByteArray(InputStream in) throws IOException { byte[] ba = new byte[2048]; int pos = 0; do { int _possibleLength = ba.length - pos; int l = in.read(ba, pos, _possibleLength); if (l > 0) { pos += l; if (pos >= ba.length) { byte[] ba2 = new byte[ba.length * 2]; System.arraycopy(ba, 0, ba2, 0, ba.length); ba = ba2; } } else { break; } } while (true); byte[] ba2 = new byte[pos]; System.arraycopy(ba, 0, ba2, 0, pos); return ba2; } /** * New trace of complete pattern. */ public AccessTrace(AccessPattern... ps) { AccessPattern p = Patterns.concat(ps); if (p.isEternal()) { throw new IllegalArgumentException("Pattern is expected not to be eternal"); } pattern = p; } /** * New trace of complete pattern. */ public AccessTrace(int _maxSize, AccessPattern... ps) { AccessPattern p = Patterns.concat(ps); pattern = Patterns.strip(p, _maxSize); } /** * New trace from partial pattern. Reads the pattern to the end but at most * the maximum size. */ public AccessTrace(AccessPattern p, int _maxSize) { pattern = Patterns.strip(p, _maxSize); } public Integer[] getObjectTrace() { if (objectTrace != null) { return objectTrace; } int[] _trace = getTrace(); Integer[] ia = new Integer[_trace.length]; for (int i = 0; i < _trace.length; i++) { ia[i] = _trace[i]; } return objectTrace = ia; } public int[] getTrace() { if (trace != null) { return trace; } try { trace = prepareTrace(pattern); } catch (Exception e) { throw new IllegalArgumentException("Error creating trace", e); } return trace; } /** * Opt calculation is extremely slow for traces with no hits at all. * This is used to predefine the opt hit count to a known value. */ public AccessTrace setOptHitCount(int _size, int _count) { size2opt.put(_size, _count); return this; } public AccessTrace setRandomHitCount(int _size, int _count) { size2random.put(_size, _count); return this; } public AccessTrace disableOptHitCount() { size2opt = null; return this; } public void write(File f) throws IOException { FileChannel out = new RandomAccessFile(f, "rw").getChannel(); ByteBuffer buf = out.map(FileChannel.MapMode.READ_WRITE, 0, getTrace().length * 4); buf.order(ByteOrder.BIG_ENDIAN); buf.asIntBuffer().put(getTrace()); out.close(); } /** * Return an access pattern which starts at the beginning of the trace. */ public AccessPattern newPattern() { final int[] ia = getTrace(); return new AccessPattern() { int idx = 0; @Override public boolean isEternal() { return false; } @Override public boolean hasNext() throws Exception { return idx < ia.length; } @Override public int next() throws Exception { return ia[idx++]; } }; } /** * Return the array of the trace. It is not allowed to modify the array, since this * is the trace data itself. This is a poor API, however, when used in benchmarking we * don't want to have to array copy in the timing. */ public int[] getArray() { return getTrace(); } /** * Returns the hits according to Beladys optimal algorithm for the given cache size. * The calculation is done only once for a trace and a size. */ public int getOptHitCount(int _size) { if (_size <= 0) { throw new IllegalArgumentException("size must be greater 0"); } if (size2opt == null) { return 0; } Integer v = size2opt.get(_size); if (v != null) { return v; } OptimumReplacementCalculation c = new OptimumReplacementCalculation(_size, getTrace()); size2opt.put(_size, c.getHitCount()); return c.getHitCount(); } public HitRate getOptHitRate(int _size) { return new HitRate(getOptHitCount(_size)); } public HitRate getRandomHitRate(int _size) { if (_size <= 0) { throw new IllegalArgumentException("size must be greater 0"); } Integer v = size2random.get(_size); if (v == null) { v = calcRandomHits(_size); size2random.put(_size, v); } return new HitRate(v); } int calcRandomHits(int _size, int _seed) { IntSet _cache = new IntOpenHashSet(); IntList _list = new IntArrayList(); Random _random = new Random(_seed); int _hitCnt = 0; for (int v : getTrace()) { if(_cache.contains(v)) { _hitCnt++; } else { if (_cache.size() == _size) { int cnt = _random.nextInt(_cache.size()); _cache.remove(_list.get(cnt)); _list.remove(cnt); } _cache.add(v); _list.add(v); } } return _hitCnt; } int calcRandomHits(int _size) { return (calcRandomHits(_size, 1802) + calcRandomHits(_size, 4711)) / 2; } /** * Return the distinct values in this trace. */ public int getValueCount() { if (valueCount < 0) { initStatistics(); } return valueCount; } public int getHighValue() { if (valueCount < 0) { initStatistics(); } return highValue; } public int getLowValue() { if (valueCount < 0) { initStatistics(); } return lowValue; } public int getTraceLength() { return getTrace().length; } private void initStatistics() { IntSet _values = new IntOpenHashSet(); for (int v : getTrace()) { _values.add(v); if (v < lowValue) { lowValue = v; } if (v > highValue) { highValue = v; } } valueCount = _values.size(); } public String toString() { return String.format("AccessTrace(length=%d, values=%d, maxHitRate=%.2f)", getTraceLength(), getValueCount(), getRandomHitRate(getValueCount()).getFactor() * 100); } private static int[] prepareTrace(AccessPattern p, int _maxSize) throws Exception { int[] ia = new int[1024]; int i = 0; while (p.hasNext() && i < _maxSize) { if (i >= ia.length) { int[] ia2 = new int[ia.length * 2]; System.arraycopy(ia, 0, ia2, 0, i); ia = ia2; } ia[i] = p.next(); i++; } int[] ia2 = new int[i]; System.arraycopy(ia, 0, ia2, 0, i); p.close(); return ia2; } /** Read until pattern ends */ private static int[] prepareTrace(AccessPattern p) throws Exception { return prepareTrace(p, Integer.MAX_VALUE); } public class HitRate { int hitCount; public HitRate(int hitCount) { this.hitCount = hitCount; } public int getCount() { return hitCount; } public double getFactor() { return hitCount * 1D / getTraceLength(); } /** Hitrate in percent from 0 to 100 */ public int getPercent() { return getPer(100); } /** Hitrate in millis from 0 to 1000 */ public int get3digit() { return getPer(1000); } /** Hitrate from 0 to 10000 */ public int get4digit() { return getPer(10000); } public int getPer(long v) { if (getTraceLength() == 0) { throw new IllegalStateException("empty trace"); } return (int) ((hitCount * v + getTraceLength() / 2) / getTraceLength()); } } @Override public Iterator<Integer> iterator() { return new Iterator<Integer>() { int idx = 0; @Override public boolean hasNext() { return idx < getTrace().length; } @Override public Integer next() { return getTrace()[idx++]; } @Override public void remove() { throw new UnsupportedOperationException(); } }; } }