Skip to content

Commit 95a4eaa

Browse files
author
Petr Sramek
committed
just
1 parent 90ee11e commit 95a4eaa

24 files changed

+720
-472
lines changed

benchmarks/PolylineAlgorithm.Benchmarks/PolylineAlgorithm.Benchmarks.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
<IsPackable>false</IsPackable>
1515
</PropertyGroup>
1616

17+
<PropertyGroup>
18+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
19+
</PropertyGroup>
20+
1721
<ItemGroup>
1822
<PackageReference Include="BenchmarkDotNet" Version="0.13.12" />
1923
</ItemGroup>

benchmarks/PolylineAlgorithm.Benchmarks/PolylineDecoderBenchmark.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ namespace PolylineAlgorithm.Benchmarks;
99
using BenchmarkDotNet.Engines;
1010
using PolylineAlgorithm;
1111
using PolylineAlgorithm.Benchmarks.Internal;
12+
using System.Text;
1213

1314
/// <summary>
1415
/// Benchmarks for the <see cref="PolylineDecoder"/> class.
@@ -32,10 +33,12 @@ public class PolylineDecoderBenchmark {
3233
/// </summary>
3334
public char[] CharArray { get; private set; }
3435

36+
public byte[] ByteArray { get; private set; }
37+
3538
/// <summary>
3639
/// Gets the read-only memory representing the encoded polyline.
3740
/// </summary>
38-
public ReadOnlyMemory<char> Memory { get; private set; }
41+
public ReadOnlyMemory<byte> Memory { get; private set; }
3942
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
4043

4144
/// <summary>
@@ -55,7 +58,8 @@ public class PolylineDecoderBenchmark {
5558
public void SetupData() {
5659
StringValue = ValueProvider.GetPolyline(N).ToString();
5760
CharArray = StringValue.ToCharArray();
58-
Memory = StringValue.AsMemory();
61+
ByteArray = Encoding.UTF8.GetBytes(StringValue);
62+
Memory = Encoding.UTF8.GetBytes(StringValue).AsMemory();
5963
}
6064

6165
/// <summary>

benchmarks/PolylineAlgorithm.Benchmarks/Program.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ internal class Program {
1717
/// </summary>
1818
/// <param name="args">The command-line arguments.</param>
1919
static void Main(string[] args) {
20-
Directory.Delete("C:\\temp\\benchmark", true);
20+
//Directory.Delete("C:\\temp\\benchmark", true);
2121

2222
BenchmarkSwitcher
2323
.FromAssembly(typeof(Program).Assembly)
24-
.Run(args, new DebugInProcessConfig());
24+
.Run(args/*, new DebugInProcessConfig()*/);
2525
}
2626
}
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
//
2+
// Copyright © Pete Sramek. All rights reserved.
3+
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
4+
//
5+
6+
namespace PolylineAlgorithm.Benchmarks;
7+
8+
using BenchmarkDotNet.Attributes;
9+
using PolylineAlgorithm;
10+
using System;
11+
using System.Buffers;
12+
using System.Diagnostics;
13+
14+
/// <summary>
15+
/// Benchmarks for the <see cref="PolylineDecoder"/> class.
16+
/// </summary>
17+
[RankColumn]
18+
[ShortRunJob]
19+
public class ReadOnlySequenceSequenceEqualBenchmark {
20+
[Params(1, 10, 100, 500, 1_000, 10_000, 100_000, 1_000_000, 10_000_000)]
21+
public int ArrayLength;
22+
23+
[ParamsAllValues]
24+
public SequenceStructure? Structure;
25+
26+
[ParamsAllValues]
27+
public bool AllowEmptySequence;
28+
29+
/// <summary>
30+
/// Gets the string value representing the encoded polyline.
31+
/// </summary>
32+
public ReadOnlySequence<char> S1 { get; private set; }
33+
34+
/// <summary>
35+
/// Gets the string value representing the encoded polyline.
36+
/// </summary>
37+
public ReadOnlySequence<char> S2 { get; private set; }
38+
39+
//[ParamsAllValues]
40+
//public bool AllowEmpty { get; set; }
41+
42+
/// <summary>
43+
/// Sets up the data for the benchmarks.
44+
/// </summary>
45+
[GlobalSetup]
46+
public void SetupData() {
47+
var a1 = CreateArray(ArrayLength);
48+
49+
S1 = CreateSequence(a1, AllowEmptySequence);
50+
51+
if(Structure == SequenceStructure.EqualSegments) {
52+
S2 = S1;
53+
} else if (Structure == SequenceStructure.EqualContents) {
54+
S2 = CreateSequence(a1, AllowEmptySequence);
55+
} else {
56+
S2 = CreateSequence(CreateArray(ArrayLength), AllowEmptySequence);
57+
}
58+
59+
static ReadOnlySequence<T> CreateSequence<T>(Memory<T> array, bool allowEmpty = false) {
60+
ReadOnlySegment<T> initial = null!;
61+
ReadOnlySegment<T> last = null!;
62+
63+
int consumed = 0;
64+
while (consumed < array.Length) {
65+
var length = Random.Shared.Next(allowEmpty ? 0 : 1, array.Length - consumed + 1);
66+
var slice = length == 0 ? Memory<T>.Empty : array.Slice(consumed, length);
67+
var segment = new ReadOnlySegment<T>(slice);
68+
69+
if (initial is null) {
70+
initial = segment;
71+
last = segment;
72+
} else {
73+
last.Append(segment);
74+
last = segment;
75+
}
76+
77+
consumed += length;
78+
}
79+
80+
var sequence = new ReadOnlySequence<T>(initial, 0, last, last.Memory.Length);
81+
82+
return sequence;
83+
}
84+
85+
static Memory<char> CreateArray(int length) {
86+
Memory<char> array = new char[length];
87+
88+
for (int i = 0; i < length; i++) {
89+
90+
array.Span[i] = Convert.ToChar(Random.Shared.Next(Char.MinValue, Char.MaxValue + 1));
91+
}
92+
93+
return array;
94+
}
95+
}
96+
97+
/// <summary>
98+
/// Benchmarks the decoding of a polyline from a string.
99+
/// </summary>
100+
[Benchmark]
101+
public bool SequenceEqual_SequenceReader_IsNext_V1() {
102+
var left = new SequenceReader<char>(S1);
103+
var right = new SequenceReader<char>(S2);
104+
105+
while (true) {
106+
if(!right.IsNext(left.CurrentSpan)) {
107+
break;
108+
}
109+
110+
left.Advance(left.CurrentSpan.Length);
111+
right.Advance(left.CurrentSpan.Length);
112+
113+
if (left.Remaining == 0) {
114+
return true;
115+
}
116+
}
117+
118+
Debug.Assert(false);
119+
return false;
120+
}
121+
122+
/// <summary>
123+
/// Benchmarks the decoding of a polyline from a string.
124+
/// </summary>
125+
[Benchmark]
126+
public bool SequenceEqual_NerdBank_SpanEnumeration() {
127+
ReadOnlySequence<char>.Enumerator aEnumerator = S1.GetEnumerator();
128+
ReadOnlySequence<char>.Enumerator bEnumerator = S2.GetEnumerator();
129+
130+
ReadOnlySpan<char> aCurrent = default;
131+
ReadOnlySpan<char> bCurrent = default;
132+
133+
while (true) {
134+
bool aNext = TryGetNonEmptySpan(ref aEnumerator, ref aCurrent);
135+
bool bNext = TryGetNonEmptySpan(ref bEnumerator, ref bCurrent);
136+
if (!aNext && !bNext) {
137+
// We've reached the end of both sequences at the same time.
138+
return true;
139+
} else if (aNext != bNext) {
140+
// One ran out of bytes before the other.
141+
// We don't anticipate this, because we already checked the lengths.
142+
throw new InvalidOperationException();
143+
}
144+
145+
int commonLength = Math.Min(aCurrent.Length, bCurrent.Length);
146+
if (!aCurrent[..commonLength].SequenceEqual(bCurrent[..commonLength])) {
147+
Debug.Assert(false);
148+
149+
return false;
150+
}
151+
152+
aCurrent = aCurrent.Slice(commonLength);
153+
bCurrent = bCurrent.Slice(commonLength);
154+
}
155+
156+
static bool TryGetNonEmptySpan(ref ReadOnlySequence<char>.Enumerator enumerator, ref ReadOnlySpan<char> span) {
157+
while (span.Length == 0) {
158+
if (!enumerator.MoveNext()) {
159+
return false;
160+
}
161+
162+
span = enumerator.Current.Span;
163+
}
164+
165+
return true;
166+
}
167+
}
168+
169+
private class ReadOnlySegment<T> : ReadOnlySequenceSegment<T> {
170+
public ReadOnlySegment(ReadOnlyMemory<T> memory, long runningIndex = 0) {
171+
Memory = memory;
172+
RunningIndex = runningIndex;
173+
}
174+
175+
public void Append(ReadOnlyMemory<T> memory) {
176+
Append(new ReadOnlySegment<T>(memory));
177+
}
178+
179+
public void Append(ReadOnlySegment<T> next) {
180+
next.RunningIndex = RunningIndex + Memory.Length;
181+
Next = next;
182+
}
183+
}
184+
185+
public enum SequenceStructure {
186+
EqualSegments = 0,
187+
EqualContents = 1,
188+
}
189+
}

src/PolylineAlgorithm.IO.Pipelines/PolylineDecoder.cs

Lines changed: 53 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55

66
namespace PolylineAlgorithm.IO.Pipelines;
77

8+
using PolylineAlgorithm.Internal;
89
using PolylineAlgorithm.IO.Pipelines.Internal;
910
using System.Buffers;
1011
using System.IO.Pipelines;
12+
using System.Runtime.CompilerServices;
1113
using System.Text;
1214

1315

@@ -34,45 +36,34 @@ public async Task DecodeAsync(PipeReader reader, PipeWriter writer, Cancellation
3436
throw new ArgumentNullException(nameof(writer));
3537
}
3638

37-
ReadResult result;
38-
int latitude = 0;
39-
int longitude = 0;
40-
SequencePosition position;
41-
Memory<byte> temp = new byte[6];
42-
Memory<char> buffer = new char[6];
39+
int latitude;
40+
int longitude;
41+
42+
Memory<byte> buffer = new byte[6];
43+
44+
PolylineCoordinate current = Coordinate.Default;
4345

4446
while (true) {
45-
result = await reader
46-
.ReadAtLeastAsync(6, cancellation)
47+
latitude = await ReadAsync(reader, buffer, cancellation)
4748
.ConfigureAwait(false);
4849

49-
position = Process(result, ref latitude, ref temp, ref buffer);
50-
reader.AdvanceTo(position);
51-
52-
result = await reader
53-
.ReadAtLeastAsync(6, cancellation)
50+
longitude = await ReadAsync(reader, buffer, cancellation)
5451
.ConfigureAwait(false);
5552

56-
position = Process(result, ref longitude, ref temp, ref buffer);
57-
reader.AdvanceTo(position);
53+
current += CoordinateVariance.Create(latitude, longitude);
54+
55+
if (!await Formatter.TryWriteAsync(writer, current, cancellation).ConfigureAwait(false)) {
56+
throw new InvalidOperationException();
57+
}
5858

59-
if (result.IsCompleted) {
59+
if (!reader.TryRead(out var result) || (result.IsCompleted || result.IsCanceled)) {
6060
break;
6161
}
6262
}
6363

6464
await CompleteAsync(reader, writer)
6565
.ConfigureAwait(false);
6666

67-
static SequencePosition Process(ReadResult result, ref int latitude, ref Memory<byte> temp, ref Memory<char> buffer) {
68-
result.Buffer.Slice(0, 6).CopyTo(temp.Span);
69-
Encoding.UTF8.GetChars(temp.Span, buffer.Span);
70-
71-
long consumed = Decode(buffer.Span, ref latitude);
72-
73-
return result.Buffer.GetPosition(consumed);
74-
}
75-
7667
static async Task CompleteAsync(PipeReader reader, PipeWriter writer) {
7768
await reader
7869
.CompleteAsync()
@@ -81,30 +72,48 @@ await writer
8172
.CompleteAsync()
8273
.ConfigureAwait(false);
8374
}
84-
}
8575

86-
internal static int Decode(ref readonly Span<char> buffer, ref int value) {
87-
int position = 0;
88-
int chunk = 0;
89-
int sum = 0;
90-
int shifter = 0;
76+
static async Task<int> ReadAsync(PipeReader reader, Memory<byte> buffer, CancellationToken cancellationToken) {
77+
var result = await reader
78+
.ReadAtLeastAsync(buffer.Length, cancellationToken)
79+
.ConfigureAwait(false);
9180

92-
while (buffer.Length < position) {
93-
chunk = value - Defaults.Algorithm.QuestionMark;
94-
sum |= (chunk & Defaults.Algorithm.UnitSeparator) << shifter;
95-
shifter += Defaults.Algorithm.ShiftLength;
81+
result.Buffer
82+
.CopyTo(buffer.Span);
9683

97-
if (chunk < Defaults.Algorithm.Space) {
98-
break;
99-
}
100-
}
84+
int consumed = VarianceEncoding.Default
85+
.Decode(buffer.Span, out int value);
10186

102-
if (buffer.Length == position && chunk >= Defaults.Algorithm.Space) {
103-
//InvalidPolylineException.Throw(reader.Length - reader.Remaining);
104-
}
87+
var position = result.Buffer.GetPosition(consumed);
10588

106-
value += (sum & 1) == 1 ? ~(sum >> 1) : sum >> 1;
89+
reader.AdvanceTo(position);
10790

108-
return position;
91+
return value;
92+
}
10993
}
94+
95+
//internal static int Decode(ref int value, ref readonly Span<byte> buffer) {
96+
// int position = 0;
97+
// int chunk = 0;
98+
// int sum = 0;
99+
// int shifter = 0;
100+
101+
// while (buffer.Length < position) {
102+
// chunk = value - Defaults.Algorithm.QuestionMark;
103+
// sum |= (chunk & Defaults.Algorithm.UnitSeparator) << shifter;
104+
// shifter += Defaults.Algorithm.ShiftLength;
105+
106+
// if (chunk < Defaults.Algorithm.Space) {
107+
// break;
108+
// }
109+
// }
110+
111+
// if (buffer.Length == position && chunk >= Defaults.Algorithm.Space) {
112+
// //InvalidPolylineException.Throw(reader.Length - reader.Remaining);
113+
// }
114+
115+
// value += (sum & 1) == 1 ? ~(sum >> 1) : sum >> 1;
116+
117+
// return position;
118+
//}
110119
}

0 commit comments

Comments
 (0)