-
Notifications
You must be signed in to change notification settings - Fork 55
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add NuidWriter * dotnet format * Manually fix style analyzer warnings * Fix tests * Skip long running tests * Fix CI build * Remove unnecessary compare
- Loading branch information
Showing
9 changed files
with
593 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
using System.Runtime.CompilerServices; | ||
using BenchmarkDotNet.Attributes; | ||
using BenchmarkDotNet.Jobs; | ||
using NATS.Client.Core; | ||
using NATS.Client.Core.Internal; | ||
|
||
namespace MicroBenchmark; | ||
|
||
[MemoryDiagnoser] | ||
[SimpleJob(RuntimeMoniker.Net60)] | ||
[SimpleJob(RuntimeMoniker.Net70, baseline: true)] | ||
[SimpleJob(RuntimeMoniker.Net80)] | ||
[SimpleJob(RuntimeMoniker.NativeAot80)] | ||
public class NewInboxBenchmarks | ||
{ | ||
private static readonly NatsOpts LongPrefixOpt = NatsOpts.Default | ||
with | ||
{ | ||
InboxPrefix = "this-is-a-rather-long-prefix-that-we-use-here", | ||
}; | ||
|
||
private static readonly NatsConnection ConnectionDefaultPrefix = new(); | ||
private static readonly NatsConnection ConnectionLongPrefix = new(LongPrefixOpt); | ||
|
||
private char[] _buf = new char[32]; | ||
|
||
[GlobalSetup] | ||
public void Setup() | ||
{ | ||
NuidWriter.TryWriteNuid(new char[100]); | ||
} | ||
|
||
[Benchmark(Baseline = true)] | ||
[SkipLocalsInit] | ||
public bool TryWriteNuid() | ||
{ | ||
return NuidWriter.TryWriteNuid(_buf); | ||
} | ||
|
||
[Benchmark] | ||
public string NewInbox_ShortPrefix() | ||
{ | ||
return ConnectionDefaultPrefix.NewInbox(); | ||
} | ||
|
||
[Benchmark] | ||
public string NewInbox_LongPrefix() | ||
{ | ||
return ConnectionLongPrefix.NewInbox(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
using System.Diagnostics.CodeAnalysis; | ||
using System.Runtime.CompilerServices; | ||
using System.Runtime.InteropServices; | ||
using System.Security.Cryptography; | ||
|
||
namespace NATS.Client.Core.Internal; | ||
|
||
[SkipLocalsInit] | ||
internal sealed class NuidWriter | ||
{ | ||
internal const nuint NuidLength = PrefixLength + SequentialLength; | ||
private const nuint Base = 62; | ||
private const ulong MaxSequential = 839299365868340224; // 62^10 | ||
private const uint PrefixLength = 12; | ||
private const nuint SequentialLength = 10; | ||
private const int MinIncrement = 33; | ||
private const int MaxIncrement = 333; | ||
|
||
[ThreadStatic] | ||
private static NuidWriter? _writer; | ||
|
||
private char[] _prefix; | ||
private ulong _increment; | ||
private ulong _sequential; | ||
|
||
private NuidWriter() | ||
{ | ||
Refresh(out _); | ||
} | ||
|
||
private static ReadOnlySpan<char> Digits => "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; | ||
|
||
public static bool TryWriteNuid(Span<char> nuidBuffer) | ||
{ | ||
if (_writer is not null) | ||
{ | ||
return _writer.TryWriteNuidCore(nuidBuffer); | ||
} | ||
|
||
return InitAndWrite(nuidBuffer); | ||
} | ||
|
||
private static bool TryWriteNuidCore(Span<char> buffer, Span<char> prefix, ulong sequential) | ||
{ | ||
if ((uint)buffer.Length < NuidLength || prefix.Length != PrefixLength) | ||
{ | ||
return false; | ||
} | ||
|
||
Unsafe.CopyBlockUnaligned(ref Unsafe.As<char, byte>(ref buffer[0]), ref Unsafe.As<char, byte>(ref prefix[0]), PrefixLength * sizeof(char)); | ||
|
||
// NOTE: We must never write to digitsPtr! | ||
ref var digitsPtr = ref MemoryMarshal.GetReference(Digits); | ||
|
||
for (nuint i = PrefixLength; i < NuidLength; i++) | ||
{ | ||
var digitIndex = (nuint)(sequential % Base); | ||
Unsafe.Add(ref buffer[0], i) = Unsafe.Add(ref digitsPtr, digitIndex); | ||
sequential /= Base; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
private static uint GetIncrement() | ||
{ | ||
return (uint)Random.Shared.Next(MinIncrement, MaxIncrement + 1); | ||
} | ||
|
||
private static ulong GetSequential() | ||
{ | ||
return (ulong)Random.Shared.NextInt64(0, (long)MaxSequential + 1); | ||
} | ||
|
||
private static char[] GetPrefix(RandomNumberGenerator? rng = null) | ||
{ | ||
Span<byte> randomBytes = stackalloc byte[(int)PrefixLength]; | ||
|
||
// TODO: For .NET 8+, use GetItems for better distribution | ||
if (rng == null) | ||
{ | ||
RandomNumberGenerator.Fill(randomBytes); | ||
} | ||
else | ||
{ | ||
rng.GetBytes(randomBytes); | ||
} | ||
|
||
var newPrefix = new char[PrefixLength]; | ||
|
||
for (var i = 0; i < randomBytes.Length; i++) | ||
{ | ||
var digitIndex = (int)(randomBytes[i] % Base); | ||
newPrefix[i] = Digits[digitIndex]; | ||
} | ||
|
||
return newPrefix; | ||
} | ||
|
||
[MethodImpl(MethodImplOptions.NoInlining)] | ||
private static bool InitAndWrite(Span<char> span) | ||
{ | ||
_writer = new NuidWriter(); | ||
return _writer.TryWriteNuidCore(span); | ||
} | ||
|
||
private bool TryWriteNuidCore(Span<char> nuidBuffer) | ||
{ | ||
var sequential = _sequential += _increment; | ||
|
||
if (sequential < MaxSequential) | ||
{ | ||
return TryWriteNuidCore(nuidBuffer, _prefix, sequential); | ||
} | ||
|
||
return RefreshAndWrite(nuidBuffer); | ||
|
||
[MethodImpl(MethodImplOptions.NoInlining)] | ||
bool RefreshAndWrite(Span<char> buffer) | ||
{ | ||
var prefix = Refresh(out sequential); | ||
return TryWriteNuidCore(buffer, prefix, sequential); | ||
} | ||
} | ||
|
||
[MethodImpl(MethodImplOptions.NoInlining)] | ||
[MemberNotNull(nameof(_prefix))] | ||
private char[] Refresh(out ulong sequential) | ||
{ | ||
var prefix = _prefix = GetPrefix(); | ||
_increment = GetIncrement(); | ||
sequential = _sequential = GetSequential(); | ||
return prefix; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.