Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rlp.Encode skip second array creation for length #8440

Merged
merged 2 commits into from
Mar 31, 2025
Merged
Changes from 1 commit
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
133 changes: 62 additions & 71 deletions src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ public static int Encode(Span<byte> buffer, int position, ReadOnlySpan<byte> inp
int lengthOfLength = LengthOfLength(input.Length);
byte prefix = (byte)(183 + lengthOfLength);
buffer[position++] = prefix;
SerializeLength(buffer, position, input.Length);
SerializeLength(input.Length, buffer[position..]);
}

input.CopyTo(buffer.Slice(position, input.Length));
Expand Down Expand Up @@ -437,34 +437,47 @@ static void ThrowArgumentOutOfRangeException()
}
}

[SkipLocalsInit]
public static Rlp Encode(ReadOnlySpan<byte> input)
{
if (input.Length == 0)
// Handle special cases first
int length = input.Length;
if (length == 0)
{
return OfEmptyByteArray;
}

if (input.Length == 1 && input[0] < 128)
// If it's a single byte less than 128, it encodes to itself.
if (length == 1 && input[0] < 128)
{
return new Rlp(input[0]);
}

if (input.Length < 56)
// For lengths < 56, the encoding is one byte of prefix + the data
if (length < 56)
{
byte smallPrefix = (byte)(input.Length + 128);
byte[] rlpResult = new byte[input.Length + 1];
rlpResult[0] = smallPrefix;
// Allocate exactly what we need: 1 prefix byte + input length
byte[] rlpResult = GC.AllocateUninitializedArray<byte>(1 + length);
// First byte is 0x80 + length
rlpResult[0] = (byte)(0x80 + length);
// Copy input after the prefix
input.CopyTo(rlpResult.AsSpan(1));
return new Rlp(rlpResult);
}
else
{
byte[] serializedLength = SerializeLength(input.Length);
byte prefix = (byte)(183 + serializedLength.Length);
byte[] rlpResult = new byte[1 + serializedLength.Length + input.Length];
rlpResult[0] = prefix;
serializedLength.CopyTo(rlpResult.AsSpan(1));
input.CopyTo(rlpResult.AsSpan(1 + serializedLength.Length));
// For length >= 56, we need to encode the length itself (Big-Endian) and prepend a prefix
Span<byte> lengthBuffer = stackalloc byte[4]; // Enough for 32-bit lengths
int lengthOfLength = SerializeLength(length, lengthBuffer);
// Total size = 1 prefix byte + lengthOfLength + data length
int totalSize = 1 + lengthOfLength + length;
byte[] rlpResult = GC.AllocateUninitializedArray<byte>(totalSize);
// Prefix: 0xb7 (183) + number of bytes in length
rlpResult[0] = (byte)(0xb7 + lengthOfLength);
// Then copy length bytes
lengthBuffer[..lengthOfLength].CopyTo(rlpResult.AsSpan(1));
// Finally copy the actual input
input.CopyTo(rlpResult.AsSpan(1 + lengthOfLength));
return new Rlp(rlpResult);
}
}
Expand All @@ -474,30 +487,32 @@ public static Rlp Encode(byte[]? input)
return input is null ? OfEmptyByteArray : Encode(input.AsSpan());
}

public static int SerializeLength(Span<byte> buffer, int position, int value)
private static int SerializeLength(int value, Span<byte> destination)
{
if (value < 1 << 8)
// We assume 0 <= value <= int.MaxValue
if (value < (1 << 8))
{
buffer[position] = (byte)value;
return position + 1;
destination[0] = (byte)value;
return 1;
}

if (value < 1 << 16)
if (value < (1 << 16))
{
BinaryPrimitives.WriteUInt16BigEndian(buffer[position..], (ushort)value);
return position + 2;
BinaryPrimitives.WriteUInt16BigEndian(destination, (ushort)value);
return 2;
}

if (value < 1 << 24)
if (value < (1 << 24))
{
buffer[position] = (byte)(value >> 16);
buffer[position + 1] = ((byte)(value >> 8));
buffer[position + 2] = ((byte)value);
return position + 3;
destination[2] = (byte)value;
destination[1] = (byte)(value >> 8);
destination[0] = (byte)(value >> 16);
return 3;
}

BinaryPrimitives.WriteInt32BigEndian(buffer[position..], value);
return position + 4;
// Otherwise, 4 bytes
BinaryPrimitives.WriteInt32BigEndian(destination, value);
return 4;
}

public static int LengthOfLength(int value)
Expand All @@ -506,40 +521,6 @@ public static int LengthOfLength(int value)
return (bits + 7) / 8;
}

public static byte[] SerializeLength(int value)
{
if (value < 1 << 8)
{
return new[] { (byte)value };
}

if (value < 1 << 16)
{
return new[]
{
(byte) (value >> 8),
(byte) value,
};
}

if (value < 1 << 24)
{
return new[]
{
(byte) (value >> 16),
(byte) (value >> 8),
(byte) value,
};
}

return new[]
{
(byte) (value >> 24),
(byte) (value >> 16),
(byte) (value >> 8),
(byte) value
};
}

public static Rlp Encode(Hash256? keccak)
{
Expand Down Expand Up @@ -575,14 +556,15 @@ public static int StartSequence(Span<byte> buffer, int position, int sequenceLen
}
else
{
afterLength = SerializeLength(buffer, beforeLength, sequenceLength);
afterLength = beforeLength + SerializeLength(sequenceLength, buffer[beforeLength..]);
prefix = (byte)(247 + afterLength - beforeLength);
}

buffer[position] = prefix;
return afterLength;
}

[SkipLocalsInit]
public static Rlp Encode(params Rlp[] sequence)
{
int contentLength = 0;
Expand All @@ -591,34 +573,43 @@ public static Rlp Encode(params Rlp[] sequence)
contentLength += sequence[i].Length;
}

byte[] serializedLength = null;
Span<byte> lengthBuffer = stackalloc byte[4];
int lengthOfLength = 0;
byte prefix;

if (contentLength < 56)
{
prefix = (byte)(192 + contentLength);
// Single-byte prefix: 0xc0 + length
prefix = (byte)(0xc0 + contentLength);
}
else
{
serializedLength = SerializeLength(contentLength);
prefix = (byte)(247 + serializedLength.Length);
// Multi-byte prefix: 0xf7 + lengthOfLength
lengthOfLength = SerializeLength(contentLength, lengthBuffer);
prefix = (byte)(0xf7 + lengthOfLength);
}

int lengthOfPrefixAndSerializedLength = 1 + (serializedLength?.Length ?? 0);
byte[] allBytes = new byte[lengthOfPrefixAndSerializedLength + contentLength];
// Allocate the final buffer exactly once (prefix + optional length + content).
int totalSize = 1 + lengthOfLength + contentLength;
byte[] allBytes = GC.AllocateUninitializedArray<byte>(totalSize);

// Write prefix and length.
allBytes[0] = prefix;
int offset = 1;
if (serializedLength is not null)
if (lengthOfLength > 0)
{
Buffer.BlockCopy(serializedLength, 0, allBytes, offset, serializedLength.Length);
offset += serializedLength.Length;
lengthBuffer[..lengthOfLength].CopyTo(allBytes.AsSpan(offset));
offset += lengthOfLength;
}

// Copy the content from all Rlp items.
for (int i = 0; i < sequence.Length; i++)
{
Buffer.BlockCopy(sequence[i].Bytes, 0, allBytes, offset, sequence[i].Length);
offset += sequence[i].Length;
}

// Wrap in Rlp and return.
return new Rlp(allBytes);
}

Expand Down
Loading