blob: 9c21f7076556fef197795a565c0320f1c1b71f98 [file] [log] [blame]
using Lucene.Net.Diagnostics;
using Lucene.Net.Support;
using System;
using System.IO;
using System.Runtime.CompilerServices;
namespace Lucene.Net.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.
*/
using DataInput = Lucene.Net.Store.DataInput;
/// <summary>
/// Space optimized random access capable array of values with a fixed number of
/// bits/value. Values are packed contiguously.
/// <para/>
/// The implementation strives to perform af fast as possible under the
/// constraint of contiguous bits, by avoiding expensive operations. This comes
/// at the cost of code clarity.
/// <para/>
/// Technical details: this implementation is a refinement of a non-branching
/// version. The non-branching get and set methods meant that 2 or 4 atomics in
/// the underlying array were always accessed, even for the cases where only
/// 1 or 2 were needed. Even with caching, this had a detrimental effect on
/// performance.
/// Related to this issue, the old implementation used lookup tables for shifts
/// and masks, which also proved to be a bit slower than calculating the shifts
/// and masks on the fly.
/// See https://issues.apache.org/jira/browse/LUCENE-4062 for details.
/// </summary>
public class Packed64 : PackedInt32s.MutableImpl
{
internal const int BLOCK_SIZE = 64; // 32 = int, 64 = long
internal const int BLOCK_BITS = 6; // The #bits representing BLOCK_SIZE
internal const int MOD_MASK = BLOCK_SIZE - 1; // x % BLOCK_SIZE
/// <summary>
/// Values are stores contiguously in the blocks array.
/// </summary>
private readonly long[] blocks;
/// <summary>
/// A right-aligned mask of width BitsPerValue used by <see cref="Get(int)"/>.
/// </summary>
private readonly long maskRight;
/// <summary>
/// Optimization: Saves one lookup in <see cref="Get(int)"/>.
/// </summary>
private readonly int bpvMinusBlockSize;
/// <summary>
/// Creates an array with the internal structures adjusted for the given
/// limits and initialized to 0. </summary>
/// <param name="valueCount"> The number of elements. </param>
/// <param name="bitsPerValue"> The number of bits available for any given value. </param>
public Packed64(int valueCount, int bitsPerValue)
: base(valueCount, bitsPerValue)
{
PackedInt32s.Format format = PackedInt32s.Format.PACKED;
int longCount = format.Int64Count(PackedInt32s.VERSION_CURRENT, valueCount, bitsPerValue);
this.blocks = new long[longCount];
maskRight = (long)((ulong)(~0L << (BLOCK_SIZE - bitsPerValue)) >> (BLOCK_SIZE - bitsPerValue));
bpvMinusBlockSize = bitsPerValue - BLOCK_SIZE;
}
/// <summary>
/// Creates an array with content retrieved from the given <see cref="DataInput"/>. </summary>
/// <param name="in"> A <see cref="DataInput"/>, positioned at the start of Packed64-content. </param>
/// <param name="valueCount"> The number of elements. </param>
/// <param name="bitsPerValue"> The number of bits available for any given value. </param>
/// <exception cref="IOException"> If the values for the backing array could not
/// be retrieved. </exception>
public Packed64(int packedIntsVersion, DataInput @in, int valueCount, int bitsPerValue)
: base(valueCount, bitsPerValue)
{
PackedInt32s.Format format = PackedInt32s.Format.PACKED;
long byteCount = format.ByteCount(packedIntsVersion, valueCount, bitsPerValue); // to know how much to read
int longCount = format.Int64Count(PackedInt32s.VERSION_CURRENT, valueCount, bitsPerValue); // to size the array
blocks = new long[longCount];
// read as many longs as we can
for (int i = 0; i < byteCount / 8; ++i)
{
blocks[i] = @in.ReadInt64();
}
int remaining = (int)(byteCount % 8);
if (remaining != 0)
{
// read the last bytes
long lastLong = 0;
for (int i = 0; i < remaining; ++i)
{
lastLong |= (@in.ReadByte() & 0xFFL) << (56 - i * 8);
}
blocks[blocks.Length - 1] = lastLong;
}
maskRight = (long)((ulong)(~0L << (BLOCK_SIZE - bitsPerValue)) >> (BLOCK_SIZE - bitsPerValue));
bpvMinusBlockSize = bitsPerValue - BLOCK_SIZE;
}
/// <param name="index"> The position of the value. </param>
/// <returns> The value at the given index. </returns>
public override long Get(int index)
{
// The abstract index in a bit stream
long majorBitPos = (long)index * m_bitsPerValue;
// The index in the backing long-array
int elementPos = (int)(((ulong)majorBitPos) >> BLOCK_BITS);
// The number of value-bits in the second long
long endBits = (majorBitPos & MOD_MASK) + bpvMinusBlockSize;
if (endBits <= 0) // Single block
{
return ((long)((ulong)blocks[elementPos] >> (int)-endBits)) & maskRight;
}
// Two blocks
return ((blocks[elementPos] << (int)endBits) | ((long)((ulong)blocks[elementPos + 1] >> (int)(BLOCK_SIZE - endBits)))) & maskRight;
}
public override int Get(int index, long[] arr, int off, int len)
{
if (Debugging.AssertsEnabled) Debugging.Assert(len > 0, "len must be > 0 (got {0})", len);
if (Debugging.AssertsEnabled) Debugging.Assert(index >= 0 && index < m_valueCount);
len = Math.Min(len, m_valueCount - index);
if (Debugging.AssertsEnabled) Debugging.Assert(off + len <= arr.Length);
int originalIndex = index;
PackedInt32s.IDecoder decoder = BulkOperation.Of(PackedInt32s.Format.PACKED, m_bitsPerValue);
// go to the next block where the value does not span across two blocks
int offsetInBlocks = index % decoder.Int64ValueCount;
if (offsetInBlocks != 0)
{
for (int i = offsetInBlocks; i < decoder.Int64ValueCount && len > 0; ++i)
{
arr[off++] = Get(index++);
--len;
}
if (len == 0)
{
return index - originalIndex;
}
}
// bulk get
if (Debugging.AssertsEnabled) Debugging.Assert(index % decoder.Int64ValueCount == 0);
int blockIndex = (int)((ulong)((long)index * m_bitsPerValue) >> BLOCK_BITS);
if (Debugging.AssertsEnabled) Debugging.Assert((((long)index * m_bitsPerValue) & MOD_MASK) == 0);
int iterations = len / decoder.Int64ValueCount;
decoder.Decode(blocks, blockIndex, arr, off, iterations);
int gotValues = iterations * decoder.Int64ValueCount;
index += gotValues;
len -= gotValues;
if (Debugging.AssertsEnabled) Debugging.Assert(len >= 0);
if (index > originalIndex)
{
// stay at the block boundary
return index - originalIndex;
}
else
{
// no progress so far => already at a block boundary but no full block to get
if (Debugging.AssertsEnabled) Debugging.Assert(index == originalIndex);
return base.Get(index, arr, off, len);
}
}
public override void Set(int index, long value)
{
// The abstract index in a contiguous bit stream
long majorBitPos = (long)index * m_bitsPerValue;
// The index in the backing long-array
int elementPos = (int)((long)((ulong)majorBitPos >> BLOCK_BITS)); // / BLOCK_SIZE
// The number of value-bits in the second long
long endBits = (majorBitPos & MOD_MASK) + bpvMinusBlockSize;
if (endBits <= 0) // Single block
{
blocks[elementPos] = blocks[elementPos] & ~(maskRight << (int)-endBits) | (value << (int)-endBits);
return;
}
// Two blocks
blocks[elementPos] = blocks[elementPos] & ~((long)((ulong)maskRight >> (int)endBits)) | ((long)((ulong)value >> (int)endBits));
blocks[elementPos + 1] = blocks[elementPos + 1] & ((long)(unchecked((ulong)~0L) >> (int)endBits)) | (value << (int)(BLOCK_SIZE - endBits));
}
public override int Set(int index, long[] arr, int off, int len)
{
if (Debugging.AssertsEnabled) Debugging.Assert(len > 0, "len must be > 0 (got {0})", len);
if (Debugging.AssertsEnabled) Debugging.Assert(index >= 0 && index < m_valueCount);
len = Math.Min(len, m_valueCount - index);
if (Debugging.AssertsEnabled) Debugging.Assert(off + len <= arr.Length);
int originalIndex = index;
PackedInt32s.IEncoder encoder = BulkOperation.Of(PackedInt32s.Format.PACKED, m_bitsPerValue);
// go to the next block where the value does not span across two blocks
int offsetInBlocks = index % encoder.Int64ValueCount;
if (offsetInBlocks != 0)
{
for (int i = offsetInBlocks; i < encoder.Int64ValueCount && len > 0; ++i)
{
Set(index++, arr[off++]);
--len;
}
if (len == 0)
{
return index - originalIndex;
}
}
// bulk set
if (Debugging.AssertsEnabled) Debugging.Assert(index % encoder.Int64ValueCount == 0);
int blockIndex = (int)((ulong)((long)index * m_bitsPerValue) >> BLOCK_BITS);
if (Debugging.AssertsEnabled) Debugging.Assert((((long)index * m_bitsPerValue) & MOD_MASK) == 0);
int iterations = len / encoder.Int64ValueCount;
encoder.Encode(arr, off, blocks, blockIndex, iterations);
int setValues = iterations * encoder.Int64ValueCount;
index += setValues;
len -= setValues;
if (Debugging.AssertsEnabled) Debugging.Assert(len >= 0);
if (index > originalIndex)
{
// stay at the block boundary
return index - originalIndex;
}
else
{
// no progress so far => already at a block boundary but no full block to get
if (Debugging.AssertsEnabled) Debugging.Assert(index == originalIndex);
return base.Set(index, arr, off, len);
}
}
public override string ToString()
{
return "Packed64(bitsPerValue=" + m_bitsPerValue + ", size=" + Count + ", elements.length=" + blocks.Length + ")";
}
public override long RamBytesUsed()
{
return RamUsageEstimator.AlignObjectSize(
RamUsageEstimator.NUM_BYTES_OBJECT_HEADER
+ 3 * RamUsageEstimator.NUM_BYTES_INT32 // bpvMinusBlockSize,valueCount,bitsPerValue
+ RamUsageEstimator.NUM_BYTES_INT64 // maskRight
+ RamUsageEstimator.NUM_BYTES_OBJECT_REF) // blocks ref
+ RamUsageEstimator.SizeOf(blocks);
}
public override void Fill(int fromIndex, int toIndex, long val)
{
if (Debugging.AssertsEnabled)
{
Debugging.Assert(PackedInt32s.BitsRequired(val) <= BitsPerValue);
Debugging.Assert(fromIndex <= toIndex);
}
// minimum number of values that use an exact number of full blocks
int nAlignedValues = 64 / Gcd(64, m_bitsPerValue);
int span = toIndex - fromIndex;
if (span <= 3 * nAlignedValues)
{
// there needs be at least 2 * nAlignedValues aligned values for the
// block approach to be worth trying
base.Fill(fromIndex, toIndex, val);
return;
}
// fill the first values naively until the next block start
int fromIndexModNAlignedValues = fromIndex % nAlignedValues;
if (fromIndexModNAlignedValues != 0)
{
for (int i = fromIndexModNAlignedValues; i < nAlignedValues; ++i)
{
Set(fromIndex++, val);
}
}
if (Debugging.AssertsEnabled) Debugging.Assert(fromIndex % nAlignedValues == 0);
// compute the long[] blocks for nAlignedValues consecutive values and
// use them to set as many values as possible without applying any mask
// or shift
int nAlignedBlocks = (nAlignedValues * m_bitsPerValue) >> 6;
long[] nAlignedValuesBlocks;
{
Packed64 values = new Packed64(nAlignedValues, m_bitsPerValue);
for (int i = 0; i < nAlignedValues; ++i)
{
values.Set(i, val);
}
nAlignedValuesBlocks = values.blocks;
if (Debugging.AssertsEnabled) Debugging.Assert(nAlignedBlocks <= nAlignedValuesBlocks.Length);
}
int startBlock = (int)((ulong)((long)fromIndex * m_bitsPerValue) >> 6);
int endBlock = (int)((ulong)((long)toIndex * m_bitsPerValue) >> 6);
for (int block = startBlock; block < endBlock; ++block)
{
long blockValue = nAlignedValuesBlocks[block % nAlignedBlocks];
blocks[block] = blockValue;
}
// fill the gap
for (int i = (int)(((long)endBlock << 6) / m_bitsPerValue); i < toIndex; ++i)
{
Set(i, val);
}
}
private static int Gcd(int a, int b)
{
if (a < b)
{
return Gcd(b, a);
}
else if (b == 0)
{
return a;
}
else
{
return Gcd(b, a % b);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Clear()
{
Arrays.Fill(blocks, 0L);
}
}
}