1 package org.apache.lucene.util.packed;
4 * Licensed to the Apache Software Foundation (ASF) under one or more
5 * contributor license agreements. See the NOTICE file distributed with
6 * this work for additional information regarding copyright ownership.
7 * The ASF licenses this file to You under the Apache License, Version 2.0
8 * (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
20 import org.apache.lucene.store.DataInput;
21 import org.apache.lucene.util.RamUsageEstimator;
23 import java.io.IOException;
24 import java.util.Arrays;
27 * Space optimized random access capable array of values with a fixed number of
28 * bits. The maximum number of bits/value is 31. Use {@link Packed64} for higher
31 * The implementation strives to avoid conditionals and expensive operations,
32 * sacrificing code clarity to achieve better performance.
35 class Packed32 extends PackedInts.ReaderImpl implements PackedInts.Mutable {
36 static final int BLOCK_SIZE = 32; // 32 = int, 64 = long
37 static final int BLOCK_BITS = 5; // The #bits representing BLOCK_SIZE
38 static final int MOD_MASK = BLOCK_SIZE - 1; // x % BLOCK_SIZE
40 private static final int ENTRY_SIZE = BLOCK_SIZE + 1;
41 private static final int FAC_BITPOS = 3;
44 * In order to make an efficient value-getter, conditionals should be
45 * avoided. A value can be positioned inside of a block, requiring shifting
46 * left or right or it can span two blocks, requiring a left-shift on the
47 * first block and a right-shift on the right block.
49 * By always shifting the first block both left and right, we get exactly
50 * the right bits. By always shifting the second block right and applying
51 * a mask, we get the right bits there. After that, we | the two bitsets.
53 private static final int[][] SHIFTS =
54 new int[ENTRY_SIZE][ENTRY_SIZE * FAC_BITPOS];
55 private static final int[][] MASKS = new int[ENTRY_SIZE][ENTRY_SIZE];
57 static { // Generate shifts
58 for (int elementBits = 1 ; elementBits <= BLOCK_SIZE ; elementBits++) {
59 for (int bitPos = 0 ; bitPos < BLOCK_SIZE ; bitPos++) {
60 int[] currentShifts = SHIFTS[elementBits];
61 int base = bitPos * FAC_BITPOS;
62 currentShifts[base ] = bitPos;
63 currentShifts[base + 1] = BLOCK_SIZE - elementBits;
64 if (bitPos <= BLOCK_SIZE - elementBits) { // Single block
65 currentShifts[base + 2] = 0;
66 MASKS[elementBits][bitPos] = 0;
67 } else { // Two blocks
68 int rBits = elementBits - (BLOCK_SIZE - bitPos);
69 currentShifts[base + 2] = BLOCK_SIZE - rBits;
70 MASKS[elementBits][bitPos] = ~(~0 << rBits);
77 * The setter requires more masking than the getter.
79 private static final int[][] WRITE_MASKS =
80 new int[ENTRY_SIZE][ENTRY_SIZE * FAC_BITPOS];
82 for (int elementBits = 1 ; elementBits <= BLOCK_SIZE ; elementBits++) {
83 int elementPosMask = ~(~0 << elementBits);
84 int[] currentShifts = SHIFTS[elementBits];
85 int[] currentMasks = WRITE_MASKS[elementBits];
86 for (int bitPos = 0 ; bitPos < BLOCK_SIZE ; bitPos++) {
87 int base = bitPos * FAC_BITPOS;
88 currentMasks[base ] =~((elementPosMask
89 << currentShifts[base + 1])
90 >>> currentShifts[base]);
91 if (bitPos <= BLOCK_SIZE - elementBits) { // Second block not used
92 currentMasks[base+1] = ~0; // Keep all bits
93 currentMasks[base+2] = 0; // Or with 0
95 currentMasks[base+1] = ~(elementPosMask
96 << currentShifts[base + 2]);
97 currentMasks[base+2] = currentShifts[base + 2] == 0 ? 0 : ~0;
104 private int[] blocks;
106 // Cached calculations
107 private int maxPos; // blocks.length * BLOCK_SIZE / bitsPerValue - 1
108 private int[] shifts; // The shifts for the current bitsPerValue
109 private int[] readMasks;
110 private int[] writeMasks;
113 * Creates an array with the internal structures adjusted for the given
114 * limits and initialized to 0.
115 * @param valueCount the number of elements.
116 * @param bitsPerValue the number of bits available for any given value.
117 * Note: bitsPerValue >32 is not supported by this implementation.
119 public Packed32(int valueCount, int bitsPerValue) {
120 this(new int[(int)(((long)valueCount) * bitsPerValue / BLOCK_SIZE + 2)],
121 valueCount, bitsPerValue);
125 * Creates an array with content retrieved from the given DataInput.
126 * @param in a DataInput, positioned at the start of Packed64-content.
127 * @param valueCount the number of elements.
128 * @param bitsPerValue the number of bits available for any given value.
129 * @throws java.io.IOException if the values for the backing array could not
132 public Packed32(DataInput in, int valueCount, int bitsPerValue)
134 super(valueCount, bitsPerValue);
135 int size = size(bitsPerValue, valueCount);
136 blocks = new int[size + 1]; // +1 due to non-conditional tricks
137 // TODO: find a faster way to bulk-read ints...
138 for(int i = 0 ; i < size ; i++) {
139 blocks[i] = in.readInt();
142 in.readInt(); // Align to long
147 private static int size(int bitsPerValue, int valueCount) {
148 final long totBitCount = (long) valueCount * bitsPerValue;
149 return (int) (totBitCount/32 + ((totBitCount % 32 == 0 ) ? 0:1));
154 * Creates an array backed by the given blocks.
156 * Note: The blocks are used directly, so changes to the given block will
157 * affect the Packed32-structure.
158 * @param blocks used as the internal backing array.
159 * @param valueCount the number of values.
160 * @param bitsPerValue the number of bits available for any given value.
161 * Note: bitsPerValue >32 is not supported by this implementation.
163 public Packed32(int[] blocks, int valueCount, int bitsPerValue) {
164 // TODO: Check that blocks.length is sufficient for holding length values
165 super(valueCount, bitsPerValue);
166 if (bitsPerValue > 31) {
167 throw new IllegalArgumentException(String.format(
168 "This array only supports values of 31 bits or less. The "
169 + "required number of bits was %d. The Packed64 "
170 + "implementation allows values with more than 31 bits",
173 this.blocks = blocks;
177 private void updateCached() {
178 readMasks = MASKS[bitsPerValue];
179 maxPos = (int)((((long)blocks.length) * BLOCK_SIZE / bitsPerValue) - 2);
180 shifts = SHIFTS[bitsPerValue];
181 writeMasks = WRITE_MASKS[bitsPerValue];
185 * @param index the position of the value.
186 * @return the value at the given index.
188 public long get(final int index) {
189 final long majorBitPos = (long)index * bitsPerValue;
190 final int elementPos = (int)(majorBitPos >>> BLOCK_BITS); // / BLOCK_SIZE
191 final int bitPos = (int)(majorBitPos & MOD_MASK); // % BLOCK_SIZE);
193 final int base = bitPos * FAC_BITPOS;
195 return ((blocks[elementPos] << shifts[base]) >>> shifts[base+1]) |
196 ((blocks[elementPos+1] >>> shifts[base+2]) & readMasks[bitPos]);
199 public void set(final int index, final long value) {
200 final int intValue = (int)value;
201 final long majorBitPos = (long)index * bitsPerValue;
202 final int elementPos = (int)(majorBitPos >>> BLOCK_BITS); // / BLOCK_SIZE
203 final int bitPos = (int)(majorBitPos & MOD_MASK); // % BLOCK_SIZE);
204 final int base = bitPos * FAC_BITPOS;
206 blocks[elementPos ] = (blocks[elementPos ] & writeMasks[base])
207 | (intValue << shifts[base + 1] >>> shifts[base]);
208 blocks[elementPos+1] = (blocks[elementPos+1] & writeMasks[base+1])
209 | ((intValue << shifts[base + 2])
210 & writeMasks[base+2]);
213 public void clear() {
214 Arrays.fill(blocks, 0);
218 public String toString() {
219 return "Packed32(bitsPerValue=" + bitsPerValue + ", maxPos=" + maxPos
220 + ", elements.length=" + blocks.length + ")";
223 public long ramBytesUsed() {
224 return RamUsageEstimator.NUM_BYTES_ARRAY_HEADER
225 + blocks.length * RamUsageEstimator.NUM_BYTES_INT;