Skip to content

Commit 64e67a8

Browse files
committed
Deserialization: Some more performance improvements.
- Do not get the PhpDataType and switch on that, intead directly switch on the relevant input byte. - In the validator: Increment the token count only in the array and object methods. - Tokenizer: Add AggressiveOptimization hint to GetToken().
1 parent 1a6cb96 commit 64e67a8

File tree

4 files changed

+56
-64
lines changed

4 files changed

+56
-64
lines changed

PhpSerializerNET/Deserialization/PhpDeserializer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ private object MakeArray(Type targetType, PhpToken token) {
374374
}
375375

376376
private object MakeList(Type targetType, PhpToken token) {
377-
for (int i = 0; i < token.Length; i += 2) {
377+
for (int i = 0; i < token.Length * 2; i+=2) {
378378
if (this._tokens[_currentToken+i].Type != PhpDataType.Integer) {
379379
var badToken = this._tokens[_currentToken+i];
380380
throw new DeserializationException(

PhpSerializerNET/Deserialization/PhpToken.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,17 @@ This Source Code Form is subject to the terms of the Mozilla Public
55
**/
66
namespace PhpSerializerNET;
77

8+
#nullable enable
9+
810
/// <summary>
911
/// PHP data token. Holds the type, position (in the input string), length and value.
1012
/// </summary>
1113
internal readonly struct PhpToken {
1214
internal readonly PhpDataType Type;
1315
internal readonly int Position;
1416
internal readonly int Length;
15-
internal readonly string Value;
16-
internal PhpToken(PhpDataType type, int position, string value = "", int length = 0) {
17+
internal readonly string? Value;
18+
internal PhpToken(PhpDataType type, int position, string? value = null, int length = 0) {
1719
this.Type = type;
1820
this.Position = position;
1921
this.Value = value;

PhpSerializerNET/Deserialization/PhpTokenValidator.cs

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,39 +9,32 @@ This Source Code Form is subject to the terms of the Mozilla Public
99

1010
namespace PhpSerializerNET;
1111

12+
#nullable enable
13+
1214
internal ref struct PhpTokenValidator {
1315
private int _position;
14-
private int _tokenCount = 0;
16+
private int _tokenCount;
1517
private readonly ReadOnlySpan<byte> _input;
1618
private readonly int _lastIndex;
1719

1820
internal PhpTokenValidator(in ReadOnlySpan<byte> input) {
21+
this._tokenCount = 1;
1922
this._input = input;
2023
this._position = 0;
2124
this._lastIndex = this._input.Length - 1;
2225
}
2326

2427
internal void GetToken() {
25-
PhpDataType dataType = this._input[this._position++] switch {
26-
(byte)'N' => PhpDataType.Null,
27-
(byte)'b' => PhpDataType.Boolean,
28-
(byte)'s' => PhpDataType.String,
29-
(byte)'i' => PhpDataType.Integer,
30-
(byte)'d' => PhpDataType.Floating,
31-
(byte)'a' => PhpDataType.Array,
32-
(byte)'O' => PhpDataType.Object,
33-
_ => throw new DeserializationException($"Unexpected token '{this.GetCharAt(this._position - 1)}' at position {this._position - 1}.")
34-
};
35-
switch (dataType) {
36-
case PhpDataType.Boolean:
28+
switch ( this._input[this._position++]) {
29+
case (byte)'b':
3730
this.GetCharacter(':');
3831
this.GetBoolean();
3932
this.GetCharacter(';');
4033
break;
41-
case PhpDataType.Null:
34+
case (byte)'N':
4235
this.GetCharacter(';');
4336
break;
44-
case PhpDataType.String:
37+
case (byte)'s':
4538
this.GetCharacter(':');
4639
int length = this.GetLength(PhpDataType.String);
4740
this.GetCharacter(':');
@@ -50,24 +43,27 @@ internal void GetToken() {
5043
this.GetCharacter('"');
5144
this.GetCharacter(';');
5245
break;
53-
case PhpDataType.Integer:
46+
case (byte)'i':
5447
this.GetCharacter(':');
5548
this.GetInteger();
5649
this.GetCharacter(';');
5750
break;
58-
case PhpDataType.Floating:
51+
case (byte)'d':
5952
this.GetCharacter(':');
6053
this.GetFloat();
6154
this.GetCharacter(';');
6255
break;
63-
case PhpDataType.Array:
56+
case (byte)'a':
6457
this.GetArrayToken();
6558
break;
66-
case PhpDataType.Object:
59+
case (byte)'O':
6760
this.GetObjectToken();
6861
break;
62+
default:
63+
throw new DeserializationException(
64+
$"Unexpected token '{this.GetCharAt(this._position - 1)}' at position {this._position - 1}."
65+
);
6966
};
70-
this._tokenCount++;
7167
}
7268

7369
private char GetCharAt(int position) {
@@ -119,6 +115,7 @@ private void GetFloat() {
119115
);
120116
}
121117
}
118+
122119
[MethodImpl(MethodImplOptions.AggressiveInlining)]
123120
private void GetInteger() {
124121
int i = this._position;
@@ -204,15 +201,17 @@ private void GetObjectToken() {
204201
this.GetCharacter('{');
205202
int i = 0;
206203
while (this._input[this._position] != '}') {
204+
this.GetToken();
207205
this.GetToken();
208206
i++;
209-
if (i > propertyCount * 2) {
207+
if (i > propertyCount) {
210208
throw new DeserializationException(
211209
$"Object at position {position} should have {propertyCount} properties, " +
212-
$"but actually has {(i + 1) / 2} or more properties."
210+
$"but actually has {i} or more properties."
213211
);
214212
}
215213
}
214+
this._tokenCount += propertyCount * 2;
216215
this.GetCharacter('}');
217216
}
218217

@@ -223,18 +222,19 @@ private void GetArrayToken() {
223222
int length = this.GetLength(PhpDataType.Array);
224223
this.GetCharacter(':');
225224
this.GetCharacter('{');
226-
int maxTokenCount = length * 2;
227225
int i = 0;
228226
while (this._input[this._position] != '}') {
227+
this.GetToken();
229228
this.GetToken();
230229
i++;
231-
if (i > maxTokenCount) {
230+
if (i > length) {
232231
throw new DeserializationException(
233232
$"Array at position {position} should be of length {length}, " +
234-
$"but actual length is {(i + 1) / 2} or more."
233+
$"but actual length is {i} or more."
235234
);
236235
}
237236
}
237+
this._tokenCount += length * 2;
238238
this.GetCharacter('}');
239239
}
240240

PhpSerializerNET/Deserialization/PhpTokenizer.cs

Lines changed: 26 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ This Source Code Form is subject to the terms of the Mozilla Public
55
**/
66

77
using System;
8-
using System.Diagnostics;
8+
using System.Globalization;
99
using System.Runtime.CompilerServices;
1010
using System.Text;
1111

12+
#nullable enable
13+
1214
namespace PhpSerializerNET;
1315

1416
public ref struct PhpTokenizer {
@@ -26,24 +28,11 @@ private PhpTokenizer(ReadOnlySpan<byte> input, Encoding inputEncoding, Span<PhpT
2628
this._tokenPosition = 0;
2729
}
2830

29-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
30-
private PhpDataType GetDataType() {
31-
return this._input[this._position++] switch {
32-
(byte)'N' => PhpDataType.Null,
33-
(byte)'b' => PhpDataType.Boolean,
34-
(byte)'s' => PhpDataType.String,
35-
(byte)'i' => PhpDataType.Integer,
36-
(byte)'d' => PhpDataType.Floating,
37-
(byte)'a' => PhpDataType.Array,
38-
(byte)'O' => PhpDataType.Object,
39-
_ => throw new UnreachableException(),
40-
};
41-
}
42-
4331
[MethodImpl(MethodImplOptions.AggressiveInlining)]
4432
private void Advance() {
4533
this._position++;
4634
}
35+
4736
[MethodImpl(MethodImplOptions.AggressiveInlining)]
4837
private void Advance(int positons) {
4938
this._position += positons;
@@ -55,50 +44,52 @@ private string GetNumbers() {
5544
while (this._input[this._position] != (byte)';') {
5645
this._position++;
5746
}
58-
return this._inputEncoding.GetString(this._input.Slice(start, this._position-start));
47+
return this._inputEncoding.GetString(this._input.Slice(start, this._position - start));
5948
}
6049

6150
[MethodImpl(MethodImplOptions.AggressiveInlining)]
6251
private int GetLength() {
63-
if (this._input[this._position+1] == ':') {
52+
if (this._input[this._position + 1] == ':') {
6453
return _input[_position++] - 48;
6554
}
6655
int start = this._position;
6756
while (this._input[this._position] != (byte)':') {
6857
this._position++;
6958
}
70-
return int.Parse(this._input.Slice(start, this._position-start));
59+
return int.Parse(this._input.Slice(start, this._position - start), CultureInfo.InvariantCulture);
7160
}
7261

62+
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
7363
internal void GetToken() {
74-
switch (this.GetDataType()) {
75-
case PhpDataType.Boolean:
64+
switch (this._input[this._position++]) {
65+
case (byte)'b':
7666
this.GetBooleanToken();
7767
break;
78-
case PhpDataType.Null:
79-
this.GetNullToken();
68+
case (byte)'N':
69+
this._tokens[this._tokenPosition++] = new PhpToken(PhpDataType.Null, _position - 1);
70+
this.Advance();
8071
break;
81-
case PhpDataType.String:
72+
case (byte)'s':
8273
this.GetStringToken();
8374
break;
84-
case PhpDataType.Integer:
75+
case (byte)'i':
8576
this.GetIntegerToken();
8677
break;
87-
case PhpDataType.Floating:
78+
case (byte)'d':
8879
this.GetFloatingToken();
8980
break;
90-
case PhpDataType.Array:
81+
case (byte)'a':
9182
this.GetArrayToken();
9283
break;
93-
case PhpDataType.Object:
84+
case (byte)'O':
9485
this.GetObjectToken();
9586
break;
9687
};
9788
}
9889

9990
[MethodImpl(MethodImplOptions.AggressiveInlining)]
10091
private void GetNullToken() {
101-
this._tokens[this._tokenPosition++] = new PhpToken(PhpDataType.Null, _position-1);
92+
this._tokens[this._tokenPosition++] = new PhpToken(PhpDataType.Null, _position - 1);
10293
this.Advance();
10394
}
10495

@@ -110,15 +101,14 @@ private void GetBooleanToken() {
110101
_position - 2,
111102
this._input[this._position++] == (byte)'1'
112103
? "1"
113-
: "0",
114-
0
104+
: "0"
115105
);
116106
this.Advance();
117107
}
118108

119109
[MethodImpl(MethodImplOptions.AggressiveInlining)]
120110
private void GetStringToken() {
121-
int position = _position -1;
111+
int position = _position - 1;
122112
this.Advance();
123113
int length = this.GetLength();
124114
this.Advance(2);
@@ -127,15 +117,15 @@ private void GetStringToken() {
127117
position,
128118
_inputEncoding.GetString(this._input.Slice(this._position, length))
129119
);
130-
this.Advance(2+length);
120+
this.Advance(2 + length);
131121
}
132122

133123
[MethodImpl(MethodImplOptions.AggressiveInlining)]
134124
private void GetIntegerToken() {
135125
this.Advance();
136126
this._tokens[this._tokenPosition++] = new PhpToken(
137127
PhpDataType.Integer,
138-
this._position-2,
128+
this._position - 2,
139129
this.GetNumbers()
140130
);
141131
this.Advance();
@@ -160,7 +150,7 @@ private void GetArrayToken() {
160150
this._tokens[this._tokenPosition++] = new PhpToken(
161151
PhpDataType.Array,
162152
position,
163-
"",
153+
null,
164154
length
165155
);
166156
this.Advance(2);
@@ -172,12 +162,12 @@ private void GetArrayToken() {
172162

173163
[MethodImpl(MethodImplOptions.AggressiveInlining)]
174164
private void GetObjectToken() {
175-
int position = _position -1;
165+
int position = _position - 1;
176166
this.Advance();
177167
int classNameLength = this.GetLength();
178168
this.Advance(2);
179169
string className = _inputEncoding.GetString(this._input.Slice(this._position, classNameLength));
180-
this.Advance(2+classNameLength);
170+
this.Advance(2 + classNameLength);
181171
int propertyCount = this.GetLength();
182172
this._tokens[this._tokenPosition++] = new PhpToken(
183173
PhpDataType.Object,

0 commit comments

Comments
 (0)