Skip to content

Commit

Permalink
Optimize the delay delivery routing key calculation for throughput an…
Browse files Browse the repository at this point in the history
…d fewer allocations (#1411)

* Use BitVector32 instead of BitArray to avoid array allocation and array copying for BitArray

* Use string.create instead

* Avoid fixed by using Unsafe.AsPointer

* More var usage

* Align constant naming because I can't unsee it

* Going back to make sure things are not getting moved

* Comments

* Tweaks

---------

Co-authored-by: danielmarbach <[email protected]>
Co-authored-by: Brandon Ording <[email protected]>
  • Loading branch information
3 people authored Jul 3, 2024
1 parent 68cd2bf commit 7094329
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<Nullable>enable</Nullable>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>..\NServiceBus.snk</AssemblyOriginatorKeyFile>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
namespace NServiceBus.Transport.RabbitMQ
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Collections.Specialized;
using System.Runtime.CompilerServices;
using global::RabbitMQ.Client;

static class DelayInfrastructure
{
const int maxNumberOfBitsToUse = 28;
const int MaxNumberOfBitsToUse = 28;

public const int MaxLevel = maxNumberOfBitsToUse - 1;
public const int MaxDelayInSeconds = (1 << maxNumberOfBitsToUse) - 1;
public const int MaxLevel = MaxNumberOfBitsToUse - 1;
public const int MaxDelayInSeconds = (1 << MaxNumberOfBitsToUse) - 1;
public const string DelayHeader = "NServiceBus.Transport.RabbitMQ.DelayInSeconds";
public const string XDeathHeader = "x-death";
public const string XFirstDeathExchangeHeader = "x-first-death-exchange";
Expand Down Expand Up @@ -78,30 +78,53 @@ public static void TearDown(IModel channel)
}
}

public static string CalculateRoutingKey(int delayInSeconds, string address, out int startingDelayLevel)
public static unsafe string CalculateRoutingKey(int delayInSeconds, string address, out int startingDelayLevel)
{
if (delayInSeconds < 0)
{
delayInSeconds = 0;
}

var bitArray = new BitArray(new[] { delayInSeconds });
var sb = new StringBuilder();
startingDelayLevel = 0;

for (var level = MaxLevel; level >= 0; level--)
// Pinning the address of the startingDelayLevel so that we can safely write back to the address of the local.
// This trickery is done because when string.Create returns, there is no way to pass state outside of the lambda
// without doing a closure over a local variable, which would create DisplayClass allocations for every call.
fixed (int* pinnedStartingDelayLevel = &startingDelayLevel)
{
if (startingDelayLevel == 0 && bitArray[level])
var startingDelayLevelPtr = (nint)pinnedStartingDelayLevel;

// The length of the string is determined by the max level, taking into account the number of dots, the address length
// and additional space since we are zero based.
return string.Create((2 * MaxLevel) + 2 + address.Length, (delayInSeconds, address, startingDelayLevelPtr), Action);

static void Action(Span<char> span, (int, string, nint) state)
{
startingDelayLevel = level;
}
var (delayInSeconds, address, startingDelayLevelPtr) = state;

sb.Append(bitArray[level] ? "1." : "0.");
}
var startingDelayLevel = 0;
var mask = BitVector32.CreateMask();

sb.Append(address);
var bitVector = new BitVector32(delayInSeconds);

return sb.ToString();
var index = 0;
for (var level = MaxLevel; level >= 0; level--)
{
var flag = bitVector[mask << level];
if (startingDelayLevel == 0 && flag)
{
startingDelayLevel = level;
}

span[index++] = flag ? '1' : '0';
span[index++] = '.';
}

address.AsSpan().CopyTo(span[index..]);

// Write back the startingDelayLevel to the address of the local
Unsafe.Write(startingDelayLevelPtr.ToPointer(), startingDelayLevel);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<TargetFramework>net8.0</TargetFramework>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>..\NServiceBus.snk</AssemblyOriginatorKeyFile>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup>
Expand Down

0 comments on commit 7094329

Please sign in to comment.