Skip to content

Commit b2e8dd9

Browse files
authored
Enum boxing optimizations (UnitKey) (#1507)
`Enum` boxing optimizations: - introduce the `UnitKey` struct as a replacement for the conversions from `TUnit` to `Enum` - re-mapped the `UnitAbbreviationCache` using the `UnitKey` for the concurrent dictionary - removed all `Convert.ToInt32(..)` calls - optimized the `QuantityInfoLookup` collections - refactored some of the `Quantity`/`UnitConverter` code (using the `UnitParser`/`QuantityInfoLookup`) - added some tests and benchmarks covering the new modifications Breaking changes: - changing the `Quantity.Names` from `string[]` to `IReadOnlyCollection<string>` - changing the `Quantity.Infos` from `QuantityInfo[]` to `IReadOnlyList<QuantityInfo>` - marking the `UnitConverter.ConvertByAbbreviation` method that takes a `string?` for the `IFormatProvider?` as `[Obsolete]` (created an overload) - `UnitsNetSetup`: replaced the `ICollection<QuantityInfo>` constructor parameter with `IEnumerable<QuantityInfo>` - renamed the `QuantityInfo.ValueType` to `QuantityInfo.QuantityType` (making the old property `[Obsolete]`) - renamed two of the parameters of `UnitConverter.ConvertByName`
1 parent 96f924a commit b2e8dd9

36 files changed

+1970
-487
lines changed

Directory.Packages.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
44
</PropertyGroup>
55
<ItemGroup>
6+
<PackageVersion Include="System.Runtime.CompilerServices.Unsafe" Version="6.1.0" />
67
<PackageVersion Include="BenchmarkDotNet" Version="0.14.0" />
78
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
89
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />

UnitsNet.Benchmark/Conversions/FromString/ParseUnitBenchmarks.cs

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Globalization;
23
using BenchmarkDotNet.Attributes;
34
using BenchmarkDotNet.Jobs;
45
using UnitsNet.Units;
@@ -10,44 +11,54 @@ namespace UnitsNet.Benchmark.Conversions.FromString;
1011
[SimpleJob(RuntimeMoniker.Net80)]
1112
public class ParseUnitBenchmarks
1213
{
14+
private const int NbAbbreviations = 1000;
15+
16+
private static readonly CultureInfo Culture = CultureInfo.InvariantCulture;
1317
private readonly Random _random = new(42);
1418
private string[] _densityUnits;
1519
private string[] _massUnits;
1620
private string[] _pressureUnits;
1721
private string[] _volumeFlowUnits;
1822
private string[] _volumeUnits = [];
1923

20-
[Params(1000)]
21-
public int NbAbbreviations { get; set; }
22-
2324
[GlobalSetup(Target = nameof(ParseMassUnit))]
2425
public void PrepareMassUnits()
2526
{
2627
_massUnits = _random.GetItems(["mg", "g", "kg", "lbs", "Mlbs"], NbAbbreviations);
28+
// initializes the QuantityInfoLookup and the abbreviations cache
29+
Mass.TryParseUnit("_invalid", Culture, out _);
2730
}
2831

2932
[GlobalSetup(Target = nameof(ParseVolumeUnit))]
3033
public void PrepareVolumeUnits()
3134
{
3235
_volumeUnits = _random.GetItems(["ml", "l", "L", "cm³", "m³"], NbAbbreviations);
36+
// initializes the QuantityInfoLookup and the abbreviations cache
37+
Volume.TryParseUnit("_invalid", Culture, out _);
3338
}
3439

3540
[GlobalSetup(Target = nameof(ParseDensityUnit))]
3641
public void PrepareDensityUnits()
3742
{
3843
_densityUnits = _random.GetRandomAbbreviations<DensityUnit>(UnitsNetSetup.Default.UnitAbbreviations, NbAbbreviations);
44+
// initializes the QuantityInfoLookup and the abbreviations cache
45+
Density.TryParseUnit("_invalid", Culture, out _);
3946
}
4047

4148
[GlobalSetup(Target = nameof(ParsePressureUnit))]
4249
public void PreparePressureUnits()
4350
{
4451
_pressureUnits = _random.GetRandomAbbreviations<PressureUnit>(UnitsNetSetup.Default.UnitAbbreviations, NbAbbreviations);
52+
// initializes the QuantityInfoLookup and the abbreviations cache
53+
Pressure.TryParseUnit("_invalid", Culture, out _);
4554
}
4655

4756
[GlobalSetup(Target = nameof(ParseVolumeFlowUnit))]
4857
public void PrepareVolumeFlowUnits()
4958
{
5059
_volumeFlowUnits = _random.GetRandomAbbreviations<VolumeFlowUnit>(UnitsNetSetup.Default.UnitAbbreviations, NbAbbreviations);
60+
// initializes the QuantityInfoLookup and the abbreviations cache
61+
VolumeFlow.TryParseUnit("_invalid", Culture, out _);
5162
}
5263

5364
[Benchmark(Baseline = true)]
@@ -56,7 +67,7 @@ public MassUnit ParseMassUnit()
5667
MassUnit unit = default;
5768
foreach (var unitToParse in _massUnits)
5869
{
59-
unit = Mass.ParseUnit(unitToParse);
70+
unit = Mass.ParseUnit(unitToParse, Culture);
6071
}
6172

6273
return unit;
@@ -68,7 +79,7 @@ public VolumeUnit ParseVolumeUnit()
6879
VolumeUnit unit = default;
6980
foreach (var unitToParse in _volumeUnits)
7081
{
71-
unit = Volume.ParseUnit(unitToParse);
82+
unit = Volume.ParseUnit(unitToParse, Culture);
7283
}
7384

7485
return unit;
@@ -80,7 +91,7 @@ public DensityUnit ParseDensityUnit()
8091
DensityUnit unit = default;
8192
foreach (var unitToParse in _densityUnits)
8293
{
83-
unit = Density.ParseUnit(unitToParse);
94+
unit = Density.ParseUnit(unitToParse, Culture);
8495
}
8596

8697
return unit;
@@ -92,7 +103,7 @@ public PressureUnit ParsePressureUnit()
92103
PressureUnit unit = default;
93104
foreach (var unitToParse in _pressureUnits)
94105
{
95-
unit = Pressure.ParseUnit(unitToParse);
106+
unit = Pressure.ParseUnit(unitToParse, Culture);
96107
}
97108

98109
return unit;
@@ -104,7 +115,7 @@ public VolumeFlowUnit ParseVolumeFlowUnit()
104115
VolumeFlowUnit unit = default;
105116
foreach (var unitToParse in _volumeFlowUnits)
106117
{
107-
unit = VolumeFlow.ParseUnit(unitToParse);
118+
unit = VolumeFlow.ParseUnit(unitToParse, Culture);
108119
}
109120

110121
return unit;
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
using System;
2+
using System.Globalization;
3+
using System.Linq;
4+
using BenchmarkDotNet.Attributes;
5+
using BenchmarkDotNet.Jobs;
6+
using UnitsNet.Units;
7+
8+
namespace UnitsNet.Benchmark.Conversions.FromString;
9+
10+
[MemoryDiagnoser]
11+
[SimpleJob(RuntimeMoniker.Net48)]
12+
[SimpleJob(RuntimeMoniker.Net80)]
13+
public class QuantityFromStringBenchmarks
14+
{
15+
private static readonly CultureInfo Culture = CultureInfo.InvariantCulture;
16+
private static readonly string ValueToParse = 123.456.ToString(Culture);
17+
18+
private readonly Random _random = new(42);
19+
private string[] _quantitiesToParse;
20+
21+
[Params(1000)]
22+
public int NbAbbreviations { get; set; }
23+
24+
[GlobalSetup(Target = nameof(FromMassString))]
25+
public void PrepareMassStrings()
26+
{
27+
// can't have "mg" or "g" (see Acceleration.StandardGravity) and who knows what more...
28+
_quantitiesToParse = _random.GetItems(["kg", "lbs", "Mlbs"], NbAbbreviations).Select(abbreviation => $"{ValueToParse} {abbreviation}").ToArray();
29+
}
30+
31+
[GlobalSetup(Target = nameof(FromVolumeUnitAbbreviation))]
32+
public void PrepareVolumeStrings()
33+
{
34+
_quantitiesToParse = _random.GetItems(["ml", "l", "cm³", "m³"], NbAbbreviations).Select(abbreviation => $"{ValueToParse} {abbreviation}").ToArray();;
35+
}
36+
37+
[GlobalSetup(Target = nameof(FromPressureUnitAbbreviation))]
38+
public void PreparePressureUnits()
39+
{
40+
_quantitiesToParse = _random.GetRandomAbbreviations<PressureUnit>(UnitsNetSetup.Default.UnitAbbreviations, NbAbbreviations).Select(abbreviation => $"{ValueToParse} {abbreviation}").ToArray();;
41+
}
42+
43+
[GlobalSetup(Target = nameof(FromVolumeFlowUnitAbbreviation))]
44+
public void PrepareVolumeFlowUnits()
45+
{
46+
// can't have "bpm" (see Frequency)
47+
_quantitiesToParse =
48+
_random.GetItems(
49+
UnitsNetSetup.Default.UnitAbbreviations.GetAllUnitAbbreviationsForQuantity(typeof(VolumeFlowUnit)).Where(x => x != "bpm").ToArray(),
50+
NbAbbreviations).Select(abbreviation => $"{ValueToParse} {abbreviation}").ToArray();
51+
}
52+
53+
[Benchmark(Baseline = true)]
54+
public IQuantity FromMassString()
55+
{
56+
IQuantity quantity = null;
57+
foreach (var quantityString in _quantitiesToParse)
58+
{
59+
quantity = Quantity.Parse(Culture, typeof(Mass), quantityString);
60+
}
61+
62+
return quantity;
63+
}
64+
65+
[Benchmark(Baseline = false)]
66+
public IQuantity FromVolumeUnitAbbreviation()
67+
{
68+
IQuantity quantity = null;
69+
foreach (var quantityString in _quantitiesToParse)
70+
{
71+
quantity = Quantity.Parse(Culture, typeof(Volume), quantityString);
72+
}
73+
74+
return quantity;
75+
}
76+
77+
[Benchmark(Baseline = false)]
78+
public IQuantity FromPressureUnitAbbreviation()
79+
{
80+
IQuantity quantity = null;
81+
foreach (var quantityString in _quantitiesToParse)
82+
{
83+
quantity = Quantity.Parse(Culture, typeof(Pressure), quantityString);
84+
}
85+
86+
return quantity;
87+
}
88+
89+
[Benchmark(Baseline = false)]
90+
public IQuantity FromVolumeFlowUnitAbbreviation()
91+
{
92+
IQuantity quantity = null;
93+
foreach (var quantityString in _quantitiesToParse)
94+
{
95+
quantity = Quantity.Parse(Culture, typeof(VolumeFlow), quantityString);
96+
}
97+
98+
return quantity;
99+
}
100+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
using System;
2+
using System.Globalization;
3+
using System.Linq;
4+
using BenchmarkDotNet.Attributes;
5+
using BenchmarkDotNet.Jobs;
6+
using UnitsNet.Units;
7+
8+
namespace UnitsNet.Benchmark.Conversions.FromString;
9+
10+
[MemoryDiagnoser]
11+
[SimpleJob(RuntimeMoniker.Net48)]
12+
[SimpleJob(RuntimeMoniker.Net80)]
13+
public class QuantityFromUnitAbbreviationBenchmarks
14+
{
15+
private static readonly CultureInfo Culture = CultureInfo.InvariantCulture;
16+
private readonly Random _random = new(42);
17+
private string[] _massUnits;
18+
private string[] _pressureUnits;
19+
private string[] _volumeFlowUnits;
20+
private string[] _volumeUnits = [];
21+
22+
[Params(1000)]
23+
public int NbAbbreviations { get; set; }
24+
25+
[GlobalSetup(Target = nameof(FromMassUnitAbbreviation))]
26+
public void PrepareMassUnits()
27+
{
28+
// can't have "mg" or "g" (see Acceleration.StandardGravity) and who knows what more...
29+
_massUnits = _random.GetItems(["kg", "lbs", "Mlbs"], NbAbbreviations);
30+
}
31+
32+
[GlobalSetup(Target = nameof(FromVolumeUnitAbbreviation))]
33+
public void PrepareVolumeUnits()
34+
{
35+
_volumeUnits = _random.GetItems(["ml", "l", "cm³", "m³"], NbAbbreviations);
36+
}
37+
38+
[GlobalSetup(Target = nameof(FromPressureUnitAbbreviation))]
39+
public void PreparePressureUnits()
40+
{
41+
_pressureUnits = _random.GetRandomAbbreviations<PressureUnit>(UnitsNetSetup.Default.UnitAbbreviations, NbAbbreviations);
42+
}
43+
44+
[GlobalSetup(Target = nameof(FromVolumeFlowUnitAbbreviation))]
45+
public void PrepareVolumeFlowUnits()
46+
{
47+
// can't have "bpm" (see Frequency)
48+
_volumeFlowUnits =
49+
_random.GetItems(
50+
UnitsNetSetup.Default.UnitAbbreviations.GetAllUnitAbbreviationsForQuantity(typeof(VolumeFlowUnit)).Where(x => x != "bpm").ToArray(),
51+
NbAbbreviations);
52+
}
53+
54+
[Benchmark(Baseline = true)]
55+
public IQuantity FromMassUnitAbbreviation()
56+
{
57+
IQuantity quantity = null;
58+
foreach (var unitToParse in _massUnits)
59+
{
60+
quantity = Quantity.FromUnitAbbreviation(Culture, 1, unitToParse);
61+
}
62+
63+
return quantity;
64+
}
65+
66+
[Benchmark(Baseline = false)]
67+
public IQuantity FromVolumeUnitAbbreviation()
68+
{
69+
IQuantity quantity = null;
70+
foreach (var unitToParse in _volumeUnits)
71+
{
72+
quantity = Quantity.FromUnitAbbreviation(Culture, 1, unitToParse);
73+
}
74+
75+
return quantity;
76+
}
77+
78+
[Benchmark(Baseline = false)]
79+
public IQuantity FromPressureUnitAbbreviation()
80+
{
81+
IQuantity quantity = null;
82+
foreach (var unitToParse in _pressureUnits)
83+
{
84+
quantity = Quantity.FromUnitAbbreviation(Culture, 1, unitToParse);
85+
}
86+
87+
return quantity;
88+
}
89+
90+
[Benchmark(Baseline = false)]
91+
public IQuantity FromVolumeFlowUnitAbbreviation()
92+
{
93+
IQuantity quantity = null;
94+
foreach (var unitToParse in _volumeFlowUnits)
95+
{
96+
quantity = Quantity.FromUnitAbbreviation(Culture, 1, unitToParse);
97+
}
98+
99+
return quantity;
100+
}
101+
}

0 commit comments

Comments
 (0)