Skip to content
This repository was archived by the owner on Dec 24, 2022. It is now read-only.

Commit 870658e

Browse files
authored
Merge pull request #496 from ServiceStack/netcore
Optimizations for deserialization of Arrays
2 parents ba5abe2 + 8e7b6fc commit 870658e

39 files changed

+2998
-534
lines changed
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
using System;
2+
using System.Globalization;
3+
using BenchmarkDotNet.Attributes;
4+
using ServiceStack.Text;
5+
#if NETCOREAPP1_1
6+
using Microsoft.Extensions.Primitives;
7+
#endif
8+
using ServiceStack.Text.Support;
9+
10+
namespace ServiceStack.Text.Benchmarks
11+
{
12+
public class ParseBuiltinBenchmarks
13+
{
14+
const string int32_1 = "1234";
15+
const string int32_2 = "-1234";
16+
const string decimal_1 = "1234.5678";
17+
const string decimal_2 = "-1234.5678";
18+
const string decimal_3 = "1234.5678901234567890";
19+
const string decimal_4 = "-1234.5678901234567890";
20+
const string guid_1 = "{b6170a18-3dd7-4a9b-b5d6-21033b5ad162}";
21+
22+
readonly StringSegment ss_int32_1 = new StringSegment(int32_1);
23+
readonly StringSegment ss_int32_2 = new StringSegment(int32_2);
24+
readonly StringSegment ss_decimal_1 = new StringSegment(decimal_1);
25+
readonly StringSegment ss_decimal_2 = new StringSegment(decimal_2);
26+
readonly StringSegment ss_decimal_3 = new StringSegment(decimal_3);
27+
readonly StringSegment ss_decimal_4 = new StringSegment(decimal_4);
28+
readonly StringSegment ss_guid_1 = new StringSegment(guid_1);
29+
30+
[Benchmark]
31+
public void Int32Parse()
32+
{
33+
var res1 = int.Parse(int32_1, CultureInfo.InvariantCulture);
34+
var res2 = int.Parse(int32_2, CultureInfo.InvariantCulture);
35+
}
36+
37+
[Benchmark]
38+
public void StringSegment_Int32Parse()
39+
{
40+
var res1 = ss_int32_1.ParseInt32();
41+
var res2 = ss_int32_2.ParseInt32();
42+
}
43+
44+
[Benchmark]
45+
public void DecimalParse()
46+
{
47+
var res1 = decimal.Parse(decimal_1, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture);
48+
var res2 = decimal.Parse(decimal_2, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture);
49+
}
50+
51+
[Benchmark]
52+
public void BigDecimalParse()
53+
{
54+
var res1 = decimal.Parse(decimal_3, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture);
55+
var res2 = decimal.Parse(decimal_4, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture);
56+
}
57+
58+
59+
[Benchmark]
60+
public void StringSegment_DecimalParse()
61+
{
62+
var res1 = ss_decimal_1.ParseDecimal(true);
63+
var res2 = ss_decimal_2.ParseDecimal(true);
64+
}
65+
66+
[Benchmark]
67+
public void StringSegment_BigDecimalParse()
68+
{
69+
var res1 = ss_decimal_3.ParseDecimal(true);
70+
var res2 = ss_decimal_4.ParseDecimal(true);
71+
}
72+
73+
[Benchmark]
74+
public void GuidParse()
75+
{
76+
var res1 = Guid.Parse(guid_1);
77+
}
78+
79+
[Benchmark]
80+
public void StringSegment_GuidParse()
81+
{
82+
var res1 = ss_guid_1.ParseGuid();
83+
}
84+
}
85+
}

benchmarks/ServiceStack.Text.Benchmarks/Program.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ public class Program
1111
public static void Main(string[] args)
1212
{
1313
Console.WriteLine("Hello World!");
14-
BenchmarkRunner.Run<JsonSerializationBenchmarks>(
14+
//BenchmarkRunner.Run<JsonSerializationBenchmarks>(
15+
BenchmarkRunner.Run<ParseBuiltinBenchmarks>(
1516
ManualConfig
1617
.Create(DefaultConfig.Instance)
1718
//.With(Job.RyuJitX64)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFrameworks>net45;netcoreapp1.1</TargetFrameworks>
5+
<AssemblyName>ServiceStack.Text.Benchmarks</AssemblyName>
6+
<OutputType>Exe</OutputType>
7+
<PackageId>ServiceStack.Text.Benchmarks</PackageId>
8+
<PackageTargetFallback>$(PackageTargetFallback);dnxcore50</PackageTargetFallback>
9+
<RuntimeFrameworkVersion>1.1.1</RuntimeFrameworkVersion>
10+
</PropertyGroup>
11+
12+
<PropertyGroup Condition=" '$(Configuration)' != 'Debug' ">
13+
<Optimize>true</Optimize>
14+
</PropertyGroup>
15+
16+
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
17+
<DebugType>portable</DebugType>
18+
</PropertyGroup>
19+
20+
<ItemGroup>
21+
<ProjectReference Include="..\..\src\ServiceStack.Text\ServiceStack.Text.csproj" Version="1.0.*" />
22+
<ProjectReference Include="..\..\tests\ServiceStack.Text.Tests\ServiceStack.Text.Tests.csproj" Version="1.0.*" />
23+
<PackageReference Include="BenchmarkDotNet" Version="0.10.3" />
24+
</ItemGroup>
25+
26+
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp1.1' ">
27+
<PackageReference Include="Microsoft.Extensions.Primitives" Version="1.1.1" />
28+
</ItemGroup>
29+
30+
</Project>

benchmarks/ServiceStack.Text.Benchmarks/project.json

Lines changed: 0 additions & 23 deletions
This file was deleted.

src/ServiceStack.Text/Common/DeserializeArray.cs

Lines changed: 52 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
using System.Reflection;
1616
using System.Linq;
1717
using System.Threading;
18+
#if NETSTANDARD1_1
19+
using Microsoft.Extensions.Primitives;
20+
#endif
21+
using ServiceStack.Text.Support;
1822

1923
namespace ServiceStack.Text.Common
2024
{
@@ -24,15 +28,23 @@ public static class DeserializeArrayWithElements<TSerializer>
2428
private static Dictionary<Type, ParseArrayOfElementsDelegate> ParseDelegateCache
2529
= new Dictionary<Type, ParseArrayOfElementsDelegate>();
2630

27-
private delegate object ParseArrayOfElementsDelegate(string value, ParseStringDelegate parseFn);
31+
private delegate object ParseArrayOfElementsDelegate(StringSegment value, ParseStringSegmentDelegate parseFn);
2832

2933
public static Func<string, ParseStringDelegate, object> GetParseFn(Type type)
34+
{
35+
var func = GetParseStringSegmentFn(type);
36+
return (s, d) => func(new StringSegment(s), v => d(v.Value));
37+
}
38+
39+
private static readonly Type[] signature = {typeof(StringSegment), typeof(ParseStringSegmentDelegate)};
40+
41+
public static Func<StringSegment, ParseStringSegmentDelegate, object> GetParseStringSegmentFn(Type type)
3042
{
3143
ParseArrayOfElementsDelegate parseFn;
3244
if (ParseDelegateCache.TryGetValue(type, out parseFn)) return parseFn.Invoke;
3345

3446
var genericType = typeof(DeserializeArrayWithElements<,>).MakeGenericType(type, typeof(TSerializer));
35-
var mi = genericType.GetStaticMethod("ParseGenericArray");
47+
var mi = genericType.GetStaticMethod("ParseGenericArray", signature);
3648
parseFn = (ParseArrayOfElementsDelegate)mi.CreateDelegate(typeof(ParseArrayOfElementsDelegate));
3749

3850
Dictionary<Type, ParseArrayOfElementsDelegate> snapshot, newCache;
@@ -54,14 +66,17 @@ public static class DeserializeArrayWithElements<T, TSerializer>
5466
{
5567
private static readonly ITypeSerializer Serializer = JsWriter.GetTypeSerializer<TSerializer>();
5668

57-
public static T[] ParseGenericArray(string value, ParseStringDelegate elementParseFn)
69+
public static T[] ParseGenericArray(string value, ParseStringDelegate elementParseFn) =>
70+
ParseGenericArray(new StringSegment(value), v => elementParseFn(v.Value));
71+
72+
public static T[] ParseGenericArray(StringSegment value, ParseStringSegmentDelegate elementParseFn)
5873
{
59-
if ((value = DeserializeListWithElements<TSerializer>.StripList(value)) == null) return null;
60-
if (value == string.Empty) return new T[0];
74+
if (!(value = DeserializeListWithElements<TSerializer>.StripList(value)).HasValue) return null;
75+
if (value.Length == 0) return new T[0];
6176

62-
if (value[0] == JsWriter.MapStartChar)
77+
if (value.GetChar(0) == JsWriter.MapStartChar)
6378
{
64-
var itemValues = new List<string>();
79+
var itemValues = new List<StringSegment>();
6580
var i = 0;
6681
do
6782
{
@@ -104,25 +119,27 @@ public static T[] ParseGenericArray(string value, ParseStringDelegate elementPar
104119
internal static class DeserializeArray<TSerializer>
105120
where TSerializer : ITypeSerializer
106121
{
107-
private static Dictionary<Type, ParseStringDelegate> ParseDelegateCache = new Dictionary<Type, ParseStringDelegate>();
122+
private static Dictionary<Type, ParseStringSegmentDelegate> ParseDelegateCache = new Dictionary<Type, ParseStringSegmentDelegate>();
108123

109-
public static ParseStringDelegate GetParseFn(Type type)
124+
public static ParseStringDelegate GetParseFn(Type type) => v => GetParseStringSegmentFn(type)(new StringSegment(v));
125+
126+
public static ParseStringSegmentDelegate GetParseStringSegmentFn(Type type)
110127
{
111-
ParseStringDelegate parseFn;
128+
ParseStringSegmentDelegate parseFn;
112129
if (ParseDelegateCache.TryGetValue(type, out parseFn)) return parseFn;
113130

114131
var genericType = typeof(DeserializeArray<,>).MakeGenericType(type, typeof(TSerializer));
115132

116-
var mi = genericType.GetStaticMethod("GetParseFn");
117-
var parseFactoryFn = (Func<ParseStringDelegate>)mi.MakeDelegate(
118-
typeof(Func<ParseStringDelegate>));
133+
var mi = genericType.GetStaticMethod("GetParseStringSegmentFn");
134+
var parseFactoryFn = (Func<ParseStringSegmentDelegate>)mi.MakeDelegate(
135+
typeof(Func<ParseStringSegmentDelegate>));
119136
parseFn = parseFactoryFn();
120137

121-
Dictionary<Type, ParseStringDelegate> snapshot, newCache;
138+
Dictionary<Type, ParseStringSegmentDelegate> snapshot, newCache;
122139
do
123140
{
124141
snapshot = ParseDelegateCache;
125-
newCache = new Dictionary<Type, ParseStringDelegate>(ParseDelegateCache);
142+
newCache = new Dictionary<Type, ParseStringSegmentDelegate>(ParseDelegateCache);
126143
newCache[type] = parseFn;
127144

128145
} while (!ReferenceEquals(
@@ -137,19 +154,20 @@ internal static class DeserializeArray<T, TSerializer>
137154
{
138155
private static readonly ITypeSerializer Serializer = JsWriter.GetTypeSerializer<TSerializer>();
139156

140-
private static readonly ParseStringDelegate CacheFn;
157+
private static readonly ParseStringSegmentDelegate CacheFn;
141158

142159
static DeserializeArray()
143160
{
144-
CacheFn = GetParseFn();
161+
CacheFn = GetParseStringSegmentFn();
145162
}
146163

147-
public static ParseStringDelegate Parse
148-
{
149-
get { return CacheFn; }
150-
}
164+
public static ParseStringDelegate Parse => v => CacheFn(new StringSegment(v));
165+
166+
public static ParseStringSegmentDelegate ParseStringSegment => CacheFn;
151167

152-
public static ParseStringDelegate GetParseFn()
168+
public static ParseStringDelegate GetParseFn() => v => GetParseStringSegmentFn()(new StringSegment(v));
169+
170+
public static ParseStringSegmentDelegate GetParseStringSegmentFn()
153171
{
154172
var type = typeof(T);
155173
if (!type.IsArray)
@@ -158,18 +176,27 @@ public static ParseStringDelegate GetParseFn()
158176
if (type == typeof(string[]))
159177
return ParseStringArray;
160178
if (type == typeof(byte[]))
161-
return ParseByteArray;
179+
return v => ParseByteArray(v.Value);
162180

163181
var elementType = type.GetElementType();
164-
var elementParseFn = Serializer.GetParseFn(elementType);
182+
var elementParseFn = Serializer.GetParseStringSegmentFn(elementType);
165183
if (elementParseFn != null)
166184
{
167-
var parseFn = DeserializeArrayWithElements<TSerializer>.GetParseFn(elementType);
185+
var parseFn = DeserializeArrayWithElements<TSerializer>.GetParseStringSegmentFn(elementType);
168186
return value => parseFn(value, elementParseFn);
169187
}
170188
return null;
171189
}
172190

191+
public static string[] ParseStringArray(StringSegment value)
192+
{
193+
if (!(value = DeserializeListWithElements<TSerializer>.StripList(value)).HasValue) return null;
194+
return value.Length == 0
195+
? TypeConstants.EmptyStringArray
196+
: DeserializeListWithElements<TSerializer>.ParseStringList(value).ToArray();
197+
}
198+
199+
173200
public static string[] ParseStringArray(string value)
174201
{
175202
if ((value = DeserializeListWithElements<TSerializer>.StripList(value)) == null) return null;

0 commit comments

Comments
 (0)