/*- * See the file LICENSE for redistribution information. * * Copyright (c) 2002, 2014 Oracle and/or its affiliates. All rights reserved. * */ package com.sleepycat.je.tree; import com.sleepycat.je.dbi.MemoryBudget; import com.sleepycat.je.evictor.Evictor; import com.sleepycat.je.utilint.SizeofMarker; /** * The abstract class that defines the various representation used to represent * the keys associated with the IN node. There are currently two supported * representations: *
    *
  1. A default representation Default that's capable of holding * any set of keys.
  2. *
  3. * A compact representation MaxKeySize that's more efficient for * holding small keys (LTE 16 bytes) in length. If key prefixing is in use this * represents the unprefixed part of the key, since that's what is stored in * this array.
  4. *
*

* The choice of representation is made when an IN node is first read in from * the log. The MaxKeySize representation is only used when it is * more storage efficient than the default representation for the set of keys * currently associated with the IN. *

* Note that no attempt is currently made to optimize the storage * representation as keys are added to, or removed from, the * Default representation to minimize the chances of transitionary * "back and forth" representation changes that could prove to be expensive. */ public abstract class INKeyRep extends INArrayRep { /* The different representations for keys. */ public enum Type { DEFAULT, MAX_KEY_SIZE }; public INKeyRep() { } public abstract int length(); /** * Returns true if the key bytes mem usage is accounted for internally * here, or false if each key has a separate byte array and its mem usage * is accounted for by the parent. */ public abstract boolean accountsForKeyByteMemUsage(); /** * The default representation that's capable of storing keys of any size. */ public static class Default extends INKeyRep { private final byte[][] keys; Default(int nodeMaxEntries) { this.keys = new byte[nodeMaxEntries][]; } public Default(@SuppressWarnings("unused") SizeofMarker marker) { keys = null; } @Override public Type getType() { return Type.DEFAULT; } @Override public int length() { return keys.length; } @Override public byte[] get(int idx) { return keys[idx]; } @Override public INKeyRep set(int idx, byte[] key, IN parent) { keys[idx] = key; return this; } @Override public INKeyRep copy(int from, int to, int n, IN parent) { System.arraycopy(keys, from, keys, to, n); return this; } /** * Evolves to the MaxKeySize representation if that is more efficient * for the current set of keys. Note that since all the keys must be * examined to make the decision, there is a reasonable cost to the * method and it should not be invoked indiscriminately. */ @Override public INKeyRep compact(IN parent) { if (keys.length > MaxKeySize.MAX_KEYS) { return this; } final int compactMaxKeyLength = parent.getCompactMaxKeyLength(); if (compactMaxKeyLength <= 0) { return this; } int keyCount = 0; int maxKeyLength = 0; int defaultKeyBytes = 0; for (byte[] key : keys) { if (key != null) { keyCount++; if (key.length > maxKeyLength) { maxKeyLength = key.length; if (maxKeyLength > compactMaxKeyLength) { return this; } } defaultKeyBytes += MemoryBudget.byteArraySize(key.length); } } if (keyCount == 0) { return this; } long defaultSizeWithKeys = calculateMemorySize() + defaultKeyBytes; if (defaultSizeWithKeys > MaxKeySize.calculateMemorySize(keys.length, maxKeyLength)) { return compactToMaxKeySizeRep(maxKeyLength, parent); } return this; } private MaxKeySize compactToMaxKeySizeRep( int maxKeyLength, IN parent) { MaxKeySize newRep = new MaxKeySize(keys.length, (short) maxKeyLength); for (int i = 0; i < keys.length; i++) { INKeyRep rep = newRep.set(i, keys[i], parent); assert rep == newRep; /* Rep remains unchanged. */ } noteRepChange(newRep, parent); return newRep; } @Override public long calculateMemorySize() { /* * Assume empty keys array. The memory consumed by the actual keys * is accounted for by the IN.getEntryInMemorySize() method. */ return MemoryBudget.DEFAULT_KEYVALS_OVERHEAD + MemoryBudget.objectArraySize(keys.length); } @Override public boolean accountsForKeyByteMemUsage() { return false; } @Override void updateCacheStats(@SuppressWarnings("unused") boolean increment, @SuppressWarnings("unused") Evictor evictor) { /* No stats for the default representation. */ } } /** * The compact representation that can be used to represent keys LTE 16 * bytes in length. The keys are all represented inside a single byte array * instead of having one byte array per key. Within the array, all keys are * assigned a storage size equal to that taken up by the longest key, plus * one byte to hold the actual key length. This makes key retreival fast. * However, insertion and deletion for larger keys moves bytes proportional * to the storage length of the keys. This is why the representation is * restricted to keys LTE 16 bytes in size. * * On a 32 bit VM the per key overhead for the Default representation is 4 * bytes for the pointer + 16 bytes for each byte array key object, for a * total of 20 bytes/key. On a 64 bit machine the overheads are much * larger: 8 bytes for the pointer plus 24 bytes per key. * * The more fully populated the IN the more the savings with this * representation since the single byte array is sized to hold all the keys * regardless of the actual number of keys that are present. * * It's worth noting that the storage savings here are realized in addition * to the storage benefits of key prefixing, since the keys stored in the * key array are the smaller key values after the prefix has been stripped, * reducing the length of the key and making it more likely that it's small * enough for this specialized representation. */ public static class MaxKeySize extends INKeyRep { private static final int LENGTH_BYTES = 1; public static final byte DEFAULT_MAX_KEY_LENGTH = 16; public static final int MAX_KEYS = 256; private static final byte NULL_KEY = Byte.MAX_VALUE; /* * The array is sized to hold all the keys associated with the IN node. * Each key is allocated a fixed amount of storage equal to the maximum * length of all the keys in the IN node + 1 byte to hold the size of * each key. The length is biased, by -128. That is, a zero length * key is represented by -128, a 1 byte key by -127, etc. */ private final byte[] keys; /* * The number of butes used to store each key == * DEFAULT_MAX_KEY_LENGTH (16) + LENGTH_BYTES (1) */ private final short keyLength; public MaxKeySize(int nodeMaxEntries, short maxKeyLen) { assert maxKeyLen < 255; this.keyLength = (short) (maxKeyLen + LENGTH_BYTES); this.keys = new byte[keyLength * nodeMaxEntries]; for (int i = 0; i < nodeMaxEntries; i++) { INKeyRep rep = set(i, null, null); assert rep == this; /* Rep remains unchanged. */ } } /* Only for use by Sizeof */ public MaxKeySize(@SuppressWarnings("unused") SizeofMarker marker) { keys = null; keyLength = 0; } @Override public Type getType() { return Type.MAX_KEY_SIZE; } @Override public int length() { return keys.length / keyLength; } @Override public byte[] get(int idx) { final int k = idx * keyLength; if (keys[k] == NULL_KEY) { return null; } final int length = keys[k] - Byte.MIN_VALUE; final byte[] key = new byte[length]; for (int i = 0; i < length; ++i) { key[i] = keys[k + LENGTH_BYTES + i]; } return key; } @Override public INKeyRep set(int idx, byte[] key, IN parent) { final int ki = idx * keyLength; if (key == null) { keys[ki] = NULL_KEY; return this; } if (key.length >= keyLength) { Default newRep = expandToDefaultRep(parent); return newRep.set(idx, key, parent); } keys[ki] = (byte) (key.length + Byte.MIN_VALUE); for (int i = 0; i < key.length; ++i) { keys[ki + LENGTH_BYTES + i] = key[i]; } return this; } private Default expandToDefaultRep(IN parent) { final int capacity = length(); final Default newRep = new Default(capacity); for (int i = 0; i < capacity; i++) { final byte[] k = get(i); INKeyRep rep = newRep.set(i, k, parent); assert rep == newRep; /* Rep remains unchanged. */ } noteRepChange(newRep, parent); return newRep; } @Override public INKeyRep copy(int from, int to, int n, IN parent) { System.arraycopy(keys, (from * keyLength), keys, (to * keyLength), n * keyLength); return this; } @Override public INKeyRep compact(@SuppressWarnings("unused") IN parent) { /* It's as compact as it gets. */ return this; } @Override public long calculateMemorySize() { return MemoryBudget.MAX_KEY_SIZE_KEYVALS_OVERHEAD + MemoryBudget.byteArraySize(keys.length); } private static long calculateMemorySize(int maxKeys, int maxKeySize) { return MemoryBudget.MAX_KEY_SIZE_KEYVALS_OVERHEAD + MemoryBudget.byteArraySize(maxKeys * (maxKeySize + LENGTH_BYTES)); } @Override public boolean accountsForKeyByteMemUsage() { return true; } @Override void updateCacheStats(boolean increment, Evictor evictor) { if (increment) { evictor.getNINCompactKey().incrementAndGet(); } else { evictor.getNINCompactKey().decrementAndGet(); } } } }