Skip to content

Commit

Permalink
Use Unsafe.SizeOf to calculate object sizes (#39)
Browse files Browse the repository at this point in the history
* Use Unsafe.SizeOf to calculate object sizes

* Fix build

* Wrap object size calculations in properties

* Fix variable length revivifaction

* Wrap object size calculations to property in GenericFrame
  • Loading branch information
PaulusParssinen authored Mar 21, 2024
1 parent dd08eab commit 5a82884
Show file tree
Hide file tree
Showing 7 changed files with 56 additions and 92 deletions.
12 changes: 0 additions & 12 deletions libs/client/Utility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,6 @@ namespace Garnet.client
/// </summary>
public static class Utility
{
/// <summary>
/// Get size of type
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value"></param>
/// <returns></returns>
internal static unsafe int GetSize<T>(this T value)
{
T[] arr = new T[2];
return (int)((long)Unsafe.AsPointer(ref arr[1]) - (long)Unsafe.AsPointer(ref arr[0]));
}

/// <summary>
/// Parse size in string notation into long.
/// Examples: 4k, 4K, 4KB, 4 KB, 8m, 8MB, 12g, 12 GB, 16t, 16 TB, 32p, 32 PB.
Expand Down
31 changes: 12 additions & 19 deletions libs/storage/Tsavorite/cs/src/core/Allocator/BlittableAllocator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,9 @@ internal sealed unsafe class BlittableAllocator<Key, Value> : AllocatorBase<Key,
private readonly long[] pointers;
private readonly long* nativePointers;

// Record sizes
private static readonly int recordSize = Utility.GetSize(default(Record<Key, Value>));
private static readonly int recordInfoSize = Utility.GetSize(default(RecordInfo));
private static readonly int keySize = Utility.GetSize(default(Key));
private static readonly int valueSize = Utility.GetSize(default(Value));

internal static int RecordSize => recordSize;
internal static int KeySize => Unsafe.SizeOf<Key>();
internal static int ValueSize => Unsafe.SizeOf<Value>();
internal static int RecordSize => Unsafe.SizeOf<Record<Key, Value>>();

private readonly OverflowPool<PageUnit> overflowPagePool;

Expand Down Expand Up @@ -88,37 +84,34 @@ public override ref Key GetKey(long physicalAddress)

public override ref Value GetValue(long physicalAddress)
{
return ref Unsafe.AsRef<Value>((byte*)physicalAddress + RecordInfo.GetLength() + keySize);
return ref Unsafe.AsRef<Value>((byte*)physicalAddress + RecordInfo.GetLength() + KeySize);
}

public override (int actualSize, int allocatedSize) GetRecordSize(long physicalAddress)
{
return (recordSize, recordSize);
return (RecordSize, RecordSize);
}

public override (int actualSize, int allocatedSize, int keySize) GetRMWCopyDestinationRecordSize<Input, TsavoriteSession>(ref Key key, ref Input input, ref Value value, ref RecordInfo recordInfo, TsavoriteSession tsavoriteSession)
{
return (recordSize, recordSize, keySize);
return (RecordSize, RecordSize, KeySize);
}

public override int GetAverageRecordSize()
{
return recordSize;
}
public override int GetAverageRecordSize() => RecordSize;

public override int GetFixedRecordSize() => recordSize;
public override int GetFixedRecordSize() => RecordSize;

public override (int actualSize, int allocatedSize, int keySize) GetRMWInitialRecordSize<Input, TsavoriteSession>(ref Key key, ref Input input, TsavoriteSession tsavoriteSession)
{
return (recordSize, recordSize, keySize);
return (RecordSize, RecordSize, KeySize);
}

public override (int actualSize, int allocatedSize, int keySize) GetRecordSize(ref Key key, ref Value value)
{
return (recordSize, recordSize, keySize);
return (RecordSize, RecordSize, KeySize);
}

public override int GetValueLength(ref Value value) => valueSize;
public override int GetValueLength(ref Value value) => ValueSize;

/// <summary>
/// Dispose memory allocator
Expand All @@ -136,7 +129,7 @@ public override void Dispose()

public override AddressInfo* GetValueAddressInfo(long physicalAddress)
{
return (AddressInfo*)((byte*)physicalAddress + RecordInfo.GetLength() + keySize);
return (AddressInfo*)((byte*)physicalAddress + RecordInfo.GetLength() + KeySize);
}

/// <summary>
Expand Down
73 changes: 36 additions & 37 deletions libs/storage/Tsavorite/cs/src/core/Allocator/GenericAllocator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,15 @@ internal sealed unsafe class GenericAllocator<Key, Value> : AllocatorBase<Key, V
// Tail offsets per segment, in object log
public readonly long[] segmentOffsets;
// Record sizes
private static readonly int recordSize = Utility.GetSize(default(Record<Key, Value>));
private readonly SerializerSettings<Key, Value> SerializerSettings;
private readonly bool keyBlittable = Utility.IsBlittable<Key>();
private readonly bool valueBlittable = Utility.IsBlittable<Value>();

internal static int RecordSize => recordSize;

// We do not support variable-length keys in GenericAllocator
private int keySize = Utility.GetSize(default(Key));
private int valueSize = Utility.GetSize(default(Value));
internal static int KeySize => Unsafe.SizeOf<Key>();
internal static int ValueSize => Unsafe.SizeOf<Value>();
internal static int RecordSize => Unsafe.SizeOf<Record<Key, Value>>();

private readonly OverflowPool<Record<Key, Value>[]> overflowPagePool;

Expand Down Expand Up @@ -136,7 +135,7 @@ void ReturnPage(int index)

public override void Initialize()
{
Initialize(recordSize);
Initialize(RecordSize);
}

/// <summary>
Expand All @@ -157,7 +156,7 @@ public override long GetStartLogicalAddress(long page)
public override long GetFirstValidLogicalAddress(long page)
{
if (page == 0)
return (page << LogPageSizeBits) + recordSize;
return (page << LogPageSizeBits) + RecordSize;

return page << LogPageSizeBits;
}
Expand All @@ -170,7 +169,7 @@ public override ref RecordInfo GetInfo(long physicalAddress)
// Index of page within the circular buffer
int pageIndex = (int)((physicalAddress >> LogPageSizeBits) & BufferSizeMask);

return ref values[pageIndex][offset / recordSize].info;
return ref values[pageIndex][offset / RecordSize].info;
}

public override ref RecordInfo GetInfoFromBytePointer(byte* ptr)
Expand All @@ -186,7 +185,7 @@ public override ref Key GetKey(long physicalAddress)
// Index of page within the circular buffer
int pageIndex = (int)((physicalAddress >> LogPageSizeBits) & BufferSizeMask);

return ref values[pageIndex][offset / recordSize].key;
return ref values[pageIndex][offset / RecordSize].key;
}

public override ref Value GetValue(long physicalAddress)
Expand All @@ -197,36 +196,36 @@ public override ref Value GetValue(long physicalAddress)
// Index of page within the circular buffer
int pageIndex = (int)((physicalAddress >> LogPageSizeBits) & BufferSizeMask);

return ref values[pageIndex][offset / recordSize].value;
return ref values[pageIndex][offset / RecordSize].value;
}

public override (int actualSize, int allocatedSize) GetRecordSize(long physicalAddress)
{
return (recordSize, recordSize);
return (RecordSize, RecordSize);
}

public override int GetValueLength(ref Value value) => valueSize;
public override int GetValueLength(ref Value value) => ValueSize;

public override (int actualSize, int allocatedSize, int keySize) GetRMWCopyDestinationRecordSize<Input, TsavoriteSession>(ref Key key, ref Input input, ref Value value, ref RecordInfo recordInfo, TsavoriteSession tsavoriteSession)
{
return (recordSize, recordSize, keySize);
return (RecordSize, RecordSize, KeySize);
}

public override int GetAverageRecordSize()
{
return recordSize;
return RecordSize;
}

public override int GetFixedRecordSize() => recordSize;
public override int GetFixedRecordSize() => RecordSize;

public override (int actualSize, int allocatedSize, int keySize) GetRMWInitialRecordSize<Input, TsavoriteSession>(ref Key key, ref Input input, TsavoriteSession tsavoriteSession)
{
return (recordSize, recordSize, keySize);
return (RecordSize, RecordSize, KeySize);
}

public override (int actualSize, int allocatedSize, int keySize) GetRecordSize(ref Key key, ref Value value)
{
return (recordSize, recordSize, keySize);
return (RecordSize, RecordSize, KeySize);
}

internal override bool TryComplete()
Expand Down Expand Up @@ -292,18 +291,18 @@ internal Record<Key, Value>[] AllocatePage()
return item;

Record<Key, Value>[] tmp;
if (PageSize % recordSize == 0)
tmp = new Record<Key, Value>[PageSize / recordSize];
if (PageSize % RecordSize == 0)
tmp = new Record<Key, Value>[PageSize / RecordSize];
else
tmp = new Record<Key, Value>[1 + (PageSize / recordSize)];
tmp = new Record<Key, Value>[1 + (PageSize / RecordSize)];
Array.Clear(tmp, 0, tmp.Length);
return tmp;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal long SnapToLogicalAddressBoundary(ref long logicalAddress)
{
return logicalAddress = ((logicalAddress - Constants.kFirstValidAddress) / recordSize) * recordSize + Constants.kFirstValidAddress;
return logicalAddress = ((logicalAddress - Constants.kFirstValidAddress) / RecordSize) * RecordSize + Constants.kFirstValidAddress;
}

public override long GetPhysicalAddress(long logicalAddress)
Expand Down Expand Up @@ -381,7 +380,7 @@ protected override void WriteAsyncToDevice<TContext>

internal override void ClearPage(long page, int offset)
{
Array.Clear(values[page % BufferSize], offset / recordSize, values[page % BufferSize].Length - offset / recordSize);
Array.Clear(values[page % BufferSize], offset / RecordSize, values[page % BufferSize].Length - offset / RecordSize);
}

internal override void FreePage(long page)
Expand Down Expand Up @@ -495,12 +494,12 @@ private void WriteAsync<TContext>(long flushPage, ulong alignedDestinationAddres
// Track the size to be written to the object log.
long endPosition = 0;

for (int i = start / recordSize; i < end / recordSize; i++)
for (int i = start / RecordSize; i < end / RecordSize; i++)
{
if (!src[i].info.Invalid)
{
// Calculate the logical address of the 'values' page currently being written.
var address = (flushPage << LogPageSizeBits) + i * recordSize;
var address = (flushPage << LogPageSizeBits) + i * RecordSize;

// Do not write v+1 records (e.g. during a checkpoint)
if (address < fuzzyStartLogicalAddress || !src[i].info.IsInNewVersion)
Expand All @@ -511,7 +510,7 @@ private void WriteAsync<TContext>(long flushPage, ulong alignedDestinationAddres
keySerializer.Serialize(ref src[i].key);

// Store the key address into the 'buffer' AddressInfo image as an offset into 'ms'.
var key_address = GetKeyAddressInfo((long)(buffer.aligned_pointer + i * recordSize));
var key_address = GetKeyAddressInfo((long)(buffer.aligned_pointer + i * RecordSize));
key_address->Address = pos;
key_address->Size = (int)(ms.Position - pos);
addr.Add((long)key_address);
Expand All @@ -524,7 +523,7 @@ private void WriteAsync<TContext>(long flushPage, ulong alignedDestinationAddres
valueSerializer.Serialize(ref src[i].value);

// Store the value address into the 'buffer' AddressInfo image as an offset into 'ms'.
var value_address = GetValueAddressInfo((long)(buffer.aligned_pointer + i * recordSize));
var value_address = GetValueAddressInfo((long)(buffer.aligned_pointer + i * RecordSize));
value_address->Address = pos;
value_address->Size = (int)(ms.Position - pos);
addr.Add((long)value_address);
Expand All @@ -534,13 +533,13 @@ private void WriteAsync<TContext>(long flushPage, ulong alignedDestinationAddres
else
{
// Mark v+1 records as invalid to avoid deserializing them on recovery
ref var record = ref Unsafe.AsRef<Record<Key, Value>>(buffer.aligned_pointer + i * recordSize);
ref var record = ref Unsafe.AsRef<Record<Key, Value>>(buffer.aligned_pointer + i * RecordSize);
record.info.SetInvalid();
}
}

// If this record's serialized size surpassed ObjectBlockSize or it's the last record to be written, write to the object log.
if (endPosition > ObjectBlockSize || i == (end / recordSize) - 1)
if (endPosition > ObjectBlockSize || i == (end / RecordSize) - 1)
{
var memoryStreamActualLength = ms.Position;
var memoryStreamTotalLength = (int)endPosition;
Expand Down Expand Up @@ -573,7 +572,7 @@ private void WriteAsync<TContext>(long flushPage, ulong alignedDestinationAddres
((AddressInfo*)address)->Address += _objAddr;

// If we have not written all records, prepare for the next chunk of records to be written.
if (i < (end / recordSize) - 1)
if (i < (end / RecordSize) - 1)
{
// Create a new MemoryStream for the next chunk of records to be written.
ms = new MemoryStream();
Expand Down Expand Up @@ -892,7 +891,7 @@ public void Deserialize(byte* raw, long ptr, long untilptr, Record<Key, Value>[]
while (ptr < untilptr)
{
ref Record<Key, Value> record = ref Unsafe.AsRef<Record<Key, Value>>(raw + ptr);
src[ptr / recordSize].info = record.info;
src[ptr / RecordSize].info = record.info;

if (!record.info.Invalid)
{
Expand All @@ -905,11 +904,11 @@ public void Deserialize(byte* raw, long ptr, long untilptr, Record<Key, Value>[]
stream.Seek(streamStartPos + key_addr->Address - start_addr, SeekOrigin.Begin);
}

keySerializer.Deserialize(out src[ptr / recordSize].key);
keySerializer.Deserialize(out src[ptr / RecordSize].key);
}
else
{
src[ptr / recordSize].key = record.key;
src[ptr / RecordSize].key = record.key;
}

if (!record.info.Tombstone)
Expand All @@ -923,11 +922,11 @@ public void Deserialize(byte* raw, long ptr, long untilptr, Record<Key, Value>[]
stream.Seek(streamStartPos + value_addr->Address - start_addr, SeekOrigin.Begin);
}

valueSerializer.Deserialize(out src[ptr / recordSize].value);
valueSerializer.Deserialize(out src[ptr / RecordSize].value);
}
else
{
src[ptr / recordSize].value = record.value;
src[ptr / RecordSize].value = record.value;
}
}
}
Expand Down Expand Up @@ -1129,7 +1128,7 @@ internal void PopulatePage(byte* src, int required_bytes, ref Record<Key, Value>
{
fixed (RecordInfo* pin = &destinationPage[0].info)
{
Debug.Assert(required_bytes <= recordSize * destinationPage.Length);
Debug.Assert(required_bytes <= RecordSize * destinationPage.Length);

Buffer.MemoryCopy(src, Unsafe.AsPointer(ref destinationPage[0]), required_bytes, required_bytes);
}
Expand Down Expand Up @@ -1174,10 +1173,10 @@ internal override void MemoryPageScan(long beginAddress, long endAddress, IObser
{
var page = (beginAddress >> LogPageSizeBits) % BufferSize;
long pageStartAddress = beginAddress & ~PageSizeMask;
int start = (int)(beginAddress & PageSizeMask) / recordSize;
int count = (int)(endAddress - beginAddress) / recordSize;
int start = (int)(beginAddress & PageSizeMask) / RecordSize;
int count = (int)(endAddress - beginAddress) / RecordSize;
int end = start + count;
using var iter = new MemoryPageScanIterator<Key, Value>(values[page], start, end, pageStartAddress, recordSize);
using var iter = new MemoryPageScanIterator<Key, Value>(values[page], start, end, pageStartAddress, RecordSize);
Debug.Assert(epoch.ThisInstanceProtected());
try
{
Expand Down
9 changes: 5 additions & 4 deletions libs/storage/Tsavorite/cs/src/core/Allocator/GenericFrame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT license.

using System;
using System.Runtime.CompilerServices;

namespace Tsavorite.core
{
Expand All @@ -12,7 +13,7 @@ internal sealed class GenericFrame<Key, Value> : IDisposable
{
private readonly Record<Key, Value>[][] frame;
public readonly int frameSize, pageSize;
private readonly int recordSize = Utility.GetSize(default(Record<Key, Value>));
private static int RecordSize => Unsafe.SizeOf<Record<Key, Value>>();

public GenericFrame(int frameSize, int pageSize)
{
Expand All @@ -24,10 +25,10 @@ public GenericFrame(int frameSize, int pageSize)
public void Allocate(int index)
{
Record<Key, Value>[] tmp;
if (pageSize % recordSize == 0)
tmp = new Record<Key, Value>[pageSize / recordSize];
if (pageSize % RecordSize == 0)
tmp = new Record<Key, Value>[pageSize / RecordSize];
else
tmp = new Record<Key, Value>[1 + (pageSize / recordSize)];
tmp = new Record<Key, Value>[1 + (pageSize / RecordSize)];
Array.Clear(tmp, 0, tmp.Length);
frame[index] = tmp;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,17 @@ internal struct RevivificationManager<Key, Value>
internal RevivificationStats stats = new();

internal readonly bool IsEnabled = false;
internal readonly int FixedValueLength;
internal readonly int FixedValueLength => Unsafe.SizeOf<Value>();
internal bool restoreDeletedRecordsIfBinIsFull;
internal bool useFreeRecordPoolForCTT;

internal readonly bool IsFixedLength => FixedValueLength != 0;
internal readonly bool IsFixedLength { get; }

internal double revivifiableFraction;

public RevivificationManager(TsavoriteKV<Key, Value> store, bool isFixedLen, RevivificationSettings revivSettings, LogSettings logSettings)
{
// Set these first in case revivification is not enabled; they still tell us not to expect fixed-length.
if (isFixedLen)
FixedValueLength = Utility.GetSize(default(Value));

IsFixedLength = isFixedLen;
revivifiableFraction = revivSettings is null || revivSettings.RevivifiableFraction == RevivificationSettings.DefaultRevivifiableFraction
? logSettings.MutableFraction
: revivSettings.RevivifiableFraction;
Expand Down
Loading

0 comments on commit 5a82884

Please sign in to comment.