Last active
September 11, 2018 10:14
-
-
Save StagPoint/5413963ea367fd9bec9a3eb60cb0097c to your computer and use it in GitHub Desktop.
Packed Integer Array (C#) - When the range of values is known ahead of time, and that range of values can be expressed in fewer bits than a full int, this class allows you to store the elements in a smaller amount of memory by packing individual elements together into fewer array elements.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright (c) 2017 StagPoint Software | |
namespace StagPoint.Collections | |
{ | |
using System; | |
using System.Threading; | |
public class PackedIntArray | |
{ | |
#region Private instance variables | |
private readonly int m_ElementBits; | |
private readonly long m_ElementMask; | |
private readonly int m_ElementsPerWord; | |
private readonly int m_minValue; | |
private readonly int m_maxValue; | |
private readonly long[] m_values; | |
private readonly int m_length; | |
#endregion | |
#region Public properties | |
public int Length { get { return m_length; } } | |
public int this[ int index ] | |
{ | |
get { return Get( index ); } | |
set { Set( index, value ); } | |
} | |
#endregion | |
#region Constructor | |
/// <summary> | |
/// Disallows parameterless construction | |
/// </summary> | |
private PackedIntArray() | |
{ | |
throw new NotImplementedException(); | |
} | |
public PackedIntArray( int length, int minValue, int maxValue ) | |
{ | |
if( length < 1 || length > int.MaxValue ) | |
throw new ArgumentOutOfRangeException( "width" ); | |
if( minValue >= maxValue ) | |
{ | |
throw new ArgumentException( "Maximum value must be greater than Minimum value" ); | |
} | |
m_minValue = minValue; | |
m_maxValue = maxValue; | |
m_length = length; | |
var wordSize = ( sizeof( ulong ) * 8 ); | |
m_ElementBits = numberOfBitsNeeded( minValue, maxValue ); | |
if( m_ElementBits >= wordSize ) | |
{ | |
throw new ArgumentException( "This range of values cannot be packed" ); | |
} | |
m_ElementMask = ( 1u << m_ElementBits ) - 1u; | |
m_ElementsPerWord = wordSize / m_ElementBits; | |
m_values = new long[ getArrayLength( length, m_ElementsPerWord ) ]; | |
} | |
#endregion | |
#region Public functions | |
public void Clear() | |
{ | |
Array.Clear( m_values, 0, m_values.Length ); | |
} | |
public void Set( int index, int value ) | |
{ | |
if( index < 0 || index > Length - 1 ) | |
throw new IndexOutOfRangeException(); | |
if( value < m_minValue || value > m_maxValue ) | |
throw new ArgumentException( "Value is outside of the configured range" ); | |
var unsignedValue = (long)( value - m_minValue ); | |
var storageIndex = index / m_ElementsPerWord; | |
var offset = index % m_ElementsPerWord * m_ElementBits; | |
var offsetMask = ~( m_ElementMask << offset ); | |
unsignedValue = ( unsignedValue & m_ElementMask ) << offset; | |
long test = 0; | |
long original = 0; | |
do | |
{ | |
test = m_values[ storageIndex ]; | |
original = Interlocked.CompareExchange( ref m_values[ storageIndex ], test & offsetMask | unsignedValue, test ); | |
} while( test != original ); | |
} | |
public int Get( int index ) | |
{ | |
if( index < 0 || index > Length - 1 ) | |
throw new IndexOutOfRangeException(); | |
var storageIndex = index / m_ElementsPerWord; | |
var offset = index % m_ElementsPerWord * m_ElementBits; | |
var unsignedValue = ( Interlocked.Read( ref m_values[ storageIndex ] ) >> offset ) & m_ElementMask; | |
return (int)unsignedValue + m_minValue; | |
} | |
#endregion | |
#region Private utility functions | |
private static byte numberOfBitsNeeded( long lowValue, long highValue ) | |
{ | |
var range = highValue - lowValue; | |
var log2 = Math.Log( range, 2 ); | |
var numberOfBits = (int)Math.Floor( log2 ) + 1; | |
return (byte)numberOfBits; | |
} | |
private static int getArrayLength( int n, int div ) | |
{ | |
if( n <= 0 ) | |
return 0; | |
else | |
return ( n - 1 ) / div + 1; | |
} | |
#endregion | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Read and Write operations should be thread-safe, but I make no guarantees. I have not encountered any issues in my testing, but my testing is also far from exhaustive and thread safety is not critical in my use-case. Note that this only means that the write operation and read operation are thread-safe, and that calling code still has to worry about ABA and all other classes of problems that accompany multi-threaded code.
Your mileage may vary, TANSTAAFL, user assumes all risks, no warranty (express or implied), etc., etc.