Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions benchmark/BDN.benchmark/BenchmarkCategories.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

namespace BDN.benchmark
{
/// <summary>
/// Category names applied to benchmarks via <see cref="BenchmarkDotNet.Attributes.BenchmarkCategoryAttribute"/>.
/// Categories group operations by the storage access pattern they exercise, which the results table is ordered by.
/// </summary>
internal static class BenchmarkCategories
{
/// <summary>Blind writes that overwrite the value without reading it first (e.g. SET, SETEX).</summary>
public const string Upsert = "Upsert";

/// <summary>Read-modify-write operations whose result depends on the existing value (e.g. SET NX/XX, INCR/DECR).</summary>
public const string RMW = "RMW";

/// <summary>Pure reads that do not modify the value (e.g. GET).</summary>
public const string Read = "Read";
}
}
39 changes: 39 additions & 0 deletions benchmark/BDN.benchmark/NamespaceTypeOrderer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

using System.Collections.Immutable;
using BenchmarkDotNet.Order;
using BenchmarkDotNet.Reports;
using BenchmarkDotNet.Running;

namespace BDN.benchmark
{
/// <summary>
/// Summary orderer that lays out the results table primarily by namespace, then by type, while preserving
/// BenchmarkDotNet's normal within-type ordering (category, then parameters, job, and declared method order).
/// <para>
/// This matters most for joined summaries (the <c>--join</c> switch, i.e.
/// <see cref="BenchmarkDotNet.Configs.ConfigOptions.JoinSummary"/>), where the default orderer interleaves
/// benchmarks from different types: it compares only the declared method index and ignores the declaring type
/// and namespace, so the n-th method of every type is grouped together. Ordering by namespace first keeps every
/// benchmark from a given namespace in one unbroken series, with its types — and the categories within each type —
/// laid out in a stable, predictable order.
/// </para>
/// </summary>
public sealed class NamespaceTypeOrderer : DefaultOrderer
{
public override IEnumerable<BenchmarkCase> GetSummaryOrder(ImmutableArray<BenchmarkCase> benchmarksCases, Summary summary)
{
// Order the types by namespace, then by type name, emitting each type's cases as a contiguous block.
var orderedTypeGroups = benchmarksCases
.GroupBy(benchmarkCase => benchmarkCase.Descriptor.Type)
.OrderBy(group => group.Key.Namespace ?? string.Empty, StringComparer.Ordinal)
.ThenBy(group => group.Key.Name, StringComparer.Ordinal);

// Within each type defer to the base orderer so categories (and parameters/jobs/methods) are handled normally.
foreach (var typeGroup in orderedTypeGroups)
foreach (var benchmarkCase in base.GetSummaryOrder(typeGroup.ToImmutableArray(), summary))
yield return benchmarkCase;
}
}
}
10 changes: 10 additions & 0 deletions benchmark/BDN.benchmark/Network/RawStringOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,33 +65,43 @@ public override void GlobalSetup()
}

[Benchmark]
[BenchmarkCategory(BenchmarkCategories.Upsert)]
public async ValueTask Set() => await Send(set);

[Benchmark]
[BenchmarkCategory(BenchmarkCategories.Upsert)]
public async ValueTask SetEx() => await Send(setex);

[Benchmark]
[BenchmarkCategory(BenchmarkCategories.RMW)]
public async ValueTask SetNx() => await Send(setnx);

[Benchmark]
[BenchmarkCategory(BenchmarkCategories.RMW)]
public async ValueTask SetXx() => await Send(setxx);

[Benchmark]
[BenchmarkCategory(BenchmarkCategories.Read)]
public async ValueTask GetFound() => await Send(getf);

[Benchmark]
[BenchmarkCategory(BenchmarkCategories.Read)]
public async ValueTask GetNotFound() => await Send(getnf);

[Benchmark]
[BenchmarkCategory(BenchmarkCategories.RMW)]
public async ValueTask Increment() => await Send(incr);

[Benchmark]
[BenchmarkCategory(BenchmarkCategories.RMW)]
public async ValueTask Decrement() => await Send(decr);

[Benchmark]
[BenchmarkCategory(BenchmarkCategories.RMW)]
public async ValueTask IncrementBy() => await Send(incrby);

[Benchmark]
[BenchmarkCategory(BenchmarkCategories.RMW)]
public async ValueTask DecrementBy() => await Send(decrby);
}
}
10 changes: 10 additions & 0 deletions benchmark/BDN.benchmark/Operations/LTM/RawStringOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -174,33 +174,43 @@ void SetupBatch(ref RandomKeyBatch batch, string keyPrefix, Func<long, string> m
}

[Benchmark]
[BenchmarkCategory(BenchmarkCategories.Upsert)]
public void Set() => SendRandomized(ref set);

[Benchmark]
[BenchmarkCategory(BenchmarkCategories.Upsert)]
public void SetEx() => SendRandomized(ref setex);

[Benchmark]
[BenchmarkCategory(BenchmarkCategories.RMW)]
public void SetNx() => SendRandomized(ref setnx);

[Benchmark]
[BenchmarkCategory(BenchmarkCategories.RMW)]
public void SetXx() => SendRandomized(ref setxx);

[Benchmark]
[BenchmarkCategory(BenchmarkCategories.Read)]
public void GetFound() => SendRandomized(ref getf);

[Benchmark]
[BenchmarkCategory(BenchmarkCategories.Read)]
public void GetNotFound() => SendRandomized(ref getnf);

[Benchmark]
[BenchmarkCategory(BenchmarkCategories.RMW)]
public void Increment() => SendRandomized(ref incr);

[Benchmark]
[BenchmarkCategory(BenchmarkCategories.RMW)]
public void Decrement() => SendRandomized(ref decr);

[Benchmark]
[BenchmarkCategory(BenchmarkCategories.RMW)]
public void IncrementBy() => SendRandomized(ref incrby);

[Benchmark]
[BenchmarkCategory(BenchmarkCategories.RMW)]
public void DecrementBy() => SendRandomized(ref decrby);

/// <summary>Overwrite every command's key in the batch with a freshly chosen random id, then send the batch.</summary>
Expand Down
10 changes: 10 additions & 0 deletions benchmark/BDN.benchmark/Operations/RawStringOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,33 +65,43 @@ public override void GlobalSetup()
}

[Benchmark]
[BenchmarkCategory(BenchmarkCategories.Upsert)]
public void Set() => Send(set);

[Benchmark]
[BenchmarkCategory(BenchmarkCategories.Upsert)]
public void SetEx() => Send(setex);

[Benchmark]
[BenchmarkCategory(BenchmarkCategories.RMW)]
public void SetNx() => Send(setnx);

[Benchmark]
[BenchmarkCategory(BenchmarkCategories.RMW)]
public void SetXx() => Send(setxx);

[Benchmark]
[BenchmarkCategory(BenchmarkCategories.Read)]
public void GetFound() => Send(getf);

[Benchmark]
[BenchmarkCategory(BenchmarkCategories.Read)]
public void GetNotFound() => Send(getnf);

[Benchmark]
[BenchmarkCategory(BenchmarkCategories.RMW)]
public void Increment() => Send(incr);

[Benchmark]
[BenchmarkCategory(BenchmarkCategories.RMW)]
public void Decrement() => Send(decr);

[Benchmark]
[BenchmarkCategory(BenchmarkCategories.RMW)]
public void IncrementBy() => Send(incrby);

[Benchmark]
[BenchmarkCategory(BenchmarkCategories.RMW)]
public void DecrementBy() => Send(decrby);
}
}
9 changes: 8 additions & 1 deletion benchmark/BDN.benchmark/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ static void Main(string[] args)

BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly)
#if DEBUG
.Run(passthroughArgs, new DebugInProcessConfig());
.Run(passthroughArgs, new DebugInProcessConfig()
.WithOrderer(new BDN.benchmark.NamespaceTypeOrderer())
.AddColumn(CategoriesColumn.Default));
#else
.Run(passthroughArgs, new BaseConfig());
#endif
Expand Down Expand Up @@ -86,6 +88,11 @@ public BaseConfig()
_ = AddColumnProvider(DefaultColumnProviders.Instance);
_ = WithSummaryStyle(SummaryStyle.Default.WithSizeUnit(SizeUnit.B));

// Order the results table by namespace, then type, then the normal within-type ordering (categories, etc.).
// Show the per-benchmark categories so the grouping is visible in the table.
_ = WithOrderer(new BDN.benchmark.NamespaceTypeOrderer());
_ = AddColumn(CategoriesColumn.Default);

var baseJob = Job.Default.WithGcServer(true);

Net8BaseJob = baseJob
Expand Down
22 changes: 12 additions & 10 deletions libs/storage/Tsavorite/cs/src/core/Allocator/LogRecord.cs
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ public readonly OverflowByteArray ValueOverflow
{
var (length, dataAddress) = DataHeader.GetValueFieldInfo(physicalAddress);
if (!DataHeader.ValueIsOverflow || length != ObjectIdMap.ObjectIdSize)
ThrowTsavoriteException("set_ValueOverflow should only be called when trnasferring into a new record with ValueIsOverflow == true and value.Length==ObjectIdSize");
ThrowTsavoriteException("set_ValueOverflow should only be called when transferring into a new record with ValueIsOverflow == true and value.Length==ObjectIdSize");
*(int*)dataAddress = objectIdMap.AllocateAndSet(value);

// Restore ValueLength to ObjectIdSize for the in-memory invariant (atomic single-write via local + SetDataHeader).
Expand Down Expand Up @@ -450,11 +450,13 @@ public readonly RecordFieldInfo GetRecordFieldInfo()
var dataHeader = DataHeader;
return new()
{
KeySize = dataHeader.KeyLength,
ValueSize = dataHeader.ValueLength,
KeySize = DataHeader.KeyIsInline ? dataHeader.GetKeyLengthRaw() : KeyOverflow.Length,
ValueSize = DataHeader.ValueIsInline ? dataHeader.GetValueLengthRaw() : (dataHeader.ValueIsObject ? ObjectIdMap.ObjectIdSize : ValueOverflow.Length),
ExtendedNamespaceSize = dataHeader.ExtendedNamespaceLength,
ValueIsObject = dataHeader.ValueIsObject,
HasETag = dataHeader.HasETag,
HasExpiration = dataHeader.HasExpiration
HasExpiration = dataHeader.HasExpiration,
RecordType = dataHeader.RecordType,
};
}

Expand Down Expand Up @@ -1527,7 +1529,7 @@ internal readonly void SetObjectLogRecordStartPositionAndLength(in ObjectLogFile
var (_, keyAddress) = dataHeader.GetKeyFieldInfo(physicalAddress);
var overflow = objectIdMap.GetOverflowByteArray(*(int*)keyAddress);
var actualKeyLength = (ulong)overflow.Length;
dataHeader.KeyLength = (int)(actualKeyLength & RecordDataHeader.kKeyLengthValueMask);
dataHeader.KeyLength = (int)(actualKeyLength & RecordDataHeader.kKeyLengthLowBitsMask);
*(int*)keyAddress = (int)(actualKeyLength >> RecordDataHeader.kKeyLengthBits);
}

Expand All @@ -1536,12 +1538,12 @@ internal readonly void SetObjectLogRecordStartPositionAndLength(in ObjectLogFile
{
var overflow = objectIdMap.GetOverflowByteArray(*(int*)valueAddress);
var actualValueLength = (ulong)overflow.Length;
dataHeader.ValueLength = (int)(actualValueLength & RecordDataHeader.kValueLengthValueMask);
dataHeader.ValueLength = (int)(actualValueLength & RecordDataHeader.kValueLengthLowBitsMask);
*(int*)valueAddress = (int)(actualValueLength >> RecordDataHeader.kValueLengthBits);
}
else if (dataHeader.ValueIsObject)
{
dataHeader.ValueLength = (int)(valueObjectLength & RecordDataHeader.kValueLengthValueMask);
dataHeader.ValueLength = (int)(valueObjectLength & RecordDataHeader.kValueLengthLowBitsMask);
*(uint*)valueAddress = (uint)(valueObjectLength >> RecordDataHeader.kValueLengthBits);
}

Expand Down Expand Up @@ -1653,7 +1655,7 @@ internal readonly ulong SetRecoveredObjectLogRecordStartPosition(ObjectLogFilePo
var overflow = objectIdMap.GetOverflowByteArray(*(int*)keyAddress);
objectLengths += (uint)overflow.Length;
var actualKeyLength = (ulong)overflow.Length;
dataHeader.KeyLength = (int)(actualKeyLength & RecordDataHeader.kKeyLengthValueMask);
dataHeader.KeyLength = (int)(actualKeyLength & RecordDataHeader.kKeyLengthLowBitsMask);
*(int*)keyAddress = (int)(actualKeyLength >> RecordDataHeader.kKeyLengthBits);
}

Expand All @@ -1662,13 +1664,13 @@ internal readonly ulong SetRecoveredObjectLogRecordStartPosition(ObjectLogFilePo
var overflow = objectIdMap.GetOverflowByteArray(*(int*)valueAddress);
objectLengths += (uint)overflow.Length;
var actualValueLength = (ulong)overflow.Length;
dataHeader.ValueLength = (int)(actualValueLength & RecordDataHeader.kValueLengthValueMask);
dataHeader.ValueLength = (int)(actualValueLength & RecordDataHeader.kValueLengthLowBitsMask);
*(int*)valueAddress = (int)(actualValueLength >> RecordDataHeader.kValueLengthBits);
}
else if (dataHeader.ValueIsObject)
{
objectLengths += valueObjectLength;
dataHeader.ValueLength = (int)(valueObjectLength & RecordDataHeader.kValueLengthValueMask);
dataHeader.ValueLength = (int)(valueObjectLength & RecordDataHeader.kValueLengthLowBitsMask);
*(uint*)valueAddress = (uint)(valueObjectLength >> RecordDataHeader.kValueLengthBits);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,14 @@ public struct RecordDataHeader
// ── KeyLength field (low kKeyLengthBits bits after FillerWords) ─────────────
const int kKeyLengthShift = 14;
internal const int kKeyLengthBits = 10;
internal const ulong kKeyLengthValueMask = (1UL << kKeyLengthBits) - 1;
const ulong kKeyLengthMask = kKeyLengthValueMask << kKeyLengthShift;
internal const ulong kKeyLengthLowBitsMask = (1UL << kKeyLengthBits) - 1; // The bit mask at the low bit positions of the shifted ulong
const ulong kKeyLengthMask = kKeyLengthLowBitsMask << kKeyLengthShift;

// ── ValueLength field (low kValueLengthBits bits after KeyLength) ───────────
const int kValueLengthShift = kKeyLengthShift + kKeyLengthBits;
internal const int kValueLengthBits = 24;
internal const ulong kValueLengthValueMask = (1UL << kValueLengthBits) - 1;
const ulong kValueLengthMask = kValueLengthValueMask << kValueLengthShift;
internal const ulong kValueLengthLowBitsMask = (1UL << kValueLengthBits) - 1; // The bit mask at the low bit positions of the shifted ulong
const ulong kValueLengthMask = kValueLengthLowBitsMask << kValueLengthShift;

// ── RecordType byte (byte 6 of word; must be byte-aligned: requires kValueLengthShift + kValueLengthBits == 48) ─
const int kRecordTypeShift = kValueLengthShift + kValueLengthBits;
Expand Down Expand Up @@ -249,19 +249,19 @@ public void InitializeForNewRecord() { }
internal int KeyLength
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
readonly get => KeyIsInline ? (int)((word >> kKeyLengthShift) & kKeyLengthValueMask) : ObjectIdMap.ObjectIdSize;
readonly get => KeyIsInline ? (int)((word >> kKeyLengthShift) & kKeyLengthLowBitsMask) : ObjectIdMap.ObjectIdSize;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set
{
Debug.Assert((uint)value <= kKeyLengthValueMask, $"KeyLength {value} exceeds {kKeyLengthBits}-bit max");
word = (word & ~kKeyLengthMask) | (((ulong)value & kKeyLengthValueMask) << kKeyLengthShift);
Debug.Assert((uint)value <= kKeyLengthLowBitsMask, $"KeyLength {value} exceeds {kKeyLengthBits}-bit max");
word = (word & ~kKeyLengthMask) | (((ulong)value & kKeyLengthLowBitsMask) << kKeyLengthShift);
}
}

/// <summary>Read the raw value stored in the KeyLength field, without the inline check. Used by disk-serialization paths
/// where the field may hold the low <see cref="kKeyLengthBits"/> bits of the on-disk overflow length (not the effective <see cref="KeyLength"/>).</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal readonly int GetKeyLengthRaw() => (int)((word >> kKeyLengthShift) & kKeyLengthValueMask);
internal readonly int GetKeyLengthRaw() => (int)((word >> kKeyLengthShift) & kKeyLengthLowBitsMask);

/// <summary>The effective ValueLength for record-length calculations.
/// <para>For inline values, returns the raw <see cref="kValueLengthBits"/>-bit value. For overflow or object values, returns <see cref="ObjectIdMap.ObjectIdSize"/>
Expand All @@ -274,19 +274,19 @@ internal int KeyLength
internal int ValueLength
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
readonly get => ValueIsInline ? (int)((word >> kValueLengthShift) & kValueLengthValueMask) : ObjectIdMap.ObjectIdSize;
readonly get => ValueIsInline ? (int)((word >> kValueLengthShift) & kValueLengthLowBitsMask) : ObjectIdMap.ObjectIdSize;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set
{
Debug.Assert((uint)value <= kValueLengthValueMask, $"ValueLength {value} exceeds {kValueLengthBits}-bit max");
word = (word & ~kValueLengthMask) | (((ulong)value & kValueLengthValueMask) << kValueLengthShift);
Debug.Assert((uint)value <= kValueLengthLowBitsMask, $"ValueLength {value} exceeds {kValueLengthBits}-bit max");
word = (word & ~kValueLengthMask) | (((ulong)value & kValueLengthLowBitsMask) << kValueLengthShift);
}
}

/// <summary>Read the raw value stored in the ValueLength field, without the inline check. Used by disk-serialization paths
/// where the field may hold the low <see cref="kValueLengthBits"/> bits of the on-disk overflow/object length (not the effective <see cref="ValueLength"/>).</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal readonly int GetValueLengthRaw() => (int)((word >> kValueLengthShift) & kValueLengthValueMask);
internal readonly int GetValueLengthRaw() => (int)((word >> kValueLengthShift) & kValueLengthLowBitsMask);

internal readonly int ExtendedNamespaceLength
{
Expand Down Expand Up @@ -498,7 +498,7 @@ private static unsafe int SplitOverflowingFiller(long recordBaseAddress, int ali
var newRDH = new RecordDataHeader
{
word = kKeyIsInlineMask | kValueIsInlineMask
| (((ulong)newValueLength & kValueLengthValueMask) << kValueLengthShift)
| (((ulong)newValueLength & kValueLengthLowBitsMask) << kValueLengthShift)
};
// If there's still leftover filler after maxing out ValueLength, set it (this may itself trigger another split).
if (newRemainingFiller > 0)
Expand Down Expand Up @@ -597,8 +597,8 @@ internal int Initialize(in RecordSizeInfo sizeInfo, out long keyAddress, out lon
// to a 16-byte advance) or this fully-formed post-Initialize state.
word = indicatorBits
| (((ulong)fillerWords & kFillerWordsValueMask) << kFillerWordsShift)
| (((ulong)keyLength & kKeyLengthValueMask) << kKeyLengthShift)
| (((ulong)valueLength & kValueLengthValueMask) << kValueLengthShift)
| (((ulong)keyLength & kKeyLengthLowBitsMask) << kKeyLengthShift)
| (((ulong)valueLength & kValueLengthLowBitsMask) << kValueLengthShift)
| ((ulong)namespaceByte << kNamespaceShift)
| ((ulong)recordType << kRecordTypeShift);

Expand Down
Loading
Loading