Skip to content

Instantly share code, notes, and snippets.

@StagPoint
Last active September 11, 2018 10:14
Show Gist options
  • Save StagPoint/5413963ea367fd9bec9a3eb60cb0097c to your computer and use it in GitHub Desktop.
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.
// 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
}
}
@StagPoint
Copy link
Author

StagPoint commented Mar 7, 2017

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment