Skip to content

Commit 830c747

Browse files
Merge pull request #984 from stebet/decimalSerialization
Speeding up decimal (de)serialization.
2 parents dbc9853 + a935c43 commit 830c747

File tree

1 file changed

+51
-26
lines changed

1 file changed

+51
-26
lines changed

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

Lines changed: 51 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -42,27 +42,46 @@ namespace RabbitMQ.Client.Impl
4242
{
4343
internal static class WireFormatting
4444
{
45+
// * DESCRIPTION TAKEN FROM MS REFERENCE SOURCE *
46+
// https://github.com/microsoft/referencesource/blob/master/mscorlib/system/decimal.cs
47+
// The lo, mid, hi, and flags fields contain the representation of the
48+
// Decimal value. The lo, mid, and hi fields contain the 96-bit integer
49+
// part of the Decimal. Bits 0-15 (the lower word) of the flags field are
50+
// unused and must be zero; bits 16-23 contain must contain a value between
51+
// 0 and 28, indicating the power of 10 to divide the 96-bit integer part
52+
// by to produce the Decimal value; bits 24-30 are unused and must be zero;
53+
// and finally bit 31 indicates the sign of the Decimal value, 0 meaning
54+
// positive and 1 meaning negative.
55+
readonly struct DecimalData
56+
{
57+
public readonly uint Flags;
58+
public readonly uint Hi;
59+
public readonly uint Lo;
60+
public readonly uint Mid;
61+
62+
internal DecimalData(uint flags, uint hi, uint lo, uint mid)
63+
{
64+
Flags = flags;
65+
Hi = hi;
66+
Lo = lo;
67+
Mid = mid;
68+
}
69+
}
70+
4571
private static UTF8Encoding UTF8 = new UTF8Encoding();
4672

73+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
4774
public static decimal ReadDecimal(ReadOnlySpan<byte> span)
4875
{
4976
byte scale = span[0];
5077
if (scale > 28)
5178
{
52-
throw new SyntaxErrorException($"Unrepresentable AMQP decimal table field: scale={scale}");
79+
ThrowInvalidDecimalScale(scale);
5380
}
5481

5582
uint unsignedMantissa = NetworkOrderDeserializer.ReadUInt32(span.Slice(1));
56-
return new decimal(
57-
// The low 32 bits of a 96-bit integer
58-
lo: (int)(unsignedMantissa & 0x7FFFFFFF),
59-
// The middle 32 bits of a 96-bit integer.
60-
mid: 0,
61-
// The high 32 bits of a 96-bit integer.
62-
hi: 0,
63-
isNegative: (unsignedMantissa & 0x80000000) != 0,
64-
// A power of 10 ranging from 0 to 28.
65-
scale: scale);
83+
var data = new DecimalData(((uint)(scale << 16)) | unsignedMantissa & 0x80000000, 0, unsignedMantissa & 0x7FFFFFFF, 0);
84+
return Unsafe.As<DecimalData, decimal>(ref data);
6685
}
6786

6887
public static IList ReadArray(ReadOnlySpan<byte> span, out int bytesRead)
@@ -360,15 +379,11 @@ public static int GetArrayByteCount(IList val)
360379
public static int GetByteCount(string val) => string.IsNullOrEmpty(val) ? 0 : UTF8.GetByteCount(val);
361380
#endif
362381

382+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
363383
public static int WriteDecimal(Span<byte> span, decimal value)
364384
{
365-
DecimalToAmqp(value, out byte scale, out int mantissa);
366-
span[0] = scale;
367-
return 1 + WriteLong(span.Slice(1), (uint)mantissa);
368-
}
369-
370-
private static void DecimalToAmqp(decimal value, out byte scale, out int mantissa)
371-
{
385+
// Cast the decimal to our struct to avoid the decimal.GetBits allocations.
386+
DecimalData data = Unsafe.As<decimal, DecimalData>(ref value);
372387
// According to the documentation :-
373388
// - word 0: low-order "mantissa"
374389
// - word 1, word 2: medium- and high-order "mantissa"
@@ -378,16 +393,16 @@ private static void DecimalToAmqp(decimal value, out byte scale, out int mantiss
378393
// We need to be careful about the range of word 0, too: we can
379394
// only take 31 bits worth of it, since the sign bit needs to
380395
// fit in there too.
381-
int[] bitRepresentation = decimal.GetBits(value);
382-
if (bitRepresentation[1] != 0 || // mantissa extends into middle word
383-
bitRepresentation[2] != 0 || // mantissa extends into top word
384-
bitRepresentation[0] < 0) // mantissa extends beyond 31 bits
396+
if (data.Mid != 0 || // mantissa extends into middle word
397+
data.Hi != 0 || // mantissa extends into top word
398+
data.Lo < 0) // mantissa extends beyond 31 bits
385399
{
386-
throw new WireFormattingException("Decimal overflow in AMQP encoding", value);
400+
return ThrowWireFormattingException(value);
387401
}
388-
scale = (byte)((((uint)bitRepresentation[3]) >> 16) & 0xFF);
389-
mantissa = (int)((((uint)bitRepresentation[3]) & 0x80000000) |
390-
(((uint)bitRepresentation[0]) & 0x7FFFFFFF));
402+
403+
span[0] = (byte)((data.Flags >> 16) & 0xFF);
404+
WriteLong(span.Slice(1), (data.Flags & 0b1000_0000_0000_0000_0000_0000_0000_0000) | (data.Lo & 0b0111_1111_1111_1111_1111_1111_1111_1111));
405+
return 5;
391406
}
392407

393408
public static int WriteFieldValue(Span<byte> span, object value)
@@ -908,5 +923,15 @@ public static int ThrowSyntaxErrorException(uint byteCount)
908923
{
909924
throw new SyntaxErrorException($"Long string too long; byte length={byteCount}, max={int.MaxValue}");
910925
}
926+
927+
private static int ThrowWireFormattingException(decimal value)
928+
{
929+
throw new WireFormattingException("Decimal overflow in AMQP encoding", value);
930+
}
931+
932+
private static decimal ThrowInvalidDecimalScale(int scale)
933+
{
934+
throw new SyntaxErrorException($"Unrepresentable AMQP decimal table field: scale={scale}");
935+
}
911936
}
912937
}

0 commit comments

Comments
 (0)