Skip to content

Commit

Permalink
2 nibble enumerator provided
Browse files Browse the repository at this point in the history
  • Loading branch information
Scooletz committed Sep 5, 2024
1 parent 31dfbd2 commit f77586c
Show file tree
Hide file tree
Showing 2 changed files with 148 additions and 18 deletions.
70 changes: 62 additions & 8 deletions src/Paprika.Tests/Data/SlottedArrayTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ public Task Enumerate_all([Values(0, 1)] int odd)
var map = new SlottedArray(span);

var key0 = NibblePath.Empty;
var key1 = NibblePath.FromKey(stackalloc byte[1] { 7 }).SliceFrom(odd);
var key2 = NibblePath.FromKey(stackalloc byte[2] { 7, 13 }).SliceFrom(odd);
var key3 = NibblePath.FromKey(stackalloc byte[3] { 7, 13, 31 }).SliceFrom(odd);
var key4 = NibblePath.FromKey(stackalloc byte[4] { 7, 13, 31, 41 }).SliceFrom(odd);
var key1 = NibblePath.FromKey([7]).SliceFrom(odd);
var key2 = NibblePath.FromKey([7, 13]).SliceFrom(odd);
var key3 = NibblePath.FromKey([7, 13, 31]).SliceFrom(odd);
var key4 = NibblePath.FromKey([7, 13, 31, 41]).SliceFrom(odd);

map.SetAssert(key0, Data0);
map.SetAssert(key1, Data1);
Expand Down Expand Up @@ -98,10 +98,10 @@ public Task Enumerate_nibble([Values(1, 2, 3, 4)] int nibble)
var map = new SlottedArray(span);

var key0 = NibblePath.Empty;
var key1 = NibblePath.FromKey(stackalloc byte[1] { 0x1A });
var key2 = NibblePath.FromKey(stackalloc byte[2] { 0x2A, 13 });
var key3 = NibblePath.FromKey(stackalloc byte[3] { 0x3A, 13, 31 });
var key4 = NibblePath.FromKey(stackalloc byte[4] { 0x4A, 13, 31, 41 });
var key1 = NibblePath.FromKey([0x1A]);
var key2 = NibblePath.FromKey([0x2A, 13]);
var key3 = NibblePath.FromKey([0x3A, 13, 31]);
var key4 = NibblePath.FromKey([0x4A, 13, 31, 41]);

map.SetAssert(key0, Data0);
map.SetAssert(key1, Data1);
Expand Down Expand Up @@ -146,6 +146,60 @@ public Task Enumerate_nibble([Values(1, 2, 3, 4)] int nibble)
return Verify(span.ToArray());
}

[Test]
public void Enumerate_2_nibbles([Values(1, 2, 3, 4)] int nibble0)
{
const byte nibble1 = 0xA;

Span<byte> span = stackalloc byte[256];
var map = new SlottedArray(span);

var key0 = NibblePath.Empty;
var key1 = NibblePath.FromKey([0x10 | nibble1]);
var key2 = NibblePath.FromKey([0x20 | nibble1, 13]);
var key3 = NibblePath.FromKey([0x30 | nibble1, 13, 31]);
var key4 = NibblePath.FromKey([0x40 | nibble1, 13, 31, 41]);

map.SetAssert(key0, Data0);
map.SetAssert(key1, Data1);
map.SetAssert(key2, Data2);
map.SetAssert(key3, Data3);
map.SetAssert(key4, Data4);

map.GetAssert(key0, Data0);
map.GetAssert(key1, Data1);
map.GetAssert(key2, Data2);
map.GetAssert(key3, Data3);
map.GetAssert(key4, Data4);

var expected = nibble0 switch
{
1 => key1,
2 => key2,
3 => key3,
4 => key4,
_ => throw new Exception()
};

var data = nibble0 switch
{
1 => Data1,
2 => Data2,
3 => Data3,
4 => Data4,
_ => throw new Exception()
};

using var e = map.Enumerate2Nibbles((byte)nibble0, nibble1);

e.MoveNext().Should().BeTrue();

e.Current.Key.Equals(expected).Should().BeTrue();
e.Current.RawData.SequenceEqual(data).Should().BeTrue();

e.MoveNext().Should().BeFalse();
}

[Test]
public Task Enumerate_long_key([Values(0, 1)] int oddStart, [Values(0, 1)] int lengthCutOff)
{
Expand Down
96 changes: 86 additions & 10 deletions src/Paprika/Data/SlottedArray.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,20 @@ public void DeleteByPrefix(in NibblePath prefix)
Delete(item);
}
}
else if (prefix.Length == 2)
{
foreach (var item in Enumerate2Nibbles(prefix.FirstNibble, prefix.GetAt(1)))
{
if (item.Key.StartsWith(prefix))
{
Delete(item);
}
}
}
else
{
// TODO: Try to optimize by filtering by hash better. Right now, the filter is only by the first nibble.
foreach (var item in EnumerateNibble(prefix.FirstNibble))
// Filtering by 2 first nibbles should be sufficient to filter out a lot
foreach (var item in Enumerate2Nibbles(prefix.FirstNibble, prefix.GetAt(1)))
{
if (item.Key.StartsWith(prefix))
{
Expand Down Expand Up @@ -191,6 +201,7 @@ private bool TrySetImpl(ushort hash, byte preamble, in NibblePath trimmed, ReadO

public Enumerator EnumerateAll() => new(this);
public NibbleEnumerator EnumerateNibble(byte nibble) => new(this, nibble);
public Nibble2Enumerator Enumerate2Nibbles(byte nibble0, byte nibble1) => new(this, nibble0, nibble1);

[StructLayout(LayoutKind.Sequential, Pack = sizeof(byte), Size = Size)]
private ref struct Chunk
Expand Down Expand Up @@ -256,12 +267,10 @@ private void Build(Slot slot, out Item value)
value = new Item(key, data, _index);
}

public readonly void Dispose()
{
}

// a shortcut to not allocate, just copy the enumerator
public readonly Enumerator GetEnumerator() => this;

public void Dispose() { }
}

public ref struct NibbleEnumerator
Expand Down Expand Up @@ -320,16 +329,78 @@ private void Build(Slot slot, ushort hash, out Item value)
{
var span = _map.GetSlotPayload(_index, slot);
var key = Slot.UnPrepareKey(hash, slot.KeyPreamble, span, _bytes.Span, out var data);

value = new Item(key, data, _index);
}

public readonly void Dispose()
// a shortcut to not allocate, just copy the enumerator
public readonly NibbleEnumerator GetEnumerator() => this;

public void Dispose() { }
}

public ref struct Nibble2Enumerator
{
/// <summary>The map being enumerated.</summary>
private readonly SlottedArray _map;

private readonly byte _searched;

/// <summary>The next index to yield.</summary>
private int _index;

private Chunk _bytes;
private Item _current;

internal Nibble2Enumerator(SlottedArray map, byte nibble0, byte nibble1)
{
_map = map;
_searched = Slot.CombineNibbles(nibble0, nibble1);
_index = -1;
}

/// <summary>Advances the enumerator to the next element of the span.</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
var index = _index + 1;
var to = _map.Count;

var slot = _map.GetSlotRef(index);
var hash = _map.GetHashRef(index);

while (index < to &&
(slot.IsDeleted || slot.HasAtLeastTwoNibbles(hash, slot.KeyPreamble) == false ||
slot.GetNibble0And1(hash) != _searched)) // filter out deleted
{
// move by 1
index += 1;
slot = _map.GetSlotRef(index);
hash = _map.GetHashRef(index);
}

if (index < to)
{
_index = index;
Build(slot, hash, out _current);
return true;
}

return false;
}

public readonly Item Current => _current;

private void Build(Slot slot, ushort hash, out Item value)
{
var span = _map.GetSlotPayload(_index, slot);
var key = Slot.UnPrepareKey(hash, slot.KeyPreamble, span, _bytes.Span, out var data);
value = new Item(key, data, _index);
}

// a shortcut to not allocate, just copy the enumerator
public readonly NibbleEnumerator GetEnumerator() => this;
public readonly Nibble2Enumerator GetEnumerator() => this;

public void Dispose() { }
}

/// <summary>
Expand Down Expand Up @@ -1066,7 +1137,12 @@ public byte GetNibble0And1(ushort hash)
var nibble0 = (byte)(0x0F & (hash >> (3 * shift - odd * shift)));
var nibble1 = (byte)(0x0F & (hash >> (2 * shift - odd * shift)));

return (byte)((nibble0 << shift) + nibble1);
return CombineNibbles(nibble0, nibble1);
}

public static byte CombineNibbles(byte nibble0, byte nibble1)
{
return (byte)((nibble0 << NibblePath.NibbleShift) + nibble1);
}

public byte KeyPreamble
Expand Down

0 comments on commit f77586c

Please sign in to comment.