Skip to content

Commit cd142a0

Browse files
authored
Merge pull request #55 from gfoidl/low-hanging-perf
Some micro-perf-optimizations
2 parents 5b83299 + 66d7c67 commit cd142a0

File tree

9 files changed

+205
-129
lines changed

9 files changed

+205
-129
lines changed

csFastFloat/AssemblyInfo.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
using System.Runtime.CompilerServices;
2+
3+
[assembly: InternalsVisibleTo("TestcsFastFloat")]
4+
[assembly: InternalsVisibleTo("Benchmark")]
5+
6+
#if NET5_0
7+
[module: SkipLocalsInit]
8+
#endif

csFastFloat/Constants/Constants.cs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
using System;
2+
using System.Diagnostics;
3+
using System.Runtime.CompilerServices;
4+
using System.Runtime.InteropServices;
25

36
namespace csFastFloat
47
{
@@ -19,7 +22,32 @@ internal static class Constants
1922
internal const int smallest_power_of_five = -342;
2023
internal const int largest_power_of_five = 308;
2124

22-
internal readonly static ulong[] power_of_five_128 = {
25+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
26+
internal static byte get_powers(uint n)
27+
{
28+
Debug.Assert(n < powersTable.Length);
29+
ref byte tableRef = ref MemoryMarshal.GetReference(powersTable);
30+
return Unsafe.AddByteOffset(ref tableRef, (nint)n);
31+
}
32+
33+
private static ReadOnlySpan<byte> powersTable => new byte[19] {
34+
0, 3, 6, 9, 13, 16, 19, 23, 26, 29, //
35+
33, 36, 39, 43, 46, 49, 53, 56, 59, //
36+
};
37+
38+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
39+
internal static ulong get_power_of_five_128(int index)
40+
{
41+
#if NET5_0
42+
Debug.Assert(index < power_of_five_128.Length);
43+
ref ulong tableRef = ref MemoryMarshal.GetArrayDataReference(power_of_five_128);
44+
return Unsafe.Add(ref tableRef, (nint)(uint)index);
45+
# else
46+
return power_of_five_128[index];
47+
#endif
48+
}
49+
50+
private readonly static ulong[] power_of_five_128 = {
2351
0xeef453d6923bd65a,0x113faa2906a13b3f,
2452
0x9558b4661b6565f8,0x4ac7ca59a424c507,
2553
0xbaaee17fa23ebf76,0x5d79bcf00d2df649,

csFastFloat/FastDoubleParser.cs

Lines changed: 27 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,10 @@
1-
using csFastFloat.Enums;
2-
using csFastFloat.Structures;
3-
using System;
4-
using System.Collections.Generic;
5-
using System.Globalization;
1+
using System;
2+
using System.Diagnostics;
63
using System.Numerics;
74
using System.Runtime.CompilerServices;
8-
using System.Text;
9-
10-
[assembly: InternalsVisibleTo("TestcsFastFloat")]
11-
12-
[assembly: InternalsVisibleTo("Benchmark")]
13-
5+
using System.Runtime.InteropServices;
6+
using csFastFloat.Enums;
7+
using csFastFloat.Structures;
148

159
namespace csFastFloat
1610
{
@@ -20,23 +14,27 @@ namespace csFastFloat
2014
public static class FastDoubleParser
2115
{
2216
private static void ThrowArgumentException() => throw new ArgumentException();
23-
public static double exact_power_of_ten(long power) => Constants.powers_of_ten_double[power];
2417

18+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
19+
public static double exact_power_of_ten(long power)
20+
{
21+
#if NET5_0
22+
Debug.Assert(power < Constants.powers_of_ten_double.Length);
23+
ref double tableRef = ref MemoryMarshal.GetArrayDataReference(Constants.powers_of_ten_double);
24+
return Unsafe.Add(ref tableRef, (nint)power);
25+
#else
26+
return Constants.powers_of_ten_double[power];
27+
#endif
2528

29+
}
2630

2731
public static double ToFloat(bool negative, AdjustedMantissa am)
2832
{
29-
double d;
3033
ulong word = am.mantissa;
31-
word |= (ulong)(am.power2) << DoubleBinaryConstants.mantissa_explicit_bits;
34+
word |= (ulong)(uint)(am.power2) << DoubleBinaryConstants.mantissa_explicit_bits;
3235
word = negative ? word | ((ulong)(1) << DoubleBinaryConstants.sign_index) : word;
3336

34-
unsafe
35-
{
36-
Buffer.MemoryCopy(&word, &d, sizeof(double), sizeof(double));
37-
}
38-
39-
return d;
37+
return BitConverter.Int64BitsToDouble((long)word);
4038
}
4139

4240
public static double FastPath(ParsedNumberString pns)
@@ -63,16 +61,16 @@ public static unsafe double ParseDouble(string s, chars_format expectedFormat =
6361

6462
fixed (char* pStart = s)
6563
{
66-
return ParseDouble(pStart, pStart + s.Length, expectedFormat, decimal_separator);
64+
return ParseDouble(pStart, pStart + (uint)s.Length, expectedFormat, decimal_separator);
6765
}
6866
}
6967

7068

7169
public static unsafe double ParseDouble(ReadOnlySpan<char> s, chars_format expectedFormat = chars_format.is_general, char decimal_separator = '.')
7270
{
73-
fixed(char* pStart = s)
71+
fixed (char* pStart = s)
7472
{
75-
return ParseDouble(pStart, pStart + s.Length, expectedFormat, decimal_separator);
73+
return ParseDouble(pStart, pStart + (uint)s.Length, expectedFormat, decimal_separator);
7674
}
7775
}
7876

@@ -133,10 +131,8 @@ internal static AdjustedMantissa ComputeFloat (long q, ulong w)
133131

134132
if ((w == 0) || (q < DoubleBinaryConstants.smallest_power_of_ten))
135133
{
136-
answer.power2 = 0;
137-
answer.mantissa = 0;
138134
// result should be zero
139-
return answer;
135+
return default;
140136
}
141137
if (q > DoubleBinaryConstants.largest_power_of_ten)
142138
{
@@ -239,23 +235,13 @@ internal static AdjustedMantissa ComputeFloat (long q, ulong w)
239235
}
240236

241237

242-
243-
244-
static readonly byte[] powers = {
245-
0, 3, 6, 9, 13, 16, 19, 23, 26, 29, //
246-
33, 36, 39, 43, 46, 49, 53, 56, 59, //
247-
};
248-
249-
250-
internal static AdjustedMantissa ComputeFloat(DecimalInfo d)
238+
internal static AdjustedMantissa ComputeFloat(DecimalInfo d)
251239
{
252240
AdjustedMantissa answer = new AdjustedMantissa();
253241
if (d.num_digits == 0)
254242
{
255243
// should be zero
256-
answer.power2 = 0;
257-
answer.mantissa = 0;
258-
return answer;
244+
return default;
259245
}
260246
// At this point, going further, we can assume that d.num_digits > 0.
261247
//
@@ -270,9 +256,7 @@ internal static AdjustedMantissa ComputeFloat(DecimalInfo d)
270256
// We have something smaller than 1e-324 which is always zero
271257
// in binary64 and binary32.
272258
// It should be zero.
273-
answer.power2 = 0;
274-
answer.mantissa = 0;
275-
return answer;
259+
return default;
276260
}
277261
else if (d.decimal_point >= 310)
278262
{
@@ -289,7 +273,7 @@ internal static AdjustedMantissa ComputeFloat(DecimalInfo d)
289273
while (d.decimal_point > 0)
290274
{
291275
uint n = (uint)(d.decimal_point);
292-
int shift = (n < num_powers) ? powers[n] : max_shift;
276+
int shift = (n < num_powers) ? Constants.get_powers(n) : max_shift;
293277

294278
d.decimal_right_shift(shift);
295279
if (d.decimal_point < -Constants.decimal_point_range)
@@ -318,7 +302,7 @@ internal static AdjustedMantissa ComputeFloat(DecimalInfo d)
318302
else
319303
{
320304
uint n = (uint)(-d.decimal_point);
321-
shift = (n < num_powers) ? powers[n] : max_shift;
305+
shift = (n < num_powers) ? Constants.get_powers(n) : max_shift;
322306
}
323307

324308
d.decimal_left_shift(shift);

csFastFloat/FastFloatParser.cs

Lines changed: 40 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,37 @@
1-
using csFastFloat.Enums;
2-
using csFastFloat.Structures;
3-
using System;
1+
using System;
2+
using System.Diagnostics;
43
using System.Numerics;
54
using System.Runtime.CompilerServices;
6-
7-
[assembly: InternalsVisibleTo("TestcsFastFloat")]
8-
9-
[assembly: InternalsVisibleTo("Benchmark")]
10-
5+
using System.Runtime.InteropServices;
6+
using csFastFloat.Enums;
7+
using csFastFloat.Structures;
118

129
namespace csFastFloat
1310
{
1411
public static class FastFloatParser
1512
{
1613
private static void ThrowArgumentException() => throw new ArgumentException();
17-
public static float exact_power_of_ten(long power) => Constants.powers_of_ten_float[power];
1814

19-
public static float ToFloat(bool negative, AdjustedMantissa am)
15+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
16+
public static float exact_power_of_ten(long power)
17+
{
18+
#if NET5_0
19+
Debug.Assert(power < Constants.powers_of_ten_float.Length);
20+
ref float tableRef = ref MemoryMarshal.GetArrayDataReference(Constants.powers_of_ten_float);
21+
return Unsafe.Add(ref tableRef, (nint)power);
22+
#else
23+
return Constants.powers_of_ten_float[power];
24+
#endif
25+
}
26+
27+
public static unsafe float ToFloat(bool negative, AdjustedMantissa am)
2028
{
21-
float d;
2229
ulong word = am.mantissa;
23-
word |= (ulong)(am.power2) << FloatBinaryConstants.mantissa_explicit_bits;
30+
word |= (ulong)(uint)(am.power2) << FloatBinaryConstants.mantissa_explicit_bits;
2431
word = negative ? word | ((ulong)(1) << FloatBinaryConstants.sign_index) : word;
2532

26-
unsafe
27-
{
28-
Buffer.MemoryCopy(&word, &d, sizeof(float), sizeof(float));
29-
}
33+
float d = 0;
34+
Unsafe.Copy(ref d, &word);
3035

3136
return d;
3237
}
@@ -55,15 +60,15 @@ public static unsafe float ParseFloat(string s, chars_format expectedFormat = ch
5560

5661
fixed (char* pStart = s)
5762
{
58-
return ParseFloat(pStart, pStart + s.Length, expectedFormat, decimal_separator);
63+
return ParseFloat(pStart, pStart + (uint)s.Length, expectedFormat, decimal_separator);
5964
}
6065
}
6166

6267
public static unsafe float ParseFloat(ReadOnlySpan<char> s, chars_format expectedFormat = chars_format.is_general, char decimal_separator = '.')
6368
{
6469
fixed (char* pStart = s)
6570
{
66-
return ParseFloat(pStart, pStart + s.Length, expectedFormat, decimal_separator);
71+
return ParseFloat(pStart, pStart + (uint)s.Length, expectedFormat, decimal_separator);
6772
}
6873
}
6974

@@ -241,9 +246,7 @@ internal static AdjustedMantissa ComputeFloat(DecimalInfo d)
241246
if (d.num_digits == 0)
242247
{
243248
// should be zero
244-
answer.power2 = 0;
245-
answer.mantissa = 0;
246-
return answer;
249+
return default;
247250
}
248251
// At this point, going further, we can assume that d.num_digits > 0.
249252
//
@@ -258,9 +261,7 @@ internal static AdjustedMantissa ComputeFloat(DecimalInfo d)
258261
// We have something smaller than 1e-324 which is always zero
259262
// in binary64 and binary32.
260263
// It should be zero.
261-
answer.power2 = 0;
262-
answer.mantissa = 0;
263-
return answer;
264+
return default;
264265
}
265266
else if (d.decimal_point >= 310)
266267
{
@@ -272,15 +273,12 @@ internal static AdjustedMantissa ComputeFloat(DecimalInfo d)
272273
}
273274
const int max_shift = 60;
274275
const uint num_powers = 19;
275-
ReadOnlySpan<byte> powers = new byte[] {
276-
0, 3, 6, 9, 13, 16, 19, 23, 26, 29, //
277-
33, 36, 39, 43, 46, 49, 53, 56, 59, //
278-
};
276+
279277
int exp2 = 0;
280278
while (d.decimal_point > 0)
281279
{
282280
uint n = (uint)(d.decimal_point);
283-
int shift = (n < num_powers) ? powers[(int)n] : max_shift;
281+
int shift = (n < num_powers) ? Constants.get_powers(n) : max_shift;
284282

285283
d.decimal_right_shift(shift);
286284
if (d.decimal_point < -Constants.decimal_point_range)
@@ -309,7 +307,7 @@ internal static AdjustedMantissa ComputeFloat(DecimalInfo d)
309307
else
310308
{
311309
uint n = (uint)(-d.decimal_point);
312-
shift = (n < num_powers) ? powers[(int)n] : max_shift;
310+
shift = (n < num_powers) ? Constants.get_powers(n) : max_shift;
313311
}
314312

315313
d.decimal_left_shift(shift);
@@ -430,7 +428,7 @@ unsafe static internal ParsedNumberString ParseNumberString(char* p, char* pend,
430428
{
431429
return answer;
432430
}
433-
if (!Utils.is_integer(*p) && (*p != decimal_separator)) // culture info ?
431+
if (!Utils.is_integer(*p, out uint _) && (*p != decimal_separator)) // culture info ?
434432
{ // a sign must be followed by an integer or the dot
435433
return answer;
436434
}
@@ -439,12 +437,11 @@ unsafe static internal ParsedNumberString ParseNumberString(char* p, char* pend,
439437

440438
ulong i = 0; // an unsigned int avoids signed overflows (which are bad)
441439

442-
while ((p != pend) && Utils.is_integer(*p))
440+
while ((p != pend) && Utils.is_integer(*p, out uint cMinus0))
443441
{
444442
// a multiplication by 10 is cheaper than an arbitrary integer
445443
// multiplication
446-
i = 10 * i +
447-
(ulong)(*p - '0'); // might overflow, we will handle the overflow later
444+
i = 10 * i + (ulong)cMinus0; // might overflow, we will handle the overflow later
448445
++p;
449446
}
450447
char* end_of_integer_part = p;
@@ -453,9 +450,9 @@ unsafe static internal ParsedNumberString ParseNumberString(char* p, char* pend,
453450
if ((p != pend) && (*p == decimal_separator))
454451
{
455452
++p;
456-
while ((p != pend) && Utils.is_integer(*p))
453+
while ((p != pend) && Utils.is_integer(*p, out uint cMinus0))
457454
{
458-
byte digit = (byte)(*p - '0');
455+
byte digit = (byte)cMinus0;
459456
++p;
460457
i = i * 10 + digit; // in rare cases, this will overflow, but that's ok
461458
}
@@ -482,7 +479,7 @@ unsafe static internal ParsedNumberString ParseNumberString(char* p, char* pend,
482479
{
483480
++p;
484481
}
485-
if ((p == pend) || !Utils.is_integer(*p))
482+
if ((p == pend) || !Utils.is_integer(*p, out uint _))
486483
{
487484
if (expectedFormat != chars_format.is_fixed)
488485
{
@@ -494,9 +491,9 @@ unsafe static internal ParsedNumberString ParseNumberString(char* p, char* pend,
494491
}
495492
else
496493
{
497-
while ((p != pend) && Utils.is_integer(*p))
494+
while ((p != pend) && Utils.is_integer(*p, out uint cMinus0))
498495
{
499-
byte digit = (byte)(*p - '0');
496+
byte digit = (byte)cMinus0;
500497
if (exp_number < 0x10000)
501498
{
502499
exp_number = 10 * exp_number + digit;
@@ -539,9 +536,9 @@ unsafe static internal ParsedNumberString ParseNumberString(char* p, char* pend,
539536
i = 0;
540537
p = start_digits;
541538
const ulong minimal_nineteen_digit_integer = 1000000000000000000;
542-
while ((i < minimal_nineteen_digit_integer) && (p != pend) && Utils.is_integer(*p))
539+
while ((i < minimal_nineteen_digit_integer) && (p != pend) && Utils.is_integer(*p, out uint cMinus0))
543540
{
544-
i = i * 10 + (ulong)(*p - '0');
541+
i = i * 10 + (ulong)cMinus0;
545542
++p;
546543
}
547544
if (i >= minimal_nineteen_digit_integer)
@@ -552,9 +549,9 @@ unsafe static internal ParsedNumberString ParseNumberString(char* p, char* pend,
552549
{ // We have a value with a fractional component.
553550
p++; // skip the '.'
554551
char* first_after_period = p;
555-
while ((i < minimal_nineteen_digit_integer) && (p != pend) && Utils.is_integer(*p))
552+
while ((i < minimal_nineteen_digit_integer) && (p != pend) && Utils.is_integer(*p, out uint cMinus0))
556553
{
557-
i = i * 10 + (ulong)(*p - '0');
554+
i = i * 10 + (ulong)cMinus0;
558555
++p;
559556
}
560557
exponent = first_after_period - p + exp_number;

0 commit comments

Comments
 (0)