package org.apache.solr.core;
import org.apache.lucene.util.BytesRef;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.misc.Unsafe;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.reflect.Field;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
public class HS
{
public static int BYTE_SIZE = 1;
public static int SHORT_SIZE = 2;
public static int INT_SIZE = 4;
public static int LONG_SIZE = 8;
public static int FLOAT_SIZE = 4;
public static int DOUBLE_SIZE = 8;
private static Logger log = LoggerFactory.getLogger(HS.class);
public static final Unsafe unsafe;
private static native void print();
static {
try {
Field field = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (sun.misc.Unsafe) field.get(null);
} catch (Exception e) {
throw new RuntimeException("HS: can't load Unsafe!", e);
}
}
public static class Allocator {
public long allocArray(long numElements, int elementSize, boolean zero) throws OutOfMemoryError {
// any JVM accounting for memory allocated this way?
long sz = numElements * elementSize;
long addr = unsafe.allocateMemory(sz + HEADER_SIZE);
numAlloc.incrementAndGet();
if (zero) {
// zero all the memory, including the header
unsafe.setMemory(addr, sz + HEADER_SIZE, (byte)0);
}
// should never be 0 since we always add a header
addr += HEADER_SIZE;
unsafe.putLong(addr - SIZE_OFFSET, sz);
return addr;
}
public void freeArray(long ptr) {
assert arraySizeBytes(ptr) >= 0;
numFree.incrementAndGet();
unsafe.putLong(ptr - SIZE_OFFSET, -123456789L); // put negative length to trip asserts
unsafe.freeMemory(ptr - HEADER_SIZE);
}
public void reset() {
// TODO - reset numAlloc and numFree here?
}
public void debug() {
}
}
// An allocator for debugging that tracks every allocation
public static class TrackingAllocator extends Allocator {
private static class Info {
long ptr;
StackTraceElement[] stack;
}
Map<Long, Info> map = new LinkedHashMap<>();
@Override
public long allocArray(long numElements, int elementSize, boolean zero) throws OutOfMemoryError {
Info info = new Info();
Thread thread = Thread.currentThread();
info.stack = thread.getStackTrace();
// TODO: would storing String take up less space?
// ManagementFactory.getThreadMXBean().getThreadInfo(Thread.currentThread().getId());
synchronized (this) {
long ptr = super.allocArray(numElements, elementSize, zero);
info.ptr = ptr;
Info prev = map.put(ptr, info);
if (prev != null) {
throw new RuntimeException("HS Allocator ERROR : should be impossible!!!");
}
return ptr;
}
}
@Override
public void freeArray(long ptr) {
synchronized (this) {
Info curr = map.remove(ptr);
if (curr == null) {
// TODO: if we provide a way to clear, it's not an error.
// TODO: will it be possible to dynamically switch to a debugging allocator? would be good for debugging live customer site...
// perhaps detect if we have been switched on and not consider this an error...
throw new RuntimeException("HS Allocator ERROR: no record of " + ptr + " , bad pointer or double free.");
}
super.freeArray(ptr);
}
}
@Override
public void debug() {
synchronized (this) {
int n = map.size();
int show = Math.min(n, 20);
if (n > 0) {
log.error("CURRENT ALLOCATIONS=" + n + (n==show ? "" : " Showing first " + show));
}
for (Info info : map.values()) {
log.error("PTR: " + info.ptr + " SIZE=" + arraySizeBytes(info.ptr) + " ALLOCATED AT " + Diagnostics.toString(info.stack, HS.class.getSimpleName()));
}
}
}
}
public static Allocator allocator = new Allocator();
// public static Allocator allocator = new TrackingAllocator();
private static final AtomicLong numAlloc = new AtomicLong();
private static final AtomicLong numFree = new AtomicLong();
public static final int HEADER_SIZE = 16;
public static final int SIZE_OFFSET = 8;
public static long getNumAllocations() {
return numAlloc.get();
}
public static long getNumFrees() {
return numFree.get();
}
public static long allocArray(long numElements, int elementSize, boolean zero) throws OutOfMemoryError {
return allocator.allocArray(numElements, elementSize, zero);
}
public static void freeArray(long ptr) {
allocator.freeArray(ptr);
}
public static long arraySizeBytes(long ptr) {
assert ptr >= 4095 && unsafe.getLong(ptr - SIZE_OFFSET) >= 0; // if this assertion trips, it's most likely because of a double free
long sz = unsafe.getLong(ptr - SIZE_OFFSET);
return sz;
}
public static byte getByte(long ptr, int index) {
assert (index>=0) && ((((long)index+1))) <= arraySizeBytes(ptr);
return unsafe.getByte(ptr + (((long) index)));
}
public static byte getByte(long ptr, long index) {
assert (index>=0) && ((((long)index+1))) <= arraySizeBytes(ptr);
return unsafe.getByte(ptr + (((long) index)));
}
public static void setByte(long ptr, int index, byte val) {
assert (index>=0) && ((((long)index+1))) <= arraySizeBytes(ptr);
unsafe.putByte(ptr + (((long)index)), val);
}
public static void setByte(long ptr, long index, byte val) {
assert (index>=0) && ((((long)index+1))) <= arraySizeBytes(ptr);
unsafe.putByte(ptr + (((long)index)), val);
}
public static int getShort(long ptr, int index) {
assert (index>=0) && ((((long)index+1)<<1)) <= arraySizeBytes(ptr);
return unsafe.getShort(ptr + (((long)index)<<1));
}
public static void setShort(long ptr, int index, short val) {
assert (index>=0) && ((((long)index+1)<<1)) <= arraySizeBytes(ptr);
unsafe.putShort(ptr + (((long)index)<<1), val);
}
public static int getInt(long ptr, int index) {
assert (index>=0) && ((((long)index+1)<<2)) <= arraySizeBytes(ptr);
return unsafe.getInt(ptr + (((long)index)<<2));
}
public static void setInt(long ptr, int index, int val) {
assert (index>=0) && ((((long)index+1)<<2)) <= arraySizeBytes(ptr);
unsafe.putInt(ptr + (((long)index)<<2), val);
}
public static float getFloat(long ptr, int index) {
assert (index>=0) && ((((long)index+1)<<2)) <= arraySizeBytes(ptr);
return unsafe.getFloat(ptr + (((long) index) << 2));
}
public static void setFloat(long ptr, int index, float val) {
assert (index>=0) && ((((long)index+1)<<2)) <= arraySizeBytes(ptr);
unsafe.putFloat(ptr + (((long) index) << 2), val);
}
public static long getLong(long ptr, int index) {
assert (index>=0) && ((((long)index+1)<<3)) <= arraySizeBytes(ptr);
return unsafe.getLong(ptr + (((long) index) << 3));
}
public static void setLong(long ptr, int index, long val) {
assert (index>=0) && ((((long)index+1)<<3)) <= arraySizeBytes(ptr);
unsafe.putLong(ptr + (((long) index) << 3), val);
}
public static double getDouble(long ptr, int index) {
assert (index>=0) && ((((long)index+1)<<3)) <= arraySizeBytes(ptr);
return unsafe.getDouble(ptr + (((long) index) << 3));
}
public static void setDouble(long ptr, int index, double val) {
assert (index>=0) && ((((long)index+1)<<3)) <= arraySizeBytes(ptr);
unsafe.putDouble(ptr + (((long) index) << 3), val);
}
public static void copyLengthPrefixBytes(long bytesArr, long offset, BytesRef target) {
assert bytesArr>=0 && offset>=0 && offset+2 <= arraySizeBytes(bytesArr);
copyLengthPrefixBytes(bytesArr + offset, target);
}
/* WARNING: this method can't verify the pointer */
public static void copyLengthPrefixBytes(long pointer, BytesRef target) {
int len = unsafe.getByte(pointer++);
if (len < 0) {
len = ((len & 0x7f) << 8) | (unsafe.getByte(pointer++) & 0xff);
}
if (target.offset + len >= target.bytes.length) {
target.bytes = new byte[len];
target.offset = 0;
}
target.length = len;
unsafe.copyMemory(null, pointer, target.bytes, Unsafe.ARRAY_BYTE_BASE_OFFSET + target.offset, len);
}
/* WARNING: this method can't verify the pointer2 */
public static int compareLengthPrefixBytes(long pointer1, long pointer2) {
int len = unsafe.getByte(pointer1++);
if (len < 0) {
len = ((len & 0x7f) << 8) | (unsafe.getByte(pointer1++) & 0xff);
}
return compareLengthPrefixBytes(len, pointer1, pointer2);
}
/* termPointerBytes points directly to the start of the bytes of the first term (and len1 contains
* the length of that term. termPointer2 points to a normal length prefixed term. This avoids
* decoding the length of the first term multiple times when doing a binary search for example.
* WARNING: this method can't verify the pointers.
*/
public static int compareLengthPrefixBytes(int len1, long termPointerBytes, long termPointer2) {
assert len1 >= 0 && len1 < 0x7fff && termPointerBytes > 0 && termPointer2 > 0;
int len2 = unsafe.getByte(termPointer2++);
if (len2 < 0) {
len2 = ((len2 & 0x7f) << 8) | (unsafe.getByte(termPointer2++) & 0xff);
}
int upTo = len1 < len2 ? len1 : len2;
for (int i=0; i<upTo; i++) {
int diff = (unsafe.getByte(termPointerBytes + i) & 0xff) - (unsafe.getByte(termPointer2 + i) & 0xff); // compare unsigned
if (diff != 0) return diff;
}
return len1 - len2;
}
public static int compareLengthPrefixBytes(long termPointer, BytesRef termPointer2) {
assert termPointer >= 0;
int len = unsafe.getByte(termPointer++);
if (len < 0) {
len = ((len & 0x7f) << 8) | (unsafe.getByte(termPointer++) & 0xff);
}
byte[] arr2 = termPointer2.bytes;
int offset2 = termPointer2.offset;
int len2 = termPointer2.length;
int upTo = len < len2 ? len : len2;
for (int i=0; i<upTo; i++) {
int diff = (unsafe.getByte(termPointer + i) & 0xff) - (arr2[offset2+i] & 0xff); // compare unsigned
if (diff != 0) return diff;
}
return len - len2;
}
public static int getTermLength(long termPointer) {
int len = unsafe.getByte(termPointer);
if (len < 0) {
len = ((len & 0x7f) << 8) | (unsafe.getByte(termPointer + 1) & 0xff);
}
return len;
}
public static void copyBytes(byte[] srcArray, int srcOff, long targetPointer, long targetOff, int numElements) {
long nbytes = ((long)numElements);
assert srcOff>=0 && targetOff>=0 && (targetOff + nbytes) <= arraySizeBytes(targetPointer);
unsafe.copyMemory(srcArray, Unsafe.ARRAY_BYTE_BASE_OFFSET + (((long)srcOff)), null, targetPointer+targetOff, nbytes);
}
public static void copyBytes(long srcPointer, long srcOff, long targetPointer, long targetOff, long numBytes) {
assert srcOff>=0 && targetOff>=0 && (targetOff + numBytes) <= arraySizeBytes(targetPointer) && (srcOff + numBytes) <= arraySizeBytes(srcPointer);
unsafe.copyMemory(srcPointer + srcOff, targetPointer + targetOff, numBytes);
}
public static void copyInts(int[] srcArray, int srcOff, long targetPointer, long targetOff, int numElements) {
long targetOffBytes = targetOff<<2;
long nbytes = ((long)numElements) << 2;
assert srcOff>=0 && targetOff>=0 && (targetOffBytes + nbytes) <= arraySizeBytes(targetPointer);
unsafe.copyMemory(srcArray, Unsafe.ARRAY_INT_BASE_OFFSET + (((long)srcOff)<<2), null, targetPointer+targetOffBytes, nbytes);
}
public static void copyLongs(long[] srcArray, int srcOff, long targetPointer, long targetOff, int numElements) {
long targetOffBytes = targetOff<<3;
long nbytes = ((long)numElements) << 3;
assert srcOff>=0 && targetOff>=0 && (targetOffBytes + nbytes) <= arraySizeBytes(targetPointer);
unsafe.copyMemory(srcArray, Unsafe.ARRAY_LONG_BASE_OFFSET + (((long)srcOff)<<3), null, targetPointer+targetOffBytes, nbytes);
}
public static void copyInts(long sourcePointer, long srcOff, int[] targetArray, int targetOff, int numElements) {
long srcOffBytes = srcOff<<2;
long nbytes = ((long)numElements) << 2;
assert srcOff>=0 && targetOff>=0 && (srcOffBytes + nbytes) <= arraySizeBytes(sourcePointer) && (targetOff+numElements<=targetArray.length);
unsafe.copyMemory(null, sourcePointer+srcOffBytes, targetArray, Unsafe.ARRAY_INT_BASE_OFFSET + (((long)targetOff)<<2), nbytes);
}
public static void copyLongs(long sourcePointer, long srcOff, long[] targetArray, int targetOff, int numElements) {
long srcOffBytes = srcOff<<3;
long nbytes = ((long)numElements) << 3;
assert srcOff>=0 && targetOff>=0 && (srcOffBytes + nbytes) <= arraySizeBytes(sourcePointer) && (targetOff+numElements<=targetArray.length);
unsafe.copyMemory(null, sourcePointer+srcOffBytes, targetArray, Unsafe.ARRAY_LONG_BASE_OFFSET + (((long)targetOff)<<3), nbytes);
}
public static void copyInts(long src, long srcOff, long dest, long destOff, long len) {
long srcOffBytes = srcOff<<2;
long destOffBytes = destOff<<2;
long nBytes = len<<2;
assert(srcOff>=0 && destOff>=0 && srcOffBytes+nBytes <= arraySizeBytes(src) && destOffBytes+nBytes <= arraySizeBytes(dest));
unsafe.copyMemory(src+srcOffBytes, dest+destOffBytes, nBytes);
}
public static void copyLongs(long src, long srcOff, long dest, long destOff, long len) {
long srcOffBytes = srcOff<<3;
long destOffBytes = destOff<<3;
long nBytes = len<<3;
assert(srcOff>=0 && destOff>=0 && srcOffBytes+nBytes <= arraySizeBytes(src) && destOffBytes+nBytes <= arraySizeBytes(dest));
unsafe.copyMemory(src+srcOffBytes, dest+destOffBytes, nBytes);
}
public static String hex(long arr, long offset) {
StringBuilder sb = new StringBuilder();
long len = HS.arraySizeBytes(arr);
sb.append("arr=" + arr + " sizeInBytes=" + len + " offset=" + offset);
int nBytes = (int)Math.min(16, len - offset);
return sb.toString() + hexBytes(arr+offset, nBytes);
}
public static String hexBytes(long mem, int nBytes) {
StringBuilder sb = new StringBuilder();
for (int i=0; i<nBytes; i++) {
byte b = unsafe.getByte(mem + i);
if ((i & 0x03)==0) sb.append(' ');
sb.append(Integer.toHexString(b&0xff));
}
return sb.toString();
}
// TODO: introduce the concept of a reference list to ease deallocation? (just an auto-expanding long[])
// TODO: YCS: a finalizer to clean up native memory?
}