All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.lucene.util.ByteBlockPool Maven / Gradle / Ivy

There is a newer version: 6.4.2_1
Show newest version
/*
 * 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.lucene.util;

import static org.apache.lucene.util.RamUsageEstimator.NUM_BYTES_OBJECT_REF;

import java.util.Arrays;

/**
 * This class enables the allocation of fixed-size buffers and their management as part of a buffer
 * array. Allocation is done through the use of an {@link Allocator} which can be customized, e.g.
 * to allow recycling old buffers. There are methods for writing ({@link #append(BytesRef)} and
 * reading from the buffers (e.g. {@link #readBytes(long, byte[], int, int)}, which handle
 * read/write operations across buffer boundaries.
 *
 * @lucene.internal
 */
public final class ByteBlockPool implements Accountable {
  private static final long BASE_RAM_BYTES =
      RamUsageEstimator.shallowSizeOfInstance(ByteBlockPool.class);

  /**
   * Use this to find the index of the buffer containing a byte, given an offset to that byte.
   *
   * 

bufferUpto = globalOffset >> BYTE_BLOCK_SHIFT * *

bufferUpto = globalOffset / BYTE_BLOCK_SIZE */ public static final int BYTE_BLOCK_SHIFT = 15; /** The size of each buffer in the pool. */ public static final int BYTE_BLOCK_SIZE = 1 << BYTE_BLOCK_SHIFT; /** * Use this to find the position of a global offset in a particular buffer. * *

positionInCurrentBuffer = globalOffset & BYTE_BLOCK_MASK * *

positionInCurrentBuffer = globalOffset % BYTE_BLOCK_SIZE */ public static final int BYTE_BLOCK_MASK = BYTE_BLOCK_SIZE - 1; /** Abstract class for allocating and freeing byte blocks. */ public abstract static class Allocator { // TODO: ByteBlockPool assume the blockSize is always {@link BYTE_BLOCK_SIZE}, but this class // allow arbitrary value of blockSize. We should make them consistent. protected final int blockSize; protected Allocator(int blockSize) { this.blockSize = blockSize; } public abstract void recycleByteBlocks(byte[][] blocks, int start, int end); public byte[] getByteBlock() { return new byte[blockSize]; } } /** A simple {@link Allocator} that never recycles. */ public static final class DirectAllocator extends Allocator { public DirectAllocator() { super(BYTE_BLOCK_SIZE); } @Override public void recycleByteBlocks(byte[][] blocks, int start, int end) {} } /** A simple {@link Allocator} that never recycles, but tracks how much total RAM is in use. */ public static class DirectTrackingAllocator extends Allocator { private final Counter bytesUsed; public DirectTrackingAllocator(Counter bytesUsed) { super(BYTE_BLOCK_SIZE); this.bytesUsed = bytesUsed; } @Override public byte[] getByteBlock() { bytesUsed.addAndGet(blockSize); return new byte[blockSize]; } @Override public void recycleByteBlocks(byte[][] blocks, int start, int end) { bytesUsed.addAndGet(-((end - start) * blockSize)); for (int i = start; i < end; i++) { blocks[i] = null; } } } /** Array of buffers currently used in the pool. Buffers are allocated if needed. */ private byte[][] buffers = new byte[10][]; /** index into the buffers array pointing to the current buffer used as the head */ private int bufferUpto = -1; // Which buffer we are upto /** Where we are in the head buffer. */ public int byteUpto = BYTE_BLOCK_SIZE; /** Current head buffer. */ public byte[] buffer; /** * Offset from the start of the first buffer to the start of the current buffer, which is * bufferUpto * BYTE_BLOCK_SIZE. The buffer pool maintains this offset because it is the first to * overflow if there are too many allocated blocks. */ public int byteOffset = -BYTE_BLOCK_SIZE; private final Allocator allocator; public ByteBlockPool(Allocator allocator) { this.allocator = allocator; } /** * Expert: Resets the pool to its initial state, while optionally reusing the first buffer. * Buffers that are not reused are reclaimed by {@link Allocator#recycleByteBlocks(byte[][], int, * int)}. Buffers can be filled with zeros before recycling them. This is useful if a slice pool * works on top of this byte pool and relies on the buffers being filled with zeros to find the * non-zero end of slices. * * @param zeroFillBuffers if {@code true} the buffers are filled with {@code 0}. This should be * set to {@code true} if this pool is used with slices. * @param reuseFirst if {@code true} the first buffer will be reused and calling {@link * ByteBlockPool#nextBuffer()} is not needed after reset iff the block pool was used before * ie. {@link ByteBlockPool#nextBuffer()} was called before. */ public void reset(boolean zeroFillBuffers, boolean reuseFirst) { if (bufferUpto != -1) { // We allocated at least one buffer if (zeroFillBuffers) { for (int i = 0; i < bufferUpto; i++) { // Fully zero fill buffers that we fully used Arrays.fill(buffers[i], (byte) 0); } // Partial zero fill the final buffer Arrays.fill(buffers[bufferUpto], 0, byteUpto, (byte) 0); } if (bufferUpto > 0 || !reuseFirst) { final int offset = reuseFirst ? 1 : 0; // Recycle all but the first buffer allocator.recycleByteBlocks(buffers, offset, 1 + bufferUpto); Arrays.fill(buffers, offset, 1 + bufferUpto, null); } if (reuseFirst) { // Re-use the first buffer bufferUpto = 0; byteUpto = 0; byteOffset = 0; buffer = buffers[0]; } else { bufferUpto = -1; byteUpto = BYTE_BLOCK_SIZE; byteOffset = -BYTE_BLOCK_SIZE; buffer = null; } } } /** * Allocates a new buffer and advances the pool to it. This method should be called once after the * constructor to initialize the pool. In contrast to the constructor, a {@link * ByteBlockPool#reset(boolean, boolean)} call will advance the pool to its first buffer * immediately. */ public void nextBuffer() { if (1 + bufferUpto == buffers.length) { // The buffer array is full - expand it byte[][] newBuffers = new byte[ArrayUtil.oversize(buffers.length + 1, NUM_BYTES_OBJECT_REF)][]; System.arraycopy(buffers, 0, newBuffers, 0, buffers.length); buffers = newBuffers; } // Allocate new buffer and advance the pool to it buffer = buffers[1 + bufferUpto] = allocator.getByteBlock(); bufferUpto++; byteUpto = 0; byteOffset = Math.addExact(byteOffset, BYTE_BLOCK_SIZE); } /** * Fill the provided {@link BytesRef} with the bytes at the specified offset and length. This will * avoid copying the bytes if the slice fits into a single block; otherwise, it uses the provided * {@link BytesRefBuilder} to copy bytes over. */ void setBytesRef(BytesRefBuilder builder, BytesRef result, long offset, int length) { result.length = length; int bufferIndex = Math.toIntExact(offset >> BYTE_BLOCK_SHIFT); byte[] buffer = buffers[bufferIndex]; int pos = (int) (offset & BYTE_BLOCK_MASK); if (pos + length <= BYTE_BLOCK_SIZE) { // Common case: The slice lives in a single block. Reference the buffer directly. result.bytes = buffer; result.offset = pos; } else { // Uncommon case: The slice spans at least 2 blocks, so we must copy the bytes. builder.growNoCopy(length); result.bytes = builder.get().bytes; result.offset = 0; readBytes(offset, result.bytes, 0, length); } } /** Appends the bytes in the provided {@link BytesRef} at the current position. */ public void append(final BytesRef bytes) { append(bytes.bytes, bytes.offset, bytes.length); } /** * Append the bytes from a source {@link ByteBlockPool} at a given offset and length * * @param srcPool the source pool to copy from * @param srcOffset the source pool offset * @param length the number of bytes to copy */ public void append(ByteBlockPool srcPool, long srcOffset, int length) { int bytesLeft = length; while (bytesLeft > 0) { int bufferLeft = BYTE_BLOCK_SIZE - byteUpto; if (bytesLeft < bufferLeft) { // fits within current buffer appendBytesSingleBuffer(srcPool, srcOffset, bytesLeft); break; } else { // fill up this buffer and move to next one if (bufferLeft > 0) { appendBytesSingleBuffer(srcPool, srcOffset, bufferLeft); bytesLeft -= bufferLeft; srcOffset += bufferLeft; } nextBuffer(); } } } // copy from source pool until no bytes left. length must be fit within the current head buffer private void appendBytesSingleBuffer(ByteBlockPool srcPool, long srcOffset, int length) { assert length <= BYTE_BLOCK_SIZE - byteUpto; // doing a loop as the bytes to copy might span across multiple byte[] in srcPool while (length > 0) { byte[] srcBytes = srcPool.buffers[Math.toIntExact(srcOffset >> BYTE_BLOCK_SHIFT)]; int srcPos = Math.toIntExact(srcOffset & BYTE_BLOCK_MASK); int bytesToCopy = Math.min(length, BYTE_BLOCK_SIZE - srcPos); System.arraycopy(srcBytes, srcPos, buffer, byteUpto, bytesToCopy); length -= bytesToCopy; srcOffset += bytesToCopy; byteUpto += bytesToCopy; } } /** * Append the provided byte array at the current position. * * @param bytes the byte array to write */ public void append(final byte[] bytes) { append(bytes, 0, bytes.length); } /** * Append some portion of the provided byte array at the current position. * * @param bytes the byte array to write * @param offset the offset of the byte array * @param length the number of bytes to write */ public void append(final byte[] bytes, int offset, int length) { int bytesLeft = length; while (bytesLeft > 0) { int bufferLeft = BYTE_BLOCK_SIZE - byteUpto; if (bytesLeft < bufferLeft) { // fits within current buffer System.arraycopy(bytes, offset, buffer, byteUpto, bytesLeft); byteUpto += bytesLeft; break; } else { // fill up this buffer and move to next one if (bufferLeft > 0) { System.arraycopy(bytes, offset, buffer, byteUpto, bufferLeft); } nextBuffer(); bytesLeft -= bufferLeft; offset += bufferLeft; } } } /** * Reads bytes out of the pool starting at the given offset with the given length into the given * byte array at offset off. * *

Note: this method allows to copy across block boundaries. */ public void readBytes(final long offset, final byte[] bytes, int bytesOffset, int bytesLength) { int bytesLeft = bytesLength; int bufferIndex = Math.toIntExact(offset >> BYTE_BLOCK_SHIFT); int pos = (int) (offset & BYTE_BLOCK_MASK); while (bytesLeft > 0) { byte[] buffer = buffers[bufferIndex++]; assert buffer != null; int chunk = Math.min(bytesLeft, BYTE_BLOCK_SIZE - pos); System.arraycopy(buffer, pos, bytes, bytesOffset, chunk); bytesOffset += chunk; bytesLeft -= chunk; pos = 0; } } /** * Read a single byte at the given offset * * @param offset the offset to read * @return the byte */ public byte readByte(final long offset) { int bufferIndex = (int) (offset >> BYTE_BLOCK_SHIFT); int pos = (int) (offset & BYTE_BLOCK_MASK); return buffers[bufferIndex][pos]; } @Override public long ramBytesUsed() { long size = BASE_RAM_BYTES; size += RamUsageEstimator.shallowSizeOf(buffers); for (byte[] buf : buffers) { size += RamUsageEstimator.sizeOfObject(buf); } return size; } /** the current position (in absolute value) of this byte pool */ public long getPosition() { return bufferUpto * allocator.blockSize + byteUpto; } /** Retrieve the buffer at the specified index from the buffer pool. */ public byte[] getBuffer(int bufferIndex) { return buffers[bufferIndex]; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy