blob: e0ef5f9157448b0c5474a2e8e4ee6a48e8d8638e [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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 org.apache.drill.exec.vector;
import org.apache.drill.exec.exception.OutOfMemoryException;
import org.apache.drill.exec.exception.OversizedAllocationException;
import org.apache.drill.exec.expr.holders.BitHolder;
import org.apache.drill.exec.expr.holders.NullableBitHolder;
import org.apache.drill.exec.memory.BufferAllocator;
import org.apache.drill.exec.proto.UserBitShared.SerializedField;
import org.apache.drill.exec.record.MaterializedField;
import org.apache.drill.exec.record.TransferPair;
import org.apache.drill.exec.vector.complex.impl.BitReaderImpl;
import org.apache.drill.exec.vector.complex.reader.FieldReader;
import com.google.common.base.Preconditions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.buffer.DrillBuf;
/**
* Bit implements a vector of bit-width values. Elements in the vector are accessed by position from the logical start
* of the vector. The width of each element is 1 bit. The equivalent Java primitive is an int containing the value '0'
* or '1'.
*/
public final class BitVector extends BaseDataValueVector implements FixedWidthVector {
static final Logger logger = LoggerFactory.getLogger(BitVector.class);
/**
* Width of each fixed-width value.
*/
public static final int VALUE_WIDTH = 1;
/**
* Maximum number of values that this fixed-width vector can hold
* and stay below the maximum vector size limit. This is the limit
* enforced when the vector is used to hold values in a repeated
* vector.
*/
public static final int MAX_CAPACITY = MAX_BUFFER_SIZE / VALUE_WIDTH;
/**
* Maximum number of values that this fixed-width vector can hold
* and stay below the maximum vector size limit and/or stay below
* the maximum item count. This is the limit enforced when the
* vector is used to hold required or nullable values.
*/
public static final int MAX_COUNT = Math.min(MAX_ROW_COUNT, MAX_CAPACITY);
/**
* Actual maximum vector size, in bytes, given the number of fixed-width
* values that either fit in the maximum overall vector size, or that
* is no larger than the maximum vector item count.
*/
public static final int NET_MAX_SIZE = VALUE_WIDTH * MAX_COUNT;
private final FieldReader reader = new BitReaderImpl(BitVector.this);
private final Accessor accessor = new Accessor();
private final Mutator mutator = new Mutator();
private int valueCount;
private int allocationSizeInBytes = INITIAL_VALUE_ALLOCATION;
private int allocationMonitor;
public BitVector(MaterializedField field, BufferAllocator allocator) {
super(field, allocator);
}
@Override
public FieldReader getReader() {
return reader;
}
@Override
public int getBufferSize() {
return getSizeFromCount(valueCount);
}
@Override
public int getBufferSizeFor(final int valueCount) {
return getSizeFromCount(valueCount);
}
public static int getSizeFromCount(int valueCount) {
return (valueCount + 7) / 8;
}
@Override
public int getValueCapacity() {
return (int) Math.min(Integer.MAX_VALUE, data.capacity() * 8L);
}
private int getByteIndex(int index) {
return index / 8;
}
@Override
public void setInitialCapacity(final int valueCount) {
allocationSizeInBytes = getSizeFromCount(valueCount);
}
@Override
public void allocateNew() {
if (!allocateNewSafe()) {
throw new OutOfMemoryException();
}
}
@Override
public boolean allocateNewSafe() {
long curAllocationSize = allocationSizeInBytes;
if (allocationMonitor > 10) {
curAllocationSize = Math.max(8, allocationSizeInBytes / 2);
allocationMonitor = 0;
} else if (allocationMonitor < -2) {
curAllocationSize = allocationSizeInBytes * 2L;
allocationMonitor = 0;
}
try {
allocateBytes(curAllocationSize);
} catch (OutOfMemoryException ex) {
return false;
}
return true;
}
@Override
public void reset() {
valueCount = 0;
allocationSizeInBytes = INITIAL_VALUE_ALLOCATION;
allocationMonitor = 0;
zeroVector();
super.reset();
}
/**
* Allocate a new memory space for this vector. Must be called prior to using the ValueVector.
*
* @param valueCount
* The number of values which can be contained within this vector.
*/
@Override
public void allocateNew(int valueCount) {
final int size = getSizeFromCount(valueCount);
allocateBytes(size);
}
private void allocateBytes(final long size) {
if (size > MAX_ALLOCATION_SIZE) {
throw new OversizedAllocationException("Requested amount of memory is more than max allowed allocation size");
}
final int curSize = (int) size;
clear();
data = allocator.buffer(curSize);
zeroVector();
allocationSizeInBytes = curSize;
}
/**
* Allocate new buffer with double capacity, and copy data into the new buffer. Replace vector's buffer with new buffer, and release old one
*/
public void reAlloc() {
long newAllocationSize = allocationSizeInBytes * 2L;
// Some operations, such as Value Vector#exchange, can change DrillBuf data field without corresponding allocation size changes.
// Check that the size of the allocation is sufficient to copy the old buffer.
while (newAllocationSize < data.capacity()) {
newAllocationSize *= 2L;
}
if (newAllocationSize > MAX_ALLOCATION_SIZE) {
throw new OversizedAllocationException("Requested amount of memory is more than max allowed allocation size");
}
final int curSize = (int)newAllocationSize;
final DrillBuf newBuf = allocator.buffer(curSize);
newBuf.setZero(0, newBuf.capacity());
newBuf.setBytes(0, data, 0, data.capacity());
data.release();
data = newBuf;
allocationSizeInBytes = curSize;
}
// This version uses the base version because this vector appears to not be
// used, so not worth the effort to avoid zero-fill.
@Override
public DrillBuf reallocRaw(int newAllocationSize) {
while (allocationSizeInBytes < newAllocationSize) {
reAlloc();
}
return data;
}
/**
* {@inheritDoc}
*/
@Override
public void zeroVector() {
data.setZero(0, data.capacity());
}
@Override
public int getValueWidth() {
return VALUE_WIDTH;
}
public void copyFrom(int inIndex, int outIndex, BitVector from) {
this.mutator.set(outIndex, from.accessor.get(inIndex));
}
public void copyFromSafe(int inIndex, int outIndex, BitVector from) {
while (outIndex >= this.getValueCapacity()) {
reAlloc();
}
copyFrom(inIndex, outIndex, from);
}
@Override
public void copyEntry(int toIndex, ValueVector from, int fromIndex) {
copyFrom(fromIndex, toIndex, (BitVector) from);
}
@Override
public void load(SerializedField metadata, DrillBuf buffer) {
Preconditions.checkArgument(field.getName().equals(metadata.getNamePart().getName()),
"The field %s doesn't match the provided metadata %s.", field, metadata);
final int valueCount = metadata.getValueCount();
final int expectedLength = getSizeFromCount(valueCount);
final int actualLength = metadata.getBufferLength();
assert expectedLength == actualLength: "expected and actual buffer sizes do not match";
clear();
data = buffer.slice(0, actualLength);
data.retain();
this.valueCount = valueCount;
}
@Override
public Mutator getMutator() {
return new Mutator();
}
@Override
public Accessor getAccessor() {
return new Accessor();
}
@Override
public TransferPair getTransferPair(BufferAllocator allocator) {
return new TransferImpl(getField(), allocator);
}
@Override
public TransferPair getTransferPair(String ref, BufferAllocator allocator) {
return new TransferImpl(getField().withPath(ref), allocator);
}
@Override
public TransferPair makeTransferPair(ValueVector to) {
return new TransferImpl((BitVector) to);
}
public void transferTo(BitVector target) {
target.clear();
if (target.data != null) {
target.data.release();
}
target.data = data;
target.data.retain(1);
target.valueCount = valueCount;
clear();
}
public void splitAndTransferTo(int startIndex, int length, BitVector target) {
assert startIndex + length <= valueCount;
int firstByteIndex = getByteIndex(startIndex);//byte offset of the first src byte
int numBytesHoldingSourceBits = getSizeFromCount(length); //src bytes to read (including start/end bytes that might not be fully copied)
int firstBitOffset = startIndex % 8; //Offset of first src bit within the first src byte
if (firstBitOffset == 0) {
target.clear();
// slice
if (target.data != null) {
target.data.release();
}
target.data = data.slice(firstByteIndex, numBytesHoldingSourceBits);
target.data.retain(1);
} else {
// Copy data
// When the first bit starts from the middle of a byte (firstBitOffset != 0), copy data from src BitVector.
// Each byte in the target is composed by a part in i-th byte, another part in (i+1)-th byte.
// The last byte copied to target is a bit tricky :
// 1) if length requires partly byte (length % 8 !=0), copy the remaining bits only.
// 2) otherwise, copy the last byte in the same way as to the prior bytes.
target.clear();
target.allocateNew(length);
// TODO maybe do this one word at a time, rather than byte?
byte byteI, byteIPlus1 = 0;
for (int i = 0; i < numBytesHoldingSourceBits - 1; i++) {
byteI = this.data.getByte(firstByteIndex + i);
byteIPlus1 = this.data.getByte(firstByteIndex + i + 1);
// Extract higher-X bits from first byte i and lower-Y bits from byte (i + 1), where X + Y = 8 bits
// Lower-Y bits are the MS bits of the byte to be written (target byte) and Higher-X are the LS bits.
// The target bytes are assembled in accordance to little-endian ordering (byte[0] = LS bit, byte[7] = MS bit)
target.data.setByte(i, (((byteI & 0xFF) >>> firstBitOffset) + (byteIPlus1 << (8 - firstBitOffset))));
}
//Copying the last n bits
//Copy length is not a byte-multiple
if (length % 8 != 0) {
// start is not byte aligned so we have to copy some bits from the last full byte read in the
// previous loop
// if numBytesHoldingSourceBits == 1, lastButOneByte is the first byte, but we have not read it yet, so read it
byte lastButOneByte = (numBytesHoldingSourceBits == 1) ? this.data.getByte(firstByteIndex) : byteIPlus1;
byte bitsFromLastButOneByte = (byte)((lastButOneByte & 0xFF) >>> firstBitOffset);
// if last bit to be copied is before the end of the first byte, then mask of the trailing extra bits
if (8 > (length + firstBitOffset)) {
byte mask = (byte)((0x1 << length) - 1);
bitsFromLastButOneByte = (byte)((bitsFromLastButOneByte & mask));
}
// If we have to read more bits than what we have already read, read it into lastByte otherwise set lastByte to 0.
// (length % 8) is num of remaining bits to be read.
// (8 - firstBitOffset) is the number of bits already read into lastButOneByte but not used in the previous write.
// We do not have to read more bits if (8 - firstBitOffset >= length % 8)
final int lastByte = (8 - firstBitOffset >= length % 8) ?
0 : this.data.getByte(firstByteIndex + numBytesHoldingSourceBits);
target.data.setByte(numBytesHoldingSourceBits - 1, bitsFromLastButOneByte + (lastByte << (8 - firstBitOffset)));
} else {
target.data.setByte(numBytesHoldingSourceBits - 1,
(((this.data.getByte(firstByteIndex + numBytesHoldingSourceBits - 1) & 0xFF) >>> firstBitOffset) +
(this.data.getByte(firstByteIndex + numBytesHoldingSourceBits) << (8 - firstBitOffset))));
}
}
target.getMutator().setValueCount(length);
}
@Override
public void exchange(ValueVector other) {
super.exchange(other);
int temp = valueCount;
valueCount = ((BitVector) other).valueCount;
((BitVector) other).valueCount = temp;
}
private class TransferImpl implements TransferPair {
BitVector to;
public TransferImpl(MaterializedField field, BufferAllocator allocator) {
this.to = new BitVector(field, allocator);
}
public TransferImpl(BitVector to) {
this.to = to;
}
@Override
public BitVector getTo() {
return to;
}
@Override
public void transfer() {
transferTo(to);
}
@Override
public void splitAndTransfer(int startIndex, int length) {
splitAndTransferTo(startIndex, length, to);
}
@Override
public void copyValueSafe(int fromIndex, int toIndex) {
to.copyFromSafe(fromIndex, toIndex, BitVector.this);
}
}
private void incrementAllocationMonitor() {
++allocationMonitor;
}
public class Accessor extends BaseAccessor {
/**
* Get the byte holding the desired bit, then mask all other bits. Iff the result is 0, the bit was not set.
*
* @param index
* position of the bit in the vector
* @return 1 if set, otherwise 0
*/
public final int get(int index) {
int byteIndex = index >> 3;
byte b = data.getByte(byteIndex);
int bitIndex = index & 7;
return Long.bitCount(b & (1L << bitIndex));
}
@Override
public boolean isNull(int index) {
return false;
}
@Override
public final Boolean getObject(int index) {
return Boolean.valueOf(get(index) != 0);
}
@Override
public final int getValueCount() {
return valueCount;
}
public final void get(int index, BitHolder holder) {
holder.value = get(index);
}
public final void get(int index, NullableBitHolder holder) {
holder.isSet = 1;
holder.value = get(index);
}
}
/**
* MutableBit implements a vector of bit-width values. Elements in the vector are accessed by position from the
* logical start of the vector. Values should be pushed onto the vector sequentially, but may be randomly accessed.
*
* NB: this class is automatically generated from ValueVectorTypes.tdd using FreeMarker.
*/
public class Mutator extends BaseMutator {
private Mutator() { }
/**
* Set the bit at the given index to the specified value.
*
* @param index
* position of the bit to set
* @param value
* value to set (either 1 or 0)
*/
public final void set(int index, int value) {
int byteIndex = index >> 3;
int bitIndex = index & 7;
byte currentByte = data.getByte(byteIndex);
byte bitMask = (byte) (1L << bitIndex);
if (value != 0) {
currentByte |= bitMask;
} else {
currentByte -= (bitMask & currentByte);
}
data.setByte(byteIndex, currentByte);
}
public final void set(int index, BitHolder holder) {
set(index, holder.value);
}
final void set(int index, NullableBitHolder holder) {
set(index, holder.value);
}
public void setSafe(int index, int value) {
while (index >= getValueCapacity()) {
reAlloc();
}
set(index, value);
}
public void setSafe(int index, BitHolder holder) {
while (index >= getValueCapacity()) {
reAlloc();
}
set(index, holder.value);
}
public void setSafe(int index, NullableBitHolder holder) {
while (index >= getValueCapacity()) {
reAlloc();
}
set(index, holder.value);
}
@Override
public final void setValueCount(int valueCount) {
int currentValueCapacity = getValueCapacity();
BitVector.this.valueCount = valueCount;
int idx = getSizeFromCount(valueCount);
while (valueCount > getValueCapacity()) {
reAlloc();
}
if (valueCount > 0 && currentValueCapacity > valueCount * 2) {
incrementAllocationMonitor();
} else if (allocationMonitor > 0) {
allocationMonitor = 0;
}
VectorTrimmer.trim(data, idx);
}
@Override
public final void generateTestData(int values) {
boolean even = true;
for (int i = 0; i < values; i++, even = !even) {
if (even) {
set(i, 1);
}
}
setValueCount(values);
}
}
@Override
public void clear() {
valueCount = 0;
super.clear();
}
@Override
public int getPayloadByteCount(int valueCount) {
return getSizeFromCount(valueCount);
}
@Override
public void toNullable(ValueVector nullableVector) {
NullableBitVector dest = (NullableBitVector) nullableVector;
dest.getMutator().fromNotNullable(this);
}
}