/*
* Copyright (C) 2013 Omry Yadan <omry@yadan.net>
* All rights reserved.
*
* See https://github.com/omry/banana/blob/master/BSD-LICENSE for licensing information
*/
package net.yadan.banana.memory.block;
import net.yadan.banana.memory.IBlockAllocator;
import net.yadan.banana.memory.IBuffer;
import net.yadan.banana.memory.MemInitializer;
import net.yadan.banana.memory.OutOfMemoryException;
import net.yadan.banana.memory.initializers.PrototypeInitializer;
/**
* A fixed size block allocator. total memory is limited to a single int array
* which contain at most about 2B ints (Integer.MAX_VALUE)
*
* Upon construction, the block size and the number of blocks are specified. The
* maximum block number is Integer.MAX_VALUE / block_size.
*
* If you require access to more blocks than this, use {@link BigBlockAllocator}
* - which can access up to 2B blocks, regardless of the block size.
*
* @author omry
* created 21/4/2013
*/
public class BlockAllocator implements IBlockAllocator {
protected final int m_blockSize;
private int m_watermark;
private int m_free;
private int m_head;
int m_buffer[];
private int m_maxCapacity;
private MemInitializer m_initializer;
private boolean m_debug;
private double m_growthFactor;
private int m_reservedBlocks;
/**
* @param maxBlocks number of blocks to reserve space for
* @param blockSize record size in ints.
*/
public BlockAllocator(int maxBlocks, int blockSize) {
this(maxBlocks, blockSize, null);
}
/**
* @param maxBlocks number of records to reserve space for
* @param blockSize record size in ints.
* @param initializer a callback to initialize newly allocated records
*/
public BlockAllocator(int maxBlocks, int blockSize, MemInitializer initializer) {
this(maxBlocks, blockSize, 0, initializer);
}
/**
* @param maxBlocks number of records to reserve space for
* @param blockSize record size in ints.
* @param growthFactor determines by how much to grow buffer when it runs out
* of memory. 0 to disable growth
*/
public BlockAllocator(int maxBlocks, int blockSize, double growthFactor) {
this(maxBlocks, blockSize, growthFactor, null);
}
/**
* @param maxBlocks number of records to reserve space for
* @param blockSize record size in ints.
* @param growthFactor determines by how much to grow buffer when it runs out
* of memory. 0 to disable growth
* @param initializer a callback to initialize newly allocated records
*/
public BlockAllocator(int maxBlocks, int blockSize, double growthFactor,
MemInitializer initializer) {
m_reservedBlocks = 1;
m_head = -1;
m_blockSize = blockSize;
m_maxCapacity = maxBlocks + m_reservedBlocks;
m_growthFactor = growthFactor;
if (initializer == null) {
initializer = new PrototypeInitializer(blockSize);
}
m_debug = false;
m_initializer = initializer;
if (maxBlocks < 1)
throw new IllegalArgumentException("maxBlocks " + maxBlocks + " < 1");
// block 0 is reserved
long size = (m_reservedBlocks + (long) maxBlocks) * m_blockSize;
if (size > Integer.MAX_VALUE) {
throw new IllegalArgumentException("Attempted to allocate " + size
+ " ints, which is greated than Integer.MAX_VALUE (" + Integer.MAX_VALUE + ")");
}
m_buffer = new int[(int) size];
clear();
}
/**
* Allocates a single block and returns a pointer to that block
*
* @return pointer to newly allocated block
*
* @throws OutOfMemoryException : if there are 0 free blocks
*/
@Override
public int malloc() throws OutOfMemoryException {
if (m_head == -1) {
if (m_watermark == m_maxCapacity) {
if (m_growthFactor == 0) {
throw new OutOfMemoryException("Out of memory (" + maxBlocks() + "/" + usedBlocks()
+ " blocks used)");
} else {
increaseSize();
return malloc();
}
} else {
m_head = m_watermark;
set_next(m_head, -1);
m_watermark++;
}
} else {
m_free--;
}
int oldHead = m_head;
m_head = next(oldHead);
set_next(oldHead, -1);
m_initializer.initialize(this, oldHead, m_blockSize);
return oldHead;
}
private void increaseSize() {
int newMaxCapacity = m_reservedBlocks + Math.max(maxBlocks() + 1, (int) (maxBlocks() * m_growthFactor));
int new_buffer[] = new int[newMaxCapacity * blockSize()];
System.arraycopy(m_buffer, 0, new_buffer, 0, m_buffer.length);
m_maxCapacity = newMaxCapacity;
m_buffer = new_buffer;
}
@Override
public void free(int pointer) {
assert pointer != 0 : "pointer 0 should not be freed";
assert pointer != -1 : "pointer -1 should not be freed";
set_next(pointer, m_head);
m_head = pointer;
m_free++;
}
@Override
public void memCopy(int srcPtr, int srcPos, int dstPtr, int dstPos, int length) {
assert srcPtr >= 0 : "Negative pointer : " + srcPtr;
assert dstPtr >= 0 : "Negative pointer : " + srcPtr;
assert length <= m_blockSize : "length > m_blockSize";
assert srcPos + length <= m_blockSize : "src overflow";
assert dstPos + length <= m_blockSize : "dst overflow";
System.arraycopy(m_buffer, srcPtr * m_blockSize + srcPos, m_buffer, dstPtr * m_blockSize + dstPos, length);
}
@Override
public void memSet(int pointer, int srcPos, int length, int value) {
assert pointer >= 0 : "Negative pointer : " + pointer;
assert length <= m_blockSize : "length > m_blockSize";
assert srcPos + length <= m_blockSize : "overflow";
int p = pointer * m_blockSize;
for (int i = srcPos; i < srcPos + length; i++) {
m_buffer[p + i] = value;
}
}
@Override
public short getUpperShort(int pointer, int offset) {
assert pointer >= 0 : "Negative pointer : " + pointer;
assert offset >= 0 : "Negative offset_in_data " + offset;
assert offset < m_blockSize : String.format("offset >= m_blockSize : %d >= %d", offset, m_blockSize);
return (short) (m_buffer[pointer * m_blockSize + offset] >>> 16);
}
@Override
public short getLowerShort(int pointer, int offset) {
assert pointer >= 0 : "Negative pointer : " + pointer;
assert offset >= 0 : "Negative offset_in_data " + offset;
assert offset < m_blockSize : String.format("offset >= m_blockSize : %d >= %d", offset, m_blockSize);
return (short) (m_buffer[pointer * m_blockSize + offset]);
}
@Override
public void setUpperShort(int pointer, int offset, int s) {
assert pointer >= 0 : "Negative pointer : " + pointer;
assert offset >= 0 : "Negative offset " + offset;
assert offset < m_blockSize : String.format("offset >= m_blockSize : %d >= %d", offset, m_blockSize);
int off = pointer * m_blockSize + offset;
int lower = m_buffer[off] & 0x0000ffff;
m_buffer[off] = (s << 16) | lower;
}
@Override
public void setLowerShort(int pointer, int offset, int s) {
assert pointer >= 0 : "Negative pointer : " + pointer;
assert offset >= 0 : "Negative offset " + offset;
assert offset < m_blockSize : String.format("offset >= m_blockSize : %d >= %d", offset, m_blockSize);
int off = pointer * m_blockSize + offset;
int upper = m_buffer[off] & 0xffff0000;
m_buffer[off] = upper | (s & 0x0000ffff);
}
@Override
public int getInt(int pointer, int offset_in_data) {
assert pointer >= 0 : "Negative pointer : " + pointer;
assert offset_in_data >= 0 : "Negative offset_in_data " + offset_in_data;
assert offset_in_data < m_blockSize : String.format("offset_in_data >= m_blockSize : %d >= %d",
offset_in_data, m_blockSize);
return m_buffer[pointer * m_blockSize + offset_in_data];
}
@Override
public void setInt(int pointer, int offset_in_data, int data) {
assert pointer >= 0 : "Negative pointer : " + pointer;
assert offset_in_data >= 0 : "Negative offset_in_data " + offset_in_data;
assert offset_in_data < m_blockSize : String.format("offset_in_data >= m_blockSize : %d >= %d",
offset_in_data, m_blockSize);
m_buffer[pointer * m_blockSize + offset_in_data] = data;
}
@Override
public void setInts(int pointer, int dst_offset_in_record,
int src_data[], int src_pos, int length) {
assert pointer >= 0 : "Negative pointer : " + pointer;
assert src_pos >= 0 : "Negative src_pos";
assert src_pos + length <= src_data.length : String.format(
"src_pos + length > src_data.length : %d + %d > %d", src_pos, length, src_data.length);
assert pointer * m_blockSize + length <= m_buffer.length : String.format(
"pointer * blockSize + length > m_buffer.length : %d + %d >= %d", pointer * m_blockSize,
length, m_buffer.length);
assert dst_offset_in_record + length <= m_blockSize : String.format(
"dst_offset_in_record + length > m_blockSize : %d + %d >= %d", dst_offset_in_record,
length, m_blockSize);
System.arraycopy(src_data, src_pos, m_buffer, pointer * m_blockSize + dst_offset_in_record,
length);
}
@Override
public void getInts(int pointer, int src_offset_in_record,
int dst_data[], int dst_pos, int length) {
assert pointer >= 0 : "Negative pointer : " + pointer;
assert pointer * m_blockSize < m_buffer.length : String.format(
"pointer >= m_buffer.length : %d < %d", pointer, m_buffer.length);
assert src_offset_in_record >= 0 : String.format("src_offset_in_record < 0 : %d < 0",
src_offset_in_record);
assert src_offset_in_record < m_blockSize : String.format(
"src_offset_in_record >= m_blockSize : %d >= %d", src_offset_in_record, m_blockSize);
assert dst_pos >= 0 : String.format("dst_pos < 0 : %d", dst_pos);
assert dst_pos + length <= dst_data.length : String.format(
"dst_pos + length > dst_data.length : %d + %d >= %d", dst_pos, length, dst_data.length);
System.arraycopy(m_buffer, pointer * m_blockSize + src_offset_in_record, dst_data, dst_pos,
length);
}
@Override
public void setChars(int pointer, int dst_offset, char[] src_data, int src_pos, int num_chars) {
if (num_chars == 0) {
return;
}
int numInts = 1 + (num_chars - 1) / 2; // ceil(num_chars/2)
for (int i = dst_offset, src_index = src_pos, num_copied = 0; i < dst_offset + numInts; i++, src_index += 2) {
char c1 = src_data[src_index];
setUpperShort(pointer, i, c1);
num_copied++;
if (num_copied < num_chars) {
char c2 = src_data[src_index + 1];
setLowerShort(pointer, i, c2);
num_copied++;
}
}
}
@Override
public void getChars(int pointer, int src_offset, char[] dst_data, int dst_pos, int num_chars) {
if (num_chars == 0) {
return;
}
int numInts = 1 + (num_chars - 1) / 2; // ceil(length/2)
for (int i = src_offset, dst_index = dst_pos, num_copied = 0; i < src_offset + numInts; i++, dst_index += 2) {
dst_data[dst_index] = (char) getUpperShort(pointer, i);
num_copied++;
if (num_copied < num_chars) {
dst_data[dst_index + 1] = (char) getLowerShort(pointer, i);
num_copied++;
}
}
}
@Override
public void getBuffer(int pointer, int src_offset_in_record, IBuffer dst, int num_chars) {
getInts(pointer, src_offset_in_record, dst.array(), 0, num_chars);
dst.setUsed(num_chars);
}
@Override
public long getLong(int pointer, int offset_in_data) {
int ilower = getInt(pointer, offset_in_data + 1);
int iupper = getInt(pointer, offset_in_data);
long lower = 0x00000000FFFFFFFFL & ilower;
long upper = ((long) iupper) << 32;
long ret = upper | lower;
return ret;
}
@Override
public void setLong(int pointer, int offset_in_data, long data) {
// upper int
setInt(pointer, offset_in_data, (int) (data >> 32));
// lower int
setInt(pointer, offset_in_data + 1, (int) (data));
}
/**
* @return the number of free blocks
*/
@Override
public int freeBlocks() {
return m_free + m_maxCapacity - m_watermark;
}
/**
* @return the total block capacity for this allocator
*/
@Override
public int maxBlocks() {
return m_maxCapacity - m_reservedBlocks; // block 0 is reserved
}
/**
* @return number of used blocks
*/
@Override
public int usedBlocks() {
return maxBlocks() - freeBlocks();
}
/**
* @return the fixed block size for this allocator
*/
@Override
public int blockSize() {
return m_blockSize;
}
@Override
public String toString() {
StringBuilder s = new StringBuilder();
try {
s.append(String.format("IntAllocator %s/%s records of %d ints used, total ints allocated %d",
maxBlocks() - freeBlocks(), maxBlocks(), m_blockSize, m_buffer.length));
if (m_debug) {
s.append('\n');
for (int i = m_reservedBlocks; i < m_maxCapacity; i++) {
s.append('(');
for (int j = 0; j < m_blockSize; j++) {
s.append(getInt(i, j));
if (j != m_blockSize - 1) {
s.append(',');
}
}
s.append(')');
if (i != m_maxCapacity - 1) {
s.append(',');
}
}
}
} catch (RuntimeException e) {
s.append(" :: Exception inToString() " + e.getClass().getName() + " : " + e.getMessage());
}
return s.toString();
}
private int next(int node) {
return m_buffer[node * m_blockSize];
}
private void set_next(int node, int next) {
m_buffer[node * m_blockSize] = next;
}
@Override
public void clear() {
m_head = -1;
m_watermark = m_reservedBlocks;
m_free = 0;
set_next(1, -1);
}
public String debugString() {
StringBuilder sb = new StringBuilder();
for (int d : m_buffer) {
sb.append(String.format("%05X", d & 0xFFFFF)).append(' ');
}
return sb.toString();
}
public static int getIntArraySize(int capacity, int blockSize) {
return capacity * blockSize;
}
/**
* Sets the allocator growth factor.
*
* @param d new growth factor, 0 to disable growth (default)
*/
@Override
public void setGrowthFactor(double d) {
m_growthFactor = d;
}
/**
* @return the current list growth factor
*/
@Override
public double getGrowthFactor() {
return m_growthFactor;
}
@Override
public boolean isDebug() {
return m_debug;
}
@Override
public void setDebug(boolean debug) {
m_debug = debug;
}
public MemInitializer getInitializer() {
return m_initializer;
}
@Override
public void setInitializer(MemInitializer initializer) {
m_initializer = initializer;
}
@Override
public void initialize(int pointer) {
m_initializer.initialize(this, pointer, m_blockSize);
}
@Override
public long computeMemoryUsage() {
return 4 * (long)m_buffer.length;
}
@Override
public int maximumCapacityFor(int pointer) {
return m_blockSize;
}
@Override
public float getFloat(int pointer, int offset) {
return Float.intBitsToFloat(getInt(pointer, offset));
}
@Override
public void setFloat(int pointer, int offset, float f) {
setInt(pointer, offset, Float.floatToIntBits(f));
}
@Override
public double getDouble(int pointer, int offset_in_data) {
return Double.longBitsToDouble(getLong(pointer, offset_in_data));
}
@Override
public void setDouble(int pointer, int offset_in_data, double data) {
setLong(pointer, offset_in_data, Double.doubleToLongBits(data));
}
}