Skip to content

Commit e0cab76

Browse files
committed
Merge branch 'main' into importer
2 parents 90965b9 + 5792d74 commit e0cab76

File tree

10 files changed

+128
-150
lines changed

10 files changed

+128
-150
lines changed

src/Paprika.Tests/Store/DataPageTests.cs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,26 @@ public void Should_hold_all_needed_full_branches_of_Merkle(byte oddity)
6464
var batch = NewBatch(BatchId);
6565
var data = ((IBatchContext)batch).GetNewPage<DataPage>(out _);
6666

67+
const int count = 16;
68+
6769
var path = NibblePath.Empty;
6870
data.Set(path, GetValue(path), batch);
6971

70-
const int count = 16;
72+
for (byte i = 0; i < count; i++)
73+
{
74+
path = NibblePath.Single(i, oddity);
75+
data.Set(path, GetValue(path), batch);
76+
}
77+
78+
// next batch
79+
batch = batch.Next();
80+
81+
data = new DataPage(batch.GetWritableCopy(data.AsPage()));
82+
83+
// set again
84+
path = NibblePath.Empty;
85+
data.Set(path, GetValue(path), batch);
86+
7187
for (byte i = 0; i < count; i++)
7288
{
7389
path = NibblePath.Single(i, oddity);
@@ -81,7 +97,7 @@ public void Should_hold_all_needed_full_branches_of_Merkle(byte oddity)
8197
result.SequenceEqual(GetValue(path)).Should().BeTrue();
8298
}
8399

84-
batch.PageCount.Should().Be(3, "One for the data page and two for the Merkle children");
100+
batch.PageCount.Should().Be(6, "One for the data page and two for the Merkle children");
85101

86102
return;
87103
static byte[] GetValue(in NibblePath path)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using FluentAssertions;
2+
using Paprika.Store;
3+
4+
namespace Paprika.Tests.Store;
5+
6+
public class StorageFanOutTests
7+
{
8+
private const uint Meg = 1024u * 1024u;
9+
10+
[TestCase(0u)]
11+
[TestCase(1u)]
12+
[TestCase(1023u)]
13+
[TestCase(1024u)]
14+
[TestCase(1025u)]
15+
[TestCase(Meg - 1)]
16+
[TestCase(Meg)]
17+
[TestCase(Meg + 1)]
18+
[TestCase(Meg * 2 - 1)]
19+
[TestCase(Meg * 2)]
20+
[TestCase(Meg * 2 + 1)]
21+
[TestCase(Meg * 4 - 1)]
22+
[TestCase(Meg * 4)]
23+
[TestCase(Meg * 4 + 1)]
24+
[TestCase(Meg * 8 - 1)]
25+
[TestCase(Meg * 8)]
26+
[TestCase(Meg * 8 + 1)]
27+
[TestCase(Meg * 16 - 1)]
28+
[TestCase(Meg * 16)]
29+
[TestCase(Meg * 16 + 1)]
30+
[TestCase(Meg * 32 - 1)]
31+
[TestCase(Meg * 32)]
32+
[TestCase(Meg * 32 + 1)]
33+
[TestCase(Meg * 64 - 1)]
34+
[TestCase(Meg * 64)]
35+
[TestCase(Meg * 64 + 1)]
36+
public void AssertBoundaries(uint at)
37+
{
38+
(uint next0, int index0) = StorageFanOut.GetIndex(at, 0);
39+
index0.Should().BeLessThan(1024, $"L0 failed at: {at}");
40+
41+
(uint next1, int index1) = StorageFanOut.GetIndex(next0, 1);
42+
index1.Should().BeLessThan(1024, $"L1 failed at: {at}");
43+
44+
(byte bucket2, int index2) = StorageFanOut.Level2Page.GetIndex(next1);
45+
index2.Should().BeLessThan(256, $"L2 failed at: {at}");
46+
bucket2.Should().BeLessThan(16, $"L2 failed at: {at}");
47+
}
48+
}

src/Paprika/Data/SlottedArray.cs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Runtime.InteropServices;
88
using System.Runtime.Intrinsics;
99
using Paprika.Crypto;
10+
using Paprika.Merkle;
1011
using Paprika.Utils;
1112

1213
namespace Paprika.Data;
@@ -1216,10 +1217,10 @@ public void Clear()
12161217

12171218
private const int NotFound = -1;
12181219

1219-
[OptimizationOpportunity(OptimizationType.CPU,
1220-
"key encoding is delayed but it might be called twice, here + TrySet")]
12211220
private int TryGetImpl(in NibblePath key, ushort hash, byte preamble, out Span<byte> data)
12221221
{
1222+
// AssertHeader();
1223+
12231224
var count = _header.Low / Slot.TotalSize;
12241225
var jump = DoubleVectorSize / sizeof(ushort);
12251226
var aligned = AlignToDoubleVectorSize(_header.Low) / sizeof(ushort);
@@ -1751,6 +1752,21 @@ private struct Header
17511752

17521753
public readonly ushort TakenAfterOneMoreSlot => (ushort)(AlignToDoubleVectorSize(Low + Slot.TotalSize) + High);
17531754
}
1755+
1756+
// private void AssertHeader()
1757+
// {
1758+
// if (_header.Deleted > _data.Length / Slot.Size)
1759+
// {
1760+
// throw new Exception("Deleted breached potential size");
1761+
// }
1762+
// // Debug.Assert(_header.Deleted <= _data.Length / Slot.Size, "Deleted breached the ");
1763+
//
1764+
// if (_header.High + _header.Low > _data.Length)
1765+
// {
1766+
// throw new Exception("Breached HiLo");
1767+
// }
1768+
// // Debug.Assert(_header.High + _header.Low <= _data.Length);
1769+
// }
17541770
}
17551771

17561772
public readonly ref struct MapSource

src/Paprika/Data/UShortSlottedArray.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -330,8 +330,6 @@ public bool TryGet(ushort key, out ReadOnlySpan<byte> data)
330330
return false;
331331
}
332332

333-
[OptimizationOpportunity(OptimizationType.CPU,
334-
"key encoding is delayed but it might be called twice, here + TrySet")]
335333
private bool TryGetImpl(ushort key, out Span<byte> data, out int slotIndex)
336334
{
337335
var to = _header.Low;

src/Paprika/Store/BottomPage.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -566,10 +566,10 @@ public Page Set(in NibblePath key, in ReadOnlySpan<byte> data, IBatchContext bat
566566

567567
foreach (var item in copy.EnumerateAll())
568568
{
569-
bottom.Set(item.Key, item.RawData, batch);
569+
page.Set(item.Key, item.RawData, batch);
570570
}
571571

572-
bottom.Set(key, data, batch);
572+
page.Set(key, data, batch);
573573

574574
ArrayPool<byte>.Shared.Return(array);
575575

src/Paprika/Store/DataPage.cs

Lines changed: 20 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ private static void Set(DbAddress at, in NibblePath key, in ReadOnlySpan<byte> d
149149
current = childAddr;
150150
}
151151

152+
return;
153+
152154
static bool TryTurnToFanOut(Page page, Page child, int bucket, IBatchContext batch)
153155
{
154156
if (page.Header.Level % 2 != 0 || page.Header.PageType != PageType.DataPage)
@@ -174,6 +176,8 @@ static bool TryTurnToFanOut(Page page, Page child, int bucket, IBatchContext bat
174176
var children = payload.Buckets;
175177
payload.Buckets.Clear();
176178

179+
// First, set all the grand-children right
180+
177181
for (byte i = 0; i < Payload.NotFanOutBucketCount; i++)
178182
{
179183
var p = batch.GetAt(children[i]);
@@ -197,14 +201,9 @@ static bool TryTurnToFanOut(Page page, Page child, int bucket, IBatchContext bat
197201
}
198202

199203
// The child data page is no longer needed and can be recycled with its Merkle side-cars
200-
if (dp.Data.MerkleLeft.IsNull == false)
204+
if (dp.Data.Merkle.IsNull == false)
201205
{
202-
batch.RegisterForFutureReuse(batch.GetAt(dp.Data.MerkleLeft), true);
203-
}
204-
205-
if (dp.Data.MerkleRight.IsNull == false)
206-
{
207-
batch.RegisterForFutureReuse(batch.GetAt(dp.Data.MerkleRight), true);
206+
batch.RegisterForFutureReuse(batch.GetAt(dp.Data.Merkle), true);
208207
}
209208

210209
batch.RegisterForFutureReuse(dp.AsPage(), true);
@@ -231,16 +230,14 @@ private static bool TrySetAtBottom(in NibblePath key, ReadOnlySpan<byte> data, I
231230
/// </summary>
232231
private const int MerkleInMapToNibble = 3;
233232

234-
private const int MerkleInRightFromInclusive = 9;
235-
236233
private static void SetLocally(in NibblePath key, ReadOnlySpan<byte> data, IBatchContext batch, ref Payload payload,
237234
PageHeader header)
238235
{
239236
// The key should be kept locally
240237
var map = new SlottedArray(payload.DataSpan);
241238

242239
// Check if deletion with empty local
243-
if (data.IsEmpty && payload.MerkleLeft.IsNull)
240+
if (data.IsEmpty && payload.Merkle.IsNull)
244241
{
245242
map.Delete(key);
246243
return;
@@ -252,88 +249,24 @@ private static void SetLocally(in NibblePath key, ReadOnlySpan<byte> data, IBatc
252249
return;
253250
}
254251

255-
var left = batch.EnsureWritableOrGetNew<ChildBottomPage>(ref payload.MerkleLeft, header.Level);
256-
257-
ChildBottomPage right;
258-
if (payload.MerkleRight.IsNull)
259-
{
260-
// Only left exist, try to move everything there.
261-
foreach (var item in map.EnumerateAll())
262-
{
263-
// We keep these three values
264-
if (ShouldBeKeptLocalInMap(item.Key))
265-
continue;
266-
267-
if (left.Map.TrySet(item.Key, item.RawData))
268-
{
269-
map.Delete(item);
270-
}
271-
}
272-
273-
if (map.TrySet(key, data))
274-
{
275-
// All good, map can hold the data.
276-
return;
277-
}
278-
279-
// Not enough space. Create the right and perform the split
280-
right = batch.GetNewPage<ChildBottomPage>(out payload.MerkleRight, header.Level);
281-
282-
// Only left exist, try to move everything there.
283-
foreach (var item in left.Map.EnumerateAll())
284-
{
285-
// We keep these three values
286-
if (ShouldBeKeptInRight(item.Key))
287-
{
288-
right.Map.Set(item.Key, item.RawData);
289-
left.Map.Delete(item);
290-
}
291-
}
292-
}
293-
294-
right = new ChildBottomPage(batch.EnsureWritableCopy(ref payload.MerkleRight));
252+
var merkle = payload.Merkle.IsNull
253+
? batch.GetNewCleanPage<ChildBottomPage>(out payload.Merkle, header.Level).AsPage()
254+
: batch.EnsureWritableCopy(ref payload.Merkle);
295255

296-
// Redistribute keys again
256+
// Move all that are not local to the Merkle page.
297257
foreach (var item in map.EnumerateAll())
298258
{
299259
// We keep these three values
300260
if (ShouldBeKeptLocalInMap(item.Key))
301261
continue;
302262

303-
if (ShouldBeKeptInRight(item.Key))
304-
{
305-
if (item.RawData.IsEmpty)
306-
right.Map.Delete(item.Key);
307-
else
308-
right.Map.Set(item.Key, item.RawData);
309-
}
310-
else
311-
{
312-
if (item.RawData.IsEmpty)
313-
left.Map.Delete(item.Key);
314-
else
315-
left.Map.Set(item.Key, item.RawData);
316-
}
317-
263+
merkle.Set(item.Key, item.RawData, batch);
318264
map.Delete(item);
319265
}
320266

321-
if (ShouldBeKeptLocalInMap(key))
322-
{
323-
map.Set(key, data);
324-
}
325-
else if (ShouldBeKeptInRight(key))
326-
{
327-
right.Map.Set(key, data);
328-
}
329-
else
330-
{
331-
left.Map.Set(key, data);
332-
}
267+
map.Set(key, data);
333268
}
334269

335-
private static bool ShouldBeKeptInRight(in NibblePath key) => key.Nibble0 >= MerkleInRightFromInclusive;
336-
337270
private static bool ShouldBeKeptLocal(in NibblePath key) => key.IsEmpty || key.Length == 1;
338271

339272
/// <summary>
@@ -357,19 +290,15 @@ public struct Payload
357290
/// <summary>
358291
/// The size of the raw byte data held in this page. Must be long aligned.
359292
/// </summary>
360-
private const int DataSize = Size - BucketSize - DbAddress.Size * 2 - sizeof(int);
293+
private const int DataSize = Size - BucketSize - DbAddress.Size - sizeof(int);
361294

362295
private const int DataOffset = Size - DataSize;
363296

364-
365297
[FieldOffset(0)] public DbAddressList.Of256 Buckets;
366298

367-
[FieldOffset(BucketSize)] public DbAddress MerkleLeft;
299+
[FieldOffset(BucketSize)] public DbAddress Merkle;
368300

369301
[FieldOffset(BucketSize + DbAddress.Size)]
370-
public DbAddress MerkleRight;
371-
372-
[FieldOffset(BucketSize + DbAddress.Size * 2)]
373302
public int ChildDataPages;
374303

375304
/// <summary>
@@ -385,14 +314,12 @@ public struct Payload
385314
public void Clear()
386315
{
387316
new SlottedArray(DataSpan).Clear();
388-
MerkleLeft = default;
389-
MerkleRight = default;
317+
Merkle = default;
390318
Buckets.Clear();
391319
ChildDataPages = 0;
392320
}
393321

394-
public bool IsClean => MerkleLeft.IsNull &&
395-
MerkleRight.IsNull &&
322+
public bool IsClean => Merkle.IsNull &&
396323
ChildDataPages == 0 &&
397324
new SlottedArray(DataSpan).IsEmpty &&
398325
Buckets.IsClean;
@@ -469,18 +396,12 @@ private bool TryGetLocally(scoped in NibblePath key, IPageResolver batch, out Re
469396
return true;
470397
}
471398

472-
// TODO: potential IO optimization to search left only if the right does not exist or the key does not belong to the right
473-
if (Data.MerkleLeft.IsNull == false && batch.GetAt(Data.MerkleLeft).TryGet(batch, key, out result))
399+
if (Data.Merkle.IsNull)
474400
{
475-
return true;
476-
}
477-
478-
if (Data.MerkleRight.IsNull == false && batch.GetAt(Data.MerkleRight).TryGet(batch, key, out result))
479-
{
480-
return true;
401+
return false;
481402
}
482403

483-
return false;
404+
return batch.GetAt(Data.Merkle).TryGet(batch, key, out result);
484405
}
485406

486407
public SlottedArray Map => new(Data.DataSpan);

src/Paprika/Store/PagedDb.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -533,7 +533,11 @@ public IDictionary<Keccak, uint> IdCache
533533

534534
public void Prefetch(DbAddress address) => db.Prefetch(address);
535535

536-
public Page GetAt(DbAddress address) => db._manager.GetAt(address);
536+
public Page GetAt(DbAddress address)
537+
{
538+
root.Assert(address);
539+
return db._manager.GetAt(address);
540+
}
537541

538542
public override string ToString() => $"{nameof(ReadOnlyBatch)}, Name: {name}, BatchId: {BatchId}";
539543
}
@@ -695,8 +699,7 @@ public void VerifyDbPagesOnCommit()
695699
public override Page GetAt(DbAddress address)
696700
{
697701
// Getting a page beyond root!
698-
var nextFree = _root.Data.NextFreePage;
699-
Debug.Assert(address < nextFree, $"Breached the next free page, NextFree: {nextFree}, retrieved {address}");
702+
_root.Assert(address);
700703
var page = _db.GetAt(address);
701704
return page;
702705
}

0 commit comments

Comments
 (0)