diff --git a/projects/Benchmarks/WireFormatting/PrimitivesSerialization.cs b/projects/Benchmarks/WireFormatting/PrimitivesSerialization.cs index ce6f264689..bddf3f9f46 100644 --- a/projects/Benchmarks/WireFormatting/PrimitivesSerialization.cs +++ b/projects/Benchmarks/WireFormatting/PrimitivesSerialization.cs @@ -11,7 +11,7 @@ namespace RabbitMQ.Benchmarks [BenchmarkCategory("Primitives")] public class PrimitivesBase { - protected Memory _buffer = new byte[16]; + protected readonly Memory _buffer = new byte[16]; [GlobalSetup] public virtual void Setup() { } @@ -22,10 +22,18 @@ public class PrimitivesBool : PrimitivesBase public override void Setup() => WireFormatting.WriteBits(ref _buffer.Span.GetStart(), true, false, true, false, true); [Benchmark] - public int BoolRead2() => WireFormatting.ReadBits(_buffer.Span, out bool _, out bool _); + public (bool, bool) BoolRead2() + { + WireFormatting.ReadBits(_buffer.Span, out bool v1, out bool v2); + return (v1, v2); + } [Benchmark] - public int BoolRead5() => WireFormatting.ReadBits(_buffer.Span, out bool _, out bool _, out bool _, out bool _, out bool _); + public (bool, bool, bool, bool, bool) BoolRead5() + { + WireFormatting.ReadBits(_buffer.Span, out bool v1, out bool v2, out bool v3, out bool v4, out bool v5); + return (v1, v2, v3, v4, v5); + } [Benchmark] [Arguments(true, false)] @@ -52,7 +60,11 @@ public class PrimitivesLong : PrimitivesBase public override void Setup() => WireFormatting.WriteLong(ref _buffer.Span.GetStart(), 12345u); [Benchmark] - public int LongRead() => WireFormatting.ReadLong(_buffer.Span, out _); + public uint LongRead() + { + WireFormatting.ReadLong(_buffer.Span, out uint v1); + return v1; + } [Benchmark] [Arguments(12345u)] @@ -64,7 +76,11 @@ public class PrimitivesLonglong : PrimitivesBase public override void Setup() => WireFormatting.WriteLonglong(ref _buffer.Span.GetStart(), 12345ul); [Benchmark] - public int LonglongRead() => WireFormatting.ReadLonglong(_buffer.Span, out _); + public ulong LonglongRead() + { + WireFormatting.ReadLonglong(_buffer.Span, out ulong v1); + return v1; + } [Benchmark] [Arguments(12345ul)] @@ -76,7 +92,11 @@ public class PrimitivesShort : PrimitivesBase public override void Setup() => WireFormatting.WriteShort(ref _buffer.Span.GetStart(), 12345); [Benchmark] - public int ShortRead() => WireFormatting.ReadShort(_buffer.Span, out _); + public ushort ShortRead() + { + WireFormatting.ReadShort(_buffer.Span, out ushort v1); + return v1; + } [Benchmark] [Arguments(12345)] @@ -85,12 +105,16 @@ public class PrimitivesShort : PrimitivesBase public class PrimitivesTimestamp : PrimitivesBase { - AmqpTimestamp _timestamp = new AmqpTimestamp(DateTimeOffset.UtcNow.ToUnixTimeSeconds()); + private AmqpTimestamp _timestamp = new AmqpTimestamp(DateTimeOffset.UtcNow.ToUnixTimeSeconds()); public override void Setup() => WireFormatting.WriteTimestamp(ref _buffer.Span.GetStart(), _timestamp); [Benchmark] - public int TimestampRead() => WireFormatting.ReadTimestamp(_buffer.Span, out _); + public AmqpTimestamp TimestampRead() + { + WireFormatting.ReadTimestamp(_buffer.Span, out AmqpTimestamp v1); + return v1; + } [Benchmark] public int TimestampWrite() => WireFormatting.WriteTimestamp(ref _buffer.Span.GetStart(), _timestamp); diff --git a/projects/RabbitMQ.Client/client/framing/BasicProperties.cs b/projects/RabbitMQ.Client/client/framing/BasicProperties.cs index d40e6af455..4bc9704e76 100644 --- a/projects/RabbitMQ.Client/client/framing/BasicProperties.cs +++ b/projects/RabbitMQ.Client/client/framing/BasicProperties.cs @@ -200,60 +200,142 @@ public BasicProperties() public BasicProperties(ReadOnlySpan span) { - int offset = WireFormatting.ReadBits(span, - out bool contentType_present, - out bool contentEncoding_present, - out bool headers_present, - out bool deliveryMode_present, - out bool priority_present, - out bool correlationId_present, - out bool replyTo_present, - out bool expiration_present, - out bool messageId_present, - out bool timestamp_present, - out bool type_present, - out bool userId_present, - out bool appId_present, - out bool clusterId_present); - if (contentType_present) { offset += WireFormatting.ReadShortstr(span.Slice(offset), out _contentType); } - if (contentEncoding_present) { offset += WireFormatting.ReadShortstr(span.Slice(offset), out _contentEncoding); } - if (headers_present) { offset += WireFormatting.ReadDictionary(span.Slice(offset), out var tmpDirectory); _headers = tmpDirectory; } - if (deliveryMode_present) { _deliveryMode = span[offset++]; } - if (priority_present) { _priority = span[offset++]; } - if (correlationId_present) { offset += WireFormatting.ReadShortstr(span.Slice(offset), out _correlationId); } - if (replyTo_present) { offset += WireFormatting.ReadShortstr(span.Slice(offset), out _replyTo); } - if (expiration_present) { offset += WireFormatting.ReadShortstr(span.Slice(offset), out _expiration); } - if (messageId_present) { offset += WireFormatting.ReadShortstr(span.Slice(offset), out _messageId); } - if (timestamp_present) { offset += WireFormatting.ReadTimestamp(span.Slice(offset), out _timestamp); } - if (type_present) { offset += WireFormatting.ReadShortstr(span.Slice(offset), out _type); } - if (userId_present) { offset += WireFormatting.ReadShortstr(span.Slice(offset), out _userId); } - if (appId_present) { offset += WireFormatting.ReadShortstr(span.Slice(offset), out _appId); } - if (clusterId_present) { WireFormatting.ReadShortstr(span.Slice(offset), out _clusterId); } + int offset = 2; + ref readonly byte bits = ref span[0]; + if (bits.IsBitSet(ContentTypeBit)) { offset += WireFormatting.ReadShortstr(span.Slice(offset), out _contentType); } + if (bits.IsBitSet(ContentEncodingBit)) { offset += WireFormatting.ReadShortstr(span.Slice(offset), out _contentEncoding); } + if (bits.IsBitSet(HeaderBit)) { offset += WireFormatting.ReadDictionary(span.Slice(offset), out var tmpDirectory); _headers = tmpDirectory; } + if (bits.IsBitSet(DeliveryModeBit)) { _deliveryMode = span[offset++]; } + if (bits.IsBitSet(PriorityBit)) { _priority = span[offset++]; } + if (bits.IsBitSet(CorrelationIdBit)) { offset += WireFormatting.ReadShortstr(span.Slice(offset), out _correlationId); } + if (bits.IsBitSet(ReplyToBit)) { offset += WireFormatting.ReadShortstr(span.Slice(offset), out _replyTo); } + if (bits.IsBitSet(ExpirationBit)) { offset += WireFormatting.ReadShortstr(span.Slice(offset), out _expiration); } + + bits = ref span[1]; + if (bits.IsBitSet(MessageIdBit)) { offset += WireFormatting.ReadShortstr(span.Slice(offset), out _messageId); } + if (bits.IsBitSet(TimestampBit)) { offset += WireFormatting.ReadTimestamp(span.Slice(offset), out _timestamp); } + if (bits.IsBitSet(TypeBit)) { offset += WireFormatting.ReadShortstr(span.Slice(offset), out _type); } + if (bits.IsBitSet(UserIdBit)) { offset += WireFormatting.ReadShortstr(span.Slice(offset), out _userId); } + if (bits.IsBitSet(AppIdBit)) { offset += WireFormatting.ReadShortstr(span.Slice(offset), out _appId); } + if (bits.IsBitSet(ClusterIdBit)) { WireFormatting.ReadShortstr(span.Slice(offset), out _clusterId); } } public override ushort ProtocolClassId => 60; public override string ProtocolClassName => "basic"; + //---------------------------------- + // First byte + //---------------------------------- + private const byte ContentTypeBit = 7; + private const byte ContentEncodingBit = 6; + private const byte HeaderBit = 5; + private const byte DeliveryModeBit = 4; + private const byte PriorityBit = 3; + private const byte CorrelationIdBit = 2; + private const byte ReplyToBit = 1; + private const byte ExpirationBit = 0; + + //---------------------------------- + // Second byte + //---------------------------------- + private const byte MessageIdBit = 7; + private const byte TimestampBit = 6; + private const byte TypeBit = 5; + private const byte UserIdBit = 4; + private const byte AppIdBit = 3; + private const byte ClusterIdBit = 2; + internal override int WritePropertiesTo(Span span) { - int offset = WireFormatting.WriteBits(ref span.GetStart(), - IsContentTypePresent(), IsContentEncodingPresent(), IsHeadersPresent(), IsDeliveryModePresent(), IsPriorityPresent(), - IsCorrelationIdPresent(), IsReplyToPresent(), IsExpirationPresent(), IsMessageIdPresent(), IsTimestampPresent(), - IsTypePresent(), IsUserIdPresent(), IsAppIdPresent(), IsClusterIdPresent()); - if (IsContentTypePresent()) { offset += WireFormatting.WriteShortstr(ref span.GetOffset(offset), _contentType); } - if (IsContentEncodingPresent()) { offset += WireFormatting.WriteShortstr(ref span.GetOffset(offset), _contentEncoding); } - if (IsHeadersPresent()) { offset += WireFormatting.WriteTable(ref span.GetOffset(offset), _headers); } - if (IsDeliveryModePresent()) { span[offset++] = _deliveryMode; } - if (IsPriorityPresent()) { span[offset++] = _priority; } - if (IsCorrelationIdPresent()) { offset += WireFormatting.WriteShortstr(ref span.GetOffset(offset), _correlationId); } - if (IsReplyToPresent()) { offset += WireFormatting.WriteShortstr(ref span.GetOffset(offset), _replyTo); } - if (IsExpirationPresent()) { offset += WireFormatting.WriteShortstr(ref span.GetOffset(offset), _expiration); } - if (IsMessageIdPresent()) { offset += WireFormatting.WriteShortstr(ref span.GetOffset(offset), _messageId); } - if (IsTimestampPresent()) { offset += WireFormatting.WriteTimestamp(ref span.GetOffset(offset), _timestamp); } - if (IsTypePresent()) { offset += WireFormatting.WriteShortstr(ref span.GetOffset(offset), _type); } - if (IsUserIdPresent()) { offset += WireFormatting.WriteShortstr(ref span.GetOffset(offset), _userId); } - if (IsAppIdPresent()) { offset += WireFormatting.WriteShortstr(ref span.GetOffset(offset), _appId); } - if (IsClusterIdPresent()) { offset += WireFormatting.WriteShortstr(ref span.GetOffset(offset), _clusterId); } + int offset = 2; + ref byte bitValue = ref span.GetStart(); + bitValue = 0; + if (IsContentTypePresent()) + { + bitValue.SetBit(ContentTypeBit); + offset += WireFormatting.WriteShortstr(ref span.GetOffset(offset), _contentType); + } + + if (IsContentEncodingPresent()) + { + bitValue.SetBit(ContentEncodingBit); + offset += WireFormatting.WriteShortstr(ref span.GetOffset(offset), _contentEncoding); + } + + if (IsHeadersPresent()) + { + bitValue.SetBit(HeaderBit); + offset += WireFormatting.WriteTable(ref span.GetOffset(offset), _headers); + } + + if (IsDeliveryModePresent()) + { + bitValue.SetBit(DeliveryModeBit); + span.GetOffset(offset++) = _deliveryMode; + } + + if (IsPriorityPresent()) + { + bitValue.SetBit(PriorityBit); + span.GetOffset(offset++) = _priority; + } + + if (IsCorrelationIdPresent()) + { + bitValue.SetBit(CorrelationIdBit); + offset += WireFormatting.WriteShortstr(ref span.GetOffset(offset), _correlationId); + } + + if (IsReplyToPresent()) + { + bitValue.SetBit(ReplyToBit); + offset += WireFormatting.WriteShortstr(ref span.GetOffset(offset), _replyTo); + } + + if (IsExpirationPresent()) + { + bitValue.SetBit(ExpirationBit); + offset += WireFormatting.WriteShortstr(ref span.GetOffset(offset), _expiration); + } + + bitValue = ref span.GetOffset(1); + bitValue = 0; + if (IsMessageIdPresent()) + { + bitValue.SetBit(MessageIdBit); + offset += WireFormatting.WriteShortstr(ref span.GetOffset(offset), _messageId); + } + + if (IsTimestampPresent()) + { + bitValue.SetBit(TimestampBit); + offset += WireFormatting.WriteTimestamp(ref span.GetOffset(offset), _timestamp); + } + + if (IsTypePresent()) + { + bitValue.SetBit(TypeBit); + offset += WireFormatting.WriteShortstr(ref span.GetOffset(offset), _type); + } + + if (IsUserIdPresent()) + { + bitValue.SetBit(UserIdBit); + offset += WireFormatting.WriteShortstr(ref span.GetOffset(offset), _userId); + } + + if (IsAppIdPresent()) + { + bitValue.SetBit(AppIdBit); + offset += WireFormatting.WriteShortstr(ref span.GetOffset(offset), _appId); + } + + if (IsClusterIdPresent()) + { + bitValue.SetBit(ClusterIdBit); + offset += WireFormatting.WriteShortstr(ref span.GetOffset(offset), _clusterId); + } + return offset; } diff --git a/projects/RabbitMQ.Client/client/impl/WireFormatting.Read.cs b/projects/RabbitMQ.Client/client/impl/WireFormatting.Read.cs index 610459c866..085c052cd0 100644 --- a/projects/RabbitMQ.Client/client/impl/WireFormatting.Read.cs +++ b/projects/RabbitMQ.Client/client/impl/WireFormatting.Read.cs @@ -238,31 +238,6 @@ public static int ReadBits(ReadOnlySpan span, out bool val1, out bool val2 return 1; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int ReadBits(ReadOnlySpan span, - out bool val1, out bool val2, out bool val3, out bool val4, out bool val5, - out bool val6, out bool val7, out bool val8, out bool val9, out bool val10, - out bool val11, out bool val12, out bool val13, out bool val14) - { - byte bits = span[0]; - val1 = (bits & 0b1000_0000) != 0; - val2 = (bits & 0b0100_0000) != 0; - val3 = (bits & 0b0010_0000) != 0; - val4 = (bits & 0b0001_0000) != 0; - val5 = (bits & 0b0000_1000) != 0; - val6 = (bits & 0b0000_0100) != 0; - val7 = (bits & 0b0000_0010) != 0; - val8 = (bits & 0b0000_0001) != 0; - bits = span[1]; - val9 = (bits & 0b1000_0000) != 0; - val10 = (bits & 0b0100_0000) != 0; - val11 = (bits & 0b0010_0000) != 0; - val12 = (bits & 0b0001_0000) != 0; - val13 = (bits & 0b0000_1000) != 0; - val14 = (bits & 0b0000_0100) != 0; - return 2; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int ReadShort(ReadOnlySpan span, out ushort value) { @@ -328,7 +303,7 @@ public static int ThrowSyntaxErrorException(uint byteCount) private static int ThrowInvalidTableValue(char type) => throw new SyntaxErrorException($"Unrecognised type in table: {type}"); - private static decimal ThrowInvalidDecimalScale(int scale) - => throw new SyntaxErrorException($"Unrepresentable AMQP decimal table field: scale={scale}"); + private static void ThrowInvalidDecimalScale(int scale) + => throw new SyntaxErrorException($"Unrepresentable AMQP decimal table field: scale={scale}"); } } diff --git a/projects/RabbitMQ.Client/client/impl/WireFormatting.Write.cs b/projects/RabbitMQ.Client/client/impl/WireFormatting.Write.cs index 672b04490f..710564b421 100644 --- a/projects/RabbitMQ.Client/client/impl/WireFormatting.Write.cs +++ b/projects/RabbitMQ.Client/client/impl/WireFormatting.Write.cs @@ -302,23 +302,6 @@ public static int WriteBits(ref byte destination, bool val1, bool val2, bool val return 1; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int WriteBits(ref byte destination, - bool val1, bool val2, bool val3, bool val4, bool val5, - bool val6, bool val7, bool val8, bool val9, bool val10, - bool val11, bool val12, bool val13, bool val14) - { - int a = val8.ToByte() + val7.ToByte() * 2 + val6.ToByte() * 4 + val5.ToByte() * 8; - int b = val4.ToByte() + val3.ToByte() * 2 + val2.ToByte() * 4 + val1.ToByte() * 8; - destination = (byte)(a | (b << 4)); - - a = val14.ToByte() * 4 + val13.ToByte() * 8; - b = val12.ToByte() + val11.ToByte() * 2 + val10.ToByte() * 4 + val9.ToByte() * 8; - - destination.GetOffset(1) = (byte)(a | (b << 4)); - return 2; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int WriteLongstr(ref byte destination, ReadOnlySpan val) { @@ -500,9 +483,6 @@ public static int ThrowArgumentTooLong(int length) public static int ThrowArgumentOutOfRangeException(int orig, int expected) => throw new ArgumentOutOfRangeException("span", $"Span has not enough space ({orig} instead of {expected})"); - public static int ThrowArgumentOutOfRangeException(string val, int maxLength) - => throw new ArgumentOutOfRangeException(nameof(val), val, $"Value exceeds the maximum allowed length of {maxLength} bytes."); - private static int ThrowWireFormattingException(decimal value) => throw new WireFormattingException("Decimal overflow in AMQP encoding", value); diff --git a/projects/RabbitMQ.Client/util/TypeExtensions.cs b/projects/RabbitMQ.Client/util/TypeExtensions.cs index 0140321b84..906bca1293 100644 --- a/projects/RabbitMQ.Client/util/TypeExtensions.cs +++ b/projects/RabbitMQ.Client/util/TypeExtensions.cs @@ -42,6 +42,18 @@ public static ref byte GetOffset(this ref byte source, int offset) return ref Unsafe.Add(ref source, offset); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsBitSet(this in byte value, byte bitPosition) + { + return (value & (1 << bitPosition)) != 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SetBit(this ref byte value, byte bitPosition) + { + value |= (byte)(1 << bitPosition); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static byte ToByte(this ref bool source) {