blob: 62e77934ef336ac645d96813ba83ddb15c63e986 [file] [log] [blame]
package org.apache.lucene.util.packed;
/**
* 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.
*/
import org.apache.lucene.store.IndexInput;
import org.apache.lucene.util.RamUsageEstimator;
import java.io.IOException;
import java.util.Arrays;
/**
* Space optimized random access capable array of values with a fixed number of
* bits. For 32 bits/value and less, performance on 32 bit machines is not
* optimal. Consider using {@link Packed32} for such a setup.
* </p><p>
* The implementation strives to avoid conditionals and expensive operations,
* sacrificing code clarity to achieve better performance.
*/
class Packed64 extends PackedInts.ReaderImpl implements PackedInts.Mutable {
static final int BLOCK_SIZE = 64; // 32 = int, 64 = long
static final int BLOCK_BITS = 6; // The #bits representing BLOCK_SIZE
static final int MOD_MASK = BLOCK_SIZE - 1; // x % BLOCK_SIZE
private static final int ENTRY_SIZE = BLOCK_SIZE + 1;
private static final int FAC_BITPOS = 3;
/*
* In order to make an efficient value-getter, conditionals should be
* avoided. A value can be positioned inside of a block, requiring shifting
* left or right or it can span two blocks, requiring a left-shift on the
* first block and a right-shift on the right block.
* </p><p>
* By always shifting the first block both left and right, we get exactly
* the right bits. By always shifting the second block right and applying
* a mask, we get the right bits there. After that, we | the two bitsets.
*/
private static final int[][] SHIFTS =
new int[ENTRY_SIZE][ENTRY_SIZE * FAC_BITPOS];
//new int[BLOCK_SIZE+1][BLOCK_SIZE][BLOCK_SIZE+1];
private static final long[][] MASKS = new long[ENTRY_SIZE][ENTRY_SIZE];
static { // Generate shifts
for (int elementBits = 1 ; elementBits <= BLOCK_SIZE ; elementBits++) {
for (int bitPos = 0 ; bitPos < BLOCK_SIZE ; bitPos++) {
int[] currentShifts = SHIFTS[elementBits];
int base = bitPos * FAC_BITPOS;
currentShifts[base ] = bitPos;
currentShifts[base + 1] = BLOCK_SIZE - elementBits;
if (bitPos <= BLOCK_SIZE - elementBits) { // Single block
currentShifts[base + 2] = 0;
MASKS[elementBits][bitPos] = 0;
} else { // Two blocks
int rBits = elementBits - (BLOCK_SIZE - bitPos);
currentShifts[base + 2] = BLOCK_SIZE - rBits;
MASKS[elementBits][bitPos] = ~(~0L << rBits);
}
}
}
}
/*
* The setter requires more masking than the getter.
*/
private static final long[][] WRITE_MASKS =
new long[ENTRY_SIZE][ENTRY_SIZE * FAC_BITPOS];
static {
for (int elementBits = 1 ; elementBits <= BLOCK_SIZE ; elementBits++) {
long elementPosMask = ~(~0L << elementBits);
int[] currentShifts = SHIFTS[elementBits];
long[] currentMasks = WRITE_MASKS[elementBits];
for (int bitPos = 0 ; bitPos < BLOCK_SIZE ; bitPos++) {
int base = bitPos * FAC_BITPOS;
currentMasks[base ] =~((elementPosMask
<< currentShifts[base + 1])
>>> currentShifts[base]);
if (bitPos <= BLOCK_SIZE - elementBits) { // Second block not used
currentMasks[base+1] = ~0; // Keep all bits
currentMasks[base+2] = 0; // Or with 0
} else {
currentMasks[base+1] = ~(elementPosMask
<< currentShifts[base + 2]);
currentMasks[base+2] = currentShifts[base + 2] == 0 ? 0 : ~0;
}
}
}
}
/* The bits */
private long[] blocks;
// Cached calculations
private int maxPos; // blocks.length * BLOCK_SIZE / elementBits - 1
private int[] shifts; // The shifts for the current elementBits
private long[] readMasks;
private long[] writeMasks;
/**
* Creates an array with the internal structures adjusted for the given
* limits and initialized to 0.
* @param valueCount the number of elements.
* @param bitsPerValue the number of bits available for any given value.
*/
public Packed64(int valueCount, int bitsPerValue) {
// TODO: Test for edge-cases (2^31 values, 63 bitsPerValue)
// +2 due to the avoid-conditionals-trick. The last entry is always 0
this(new long[(int)((long)valueCount * bitsPerValue / BLOCK_SIZE + 2)],
valueCount, bitsPerValue);
}
/**
* Creates an array backed by the given blocks.
* </p><p>
* Note: The blocks are used directly, so changes to the given block will
* affect the Packed32-structure.
* @param blocks used as the internal backing array. Not that the last
* element cannot be addressed directly.
* @param valueCount the number of values.
* @param bitsPerValue the number of bits available for any given value.
*/
public Packed64(long[] blocks, int valueCount, int bitsPerValue) {
super(valueCount, bitsPerValue);
this.blocks = blocks;
updateCached();
}
/**
* Creates an array with content retrieved from the given IndexInput.
* @param in an IndexInput, positioned at the start of Packed64-content.
* @param valueCount the number of elements.
* @param bitsPerValue the number of bits available for any given value.
* @throws java.io.IOException if the values for the backing array could not
* be retrieved.
*/
public Packed64(IndexInput in, int valueCount, int bitsPerValue)
throws IOException {
super(valueCount, bitsPerValue);
int size = size(valueCount, bitsPerValue);
blocks = new long[size+1]; // +1 due to non-conditional tricks
// TODO: find a faster way to bulk-read longs...
for(int i=0;i<size;i++) {
blocks[i] = in.readLong();
}
updateCached();
}
private static int size(int valueCount, int bitsPerValue) {
final long totBitCount = (long) valueCount * bitsPerValue;
return (int)(totBitCount/64 + ((totBitCount % 64 == 0 ) ? 0:1));
}
private void updateCached() {
readMasks = MASKS[bitsPerValue];
shifts = SHIFTS[bitsPerValue];
writeMasks = WRITE_MASKS[bitsPerValue];
maxPos = (int)((((long)blocks.length) * BLOCK_SIZE / bitsPerValue) - 2);
}
/**
* @param index the position of the value.
* @return the value at the given index.
*/
public long get(final int index) {
final long majorBitPos = (long)index * bitsPerValue;
final int elementPos = (int)(majorBitPos >>> BLOCK_BITS); // / BLOCK_SIZE
final int bitPos = (int)(majorBitPos & MOD_MASK); // % BLOCK_SIZE);
final int base = bitPos * FAC_BITPOS;
return ((blocks[elementPos] << shifts[base]) >>> shifts[base+1]) |
((blocks[elementPos+1] >>> shifts[base+2]) & readMasks[bitPos]);
}
public void set(final int index, final long value) {
final long majorBitPos = (long)index * bitsPerValue;
final int elementPos = (int)(majorBitPos >>> BLOCK_BITS); // / BLOCK_SIZE
final int bitPos = (int)(majorBitPos & MOD_MASK); // % BLOCK_SIZE);
final int base = bitPos * FAC_BITPOS;
blocks[elementPos ] = (blocks[elementPos ] & writeMasks[base])
| (value << shifts[base + 1] >>> shifts[base]);
blocks[elementPos+1] = (blocks[elementPos+1] & writeMasks[base+1])
| ((value << shifts[base + 2]) & writeMasks[base+2]);
}
@Override
public String toString() {
return "Packed64(bitsPerValue=" + bitsPerValue + ", size="
+ size() + ", maxPos=" + maxPos
+ ", elements.length=" + blocks.length + ")";
}
public long ramBytesUsed() {
return RamUsageEstimator.NUM_BYTES_ARRAY_HEADER
+ blocks.length * RamUsageEstimator.NUM_BYTES_LONG;
}
public void clear() {
Arrays.fill(blocks, 0L);
}
}