Skip to content

Commit 0f515b6

Browse files
author
Stefán J. Sigurðarson
committed
Speeding up decimal (de)serialization.
1 parent 34aac15 commit 0f515b6

File tree

1 file changed

+62
-37
lines changed

1 file changed

+62
-37
lines changed

projects/RabbitMQ.Client/client/impl/WireFormatting.cs

Lines changed: 62 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -34,32 +34,56 @@
3434
using System.Collections.Generic;
3535
using System.Runtime.CompilerServices;
3636
using System.Text;
37+
3738
using RabbitMQ.Client.Exceptions;
3839
using RabbitMQ.Util;
3940

4041
namespace RabbitMQ.Client.Impl
4142
{
43+
// * DESCRIPTION TAKEN FROM MS REFERENCE SOURCE *
44+
// https://github.com/microsoft/referencesource/blob/master/mscorlib/system/decimal.cs
45+
// The lo, mid, hi, and flags fields contain the representation of the
46+
// Decimal value. The lo, mid, and hi fields contain the 96-bit integer
47+
// part of the Decimal. Bits 0-15 (the lower word) of the flags field are
48+
// unused and must be zero; bits 16-23 contain must contain a value between
49+
// 0 and 28, indicating the power of 10 to divide the 96-bit integer part
50+
// by to produce the Decimal value; bits 24-30 are unused and must be zero;
51+
// and finally bit 31 indicates the sign of the Decimal value, 0 meaning
52+
// positive and 1 meaning negative.
53+
readonly struct DecimalData
54+
{
55+
public readonly uint Flags;
56+
public readonly uint Hi;
57+
public readonly uint Lo;
58+
public readonly uint Mid;
59+
60+
internal DecimalData(uint flags, uint hi, uint lo, uint mid)
61+
{
62+
Flags = flags;
63+
Hi = hi;
64+
Lo = lo;
65+
Mid = mid;
66+
}
67+
}
68+
4269
internal static class WireFormatting
4370
{
71+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
4472
public static decimal ReadDecimal(ReadOnlySpan<byte> span)
4573
{
4674
byte scale = span[0];
75+
ValidateDecimalScale(scale);
76+
uint unsignedMantissa = NetworkOrderDeserializer.ReadUInt32(span.Slice(1));
77+
var data = new DecimalData(((uint)(scale << 16)) | unsignedMantissa & 0x80000000, 0, unsignedMantissa & 0x7FFFFFFF, 0);
78+
return Unsafe.As<DecimalData, decimal>(ref data);
79+
}
80+
81+
private static void ValidateDecimalScale(byte scale)
82+
{
4783
if (scale > 28)
4884
{
4985
throw new SyntaxErrorException($"Unrepresentable AMQP decimal table field: scale={scale}");
5086
}
51-
52-
uint unsignedMantissa = NetworkOrderDeserializer.ReadUInt32(span.Slice(1));
53-
return new decimal(
54-
// The low 32 bits of a 96-bit integer
55-
lo: (int)(unsignedMantissa & 0x7FFFFFFF),
56-
// The middle 32 bits of a 96-bit integer.
57-
mid: 0,
58-
// The high 32 bits of a 96-bit integer.
59-
hi: 0,
60-
isNegative: (unsignedMantissa & 0x80000000) != 0,
61-
// A power of 10 ranging from 0 to 28.
62-
scale: scale);
6387
}
6488

6589
public static IList ReadArray(ReadOnlySpan<byte> span, out int bytesRead)
@@ -229,16 +253,16 @@ public static int ReadBits(ReadOnlySpan<byte> span,
229253
out bool val11, out bool val12, out bool val13, out bool val14)
230254
{
231255
byte bits = span[0];
232-
val1 = (bits & 0b1000_0000) != 0;
233-
val2 = (bits & 0b0100_0000) != 0;
234-
val3 = (bits & 0b0010_0000) != 0;
235-
val4 = (bits & 0b0001_0000) != 0;
236-
val5 = (bits & 0b0000_1000) != 0;
237-
val6 = (bits & 0b0000_0100) != 0;
238-
val7 = (bits & 0b0000_0010) != 0;
239-
val8 = (bits & 0b0000_0001) != 0;
256+
val1 = (bits & 0b1000_0000) != 0;
257+
val2 = (bits & 0b0100_0000) != 0;
258+
val3 = (bits & 0b0010_0000) != 0;
259+
val4 = (bits & 0b0001_0000) != 0;
260+
val5 = (bits & 0b0000_1000) != 0;
261+
val6 = (bits & 0b0000_0100) != 0;
262+
val7 = (bits & 0b0000_0010) != 0;
263+
val8 = (bits & 0b0000_0001) != 0;
240264
bits = span[1];
241-
val9 = (bits & 0b1000_0000) != 0;
265+
val9 = (bits & 0b1000_0000) != 0;
242266
val10 = (bits & 0b0100_0000) != 0;
243267
val11 = (bits & 0b0010_0000) != 0;
244268
val12 = (bits & 0b0001_0000) != 0;
@@ -342,15 +366,11 @@ public static int GetArrayByteCount(IList val)
342366
return byteCount;
343367
}
344368

369+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
345370
public static int WriteDecimal(Span<byte> span, decimal value)
346371
{
347-
DecimalToAmqp(value, out byte scale, out int mantissa);
348-
span[0] = scale;
349-
return 1 + WriteLong(span.Slice(1), (uint)mantissa);
350-
}
351-
352-
private static void DecimalToAmqp(decimal value, out byte scale, out int mantissa)
353-
{
372+
// Cast the decimal to our struct to avoid the decimal.GetBits allocations.
373+
DecimalData data = Unsafe.As<decimal, DecimalData>(ref value);
354374
// According to the documentation :-
355375
// - word 0: low-order "mantissa"
356376
// - word 1, word 2: medium- and high-order "mantissa"
@@ -360,16 +380,16 @@ private static void DecimalToAmqp(decimal value, out byte scale, out int mantiss
360380
// We need to be careful about the range of word 0, too: we can
361381
// only take 31 bits worth of it, since the sign bit needs to
362382
// fit in there too.
363-
int[] bitRepresentation = decimal.GetBits(value);
364-
if (bitRepresentation[1] != 0 || // mantissa extends into middle word
365-
bitRepresentation[2] != 0 || // mantissa extends into top word
366-
bitRepresentation[0] < 0) // mantissa extends beyond 31 bits
383+
if (data.Mid != 0 || // mantissa extends into middle word
384+
data.Hi != 0 || // mantissa extends into top word
385+
data.Lo < 0) // mantissa extends beyond 31 bits
367386
{
368-
throw new WireFormattingException("Decimal overflow in AMQP encoding", value);
387+
return ThrowWireFormattingException(value);
369388
}
370-
scale = (byte)((((uint)bitRepresentation[3]) >> 16) & 0xFF);
371-
mantissa = (int)((((uint)bitRepresentation[3]) & 0x80000000) |
372-
(((uint)bitRepresentation[0]) & 0x7FFFFFFF));
389+
390+
span[0] = (byte)((data.Flags >> 16) & 0xFF);
391+
WriteLong(span.Slice(1), (data.Flags & 0b1000_0000_0000_0000_0000_0000_0000_0000) | (data.Lo & 0b0111_1111_1111_1111_1111_1111_1111_1111));
392+
return 5;
373393
}
374394

375395
public static int WriteFieldValue(Span<byte> span, object value)
@@ -499,7 +519,7 @@ public static int WriteLonglong(Span<byte> span, ulong val)
499519
[MethodImpl(MethodImplOptions.AggressiveInlining)]
500520
public static int WriteBits(Span<byte> span, bool val)
501521
{
502-
span[0] = val ? (byte) 1 : (byte) 0;
522+
span[0] = val ? (byte)1 : (byte)0;
503523
return 1;
504524
}
505525

@@ -855,5 +875,10 @@ public static int ThrowSyntaxErrorException(uint byteCount)
855875
{
856876
throw new SyntaxErrorException($"Long string too long; byte length={byteCount}, max={int.MaxValue}");
857877
}
878+
879+
private static int ThrowWireFormattingException(decimal value)
880+
{
881+
throw new WireFormattingException("Decimal overflow in AMQP encoding", value);
882+
}
858883
}
859884
}

0 commit comments

Comments
 (0)