Skip to content

Commit 98799be

Browse files
author
Petr Sramek
committed
Updated algorithm implementation performance benchmarks
1 parent c68e7dc commit 98799be

File tree

2 files changed

+250
-16
lines changed

2 files changed

+250
-16
lines changed

benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/DecodePerformanceBenchmark.cs

Lines changed: 107 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,38 +2,48 @@
22
{
33
using BenchmarkDotNet.Attributes;
44
using BenchmarkDotNet.Engines;
5+
using DropoutCoder.PolylineAlgorithm.Validation;
56
using System;
67

78
[MemoryDiagnoser]
89
public class DecodePerformanceBenchmark
910
{
1011
private Consumer _consumer = new Consumer();
11-
public static IEnumerable<(int, char[])> Polylines()
12+
public static IEnumerable<(int, string)> Polylines()
1213
{
13-
yield return (1, "mz}lHssngJj`gqSnx~lEcovfTnms{Zdy~qQj_deI".ToCharArray());
14-
yield return (2, "}vwdGjafcRsvjKi}pxUhsrtCngtcAjjgzEdqvtLrscbKj}nr@wetlUc`nq]}_kfCyrfaK~wluUl`u}|@wa{lUmmuap@va{lU~oihCu||bF`|era@wsnnIjny{DxamaScqxza@dklDf{}kb@mtpeCavfzGqhx`Wyzzkm@jm`d@dba~Pppkg@h}pxU|rtnHp|flA|~xaPuykyN}fhv[h}pxUx~p}Ymx`sZih~iB{edwB".ToCharArray());
15-
yield return (3, "}adrJh}}cVazlw@uykyNhaqeE`vfzG_~kY}~`eTsr{~Cwn~aOty_g@thapJvvoqKxt{sStfahDmtvmIfmiqBhjq|HujpgComs{Z}dhdKcidPymnvBqmquE~qrfI`x{lPf|ftGn~}d_@q}saAurjmu@bwr_DxrfaK~{rO~bidPwfduXwlioFlpum@twvfFpmi~VzxcsOqyejYhh|i@pbnr[twvfF_ueUujvbSa_d~ZkcnjZla~f[pmquEebxo[j}nr@xnn|H{gyiKbh{yH`oenn@y{mpIrbd~EmipgH}fuov@hjqtTp|flAttvkFrym_d@|eyCwn~aOfvdNmeawM??{yxdUcidPca{}D_atqGenzcAlra{@trgWhn{aZ??tluqOgu~sH".ToCharArray());
14+
yield return (1, "mz}lHssngJj`gqSnx~lEcovfTnms{Zdy~qQj_deI");
15+
yield return (2, "}vwdGjafcRsvjKi}pxUhsrtCngtcAjjgzEdqvtLrscbKj}nr@wetlUc`nq]}_kfCyrfaK~wluUl`u}|@wa{lUmmuap@va{lU~oihCu||bF`|era@wsnnIjny{DxamaScqxza@dklDf{}kb@mtpeCavfzGqhx`Wyzzkm@jm`d@dba~Pppkg@h}pxU|rtnHp|flA|~xaPuykyN}fhv[h}pxUx~p}Ymx`sZih~iB{edwB");
16+
yield return (3, "}adrJh}}cVazlw@uykyNhaqeE`vfzG_~kY}~`eTsr{~Cwn~aOty_g@thapJvvoqKxt{sStfahDmtvmIfmiqBhjq|HujpgComs{Z}dhdKcidPymnvBqmquE~qrfI`x{lPf|ftGn~}d_@q}saAurjmu@bwr_DxrfaK~{rO~bidPwfduXwlioFlpum@twvfFpmi~VzxcsOqyejYhh|i@pbnr[twvfF_ueUujvbSa_d~ZkcnjZla~f[pmquEebxo[j}nr@xnn|H{gyiKbh{yH`oenn@y{mpIrbd~EmipgH}fuov@hjqtTp|flAttvkFrym_d@|eyCwn~aOfvdNmeawM??{yxdUcidPca{}D_atqGenzcAlra{@trgWhn{aZ??tluqOgu~sH");
1617
}
1718

1819
[Benchmark(Baseline = true)]
1920
[ArgumentsSource(nameof(Polylines))]
20-
public void Decode_V1((int, char[]) arg) => V1.Decode(arg.Item2).Consume(_consumer);
21+
public void Decode_V1((int, string) arg) => For.Loop(1001, () => V1.Decode(arg.Item2).Consume(_consumer));
2122

2223
[Benchmark]
2324
[ArgumentsSource(nameof(Polylines))]
24-
public void Decode_V1_Parallel((int, char[]) arg) => Parallel.For(0, 100, (i) => V1.Decode(arg.Item2).Consume(_consumer));
25+
public void Decode_V2((int, string) arg) => For.Loop(1001, () => V2.Decode(arg.Item2).Consume(_consumer));
2526

2627
[Benchmark]
2728
[ArgumentsSource(nameof(Polylines))]
28-
public void Decode_V2((int, char[]) arg) => V2.Decode(arg.Item2).Consume(_consumer);
29+
public void Decode_V3((int, string) arg) => For.Loop(1001, () => V3.Decode(arg.Item2).Consume(_consumer));
2930

3031
[Benchmark]
3132
[ArgumentsSource(nameof(Polylines))]
32-
public void Decode_V2_Parallel((int, char[]) arg) => Parallel.For(0, 100, (i) => V2.Decode(arg.Item2).Consume(_consumer));
33+
public void Decode_V1_Parallel((int, string) arg) => Parallel.For(0, 1001, new ParallelOptions { MaxDegreeOfParallelism = 10 }, (i) => V1.Decode(arg.Item2).Consume(_consumer));
34+
35+
[Benchmark]
36+
[ArgumentsSource(nameof(Polylines))]
37+
public void Decode_V2_Parallel((int, string) arg) => Parallel.For(0, 1001, new ParallelOptions { MaxDegreeOfParallelism = 10 }, (i) => V2.Decode(arg.Item2).Consume(_consumer));
38+
39+
[Benchmark]
40+
[ArgumentsSource(nameof(Polylines))]
41+
public void Decode_V3_Parallel((int, string) arg) => Parallel.For(0, 1001, new ParallelOptions { MaxDegreeOfParallelism = 10 }, (i) => V3.Decode(arg.Item2).Consume(_consumer));
42+
3343

3444
private class V1
3545
{
36-
public static IEnumerable<(double Latitude, double Longitude)> Decode(char[] polyline)
46+
public static IEnumerable<(double Latitude, double Longitude)> Decode(string polyline)
3747
{
3848
if (polyline is null || polyline.Length == 0)
3949
{
@@ -71,7 +81,7 @@ private class V1
7181
return result;
7282
}
7383

74-
private static bool TryCalculateNext(ref char[] polyline, ref int index, ref int value)
84+
private static bool TryCalculateNext(ref string polyline, ref int index, ref int value)
7585
{
7686
int chunk;
7787
int sum = 0;
@@ -118,7 +128,7 @@ public static bool IsValidLongitude(double longitude)
118128

119129
private class V2
120130
{
121-
public static IEnumerable<(double Latitude, double Longitude)> Decode(char[] polyline)
131+
public static IEnumerable<(double Latitude, double Longitude)> Decode(string polyline)
122132
{
123133
if (polyline is null || polyline.Length == 0)
124134
{
@@ -152,7 +162,7 @@ private class V2
152162
}
153163
}
154164

155-
private static bool TryCalculateNext(ref char[] polyline, ref int offset, ref int value)
165+
private static bool TryCalculateNext(ref string polyline, ref int offset, ref int value)
156166
{
157167
int chunk;
158168
int sum = 0;
@@ -196,5 +206,90 @@ public static bool IsValidLongitude(double longitude)
196206
}
197207
}
198208
}
209+
210+
private class V3
211+
{
212+
public static IEnumerable<(double Latitude, double Longitude)> Decode(string polyline)
213+
{
214+
// Checking null and at least one character
215+
if (polyline == null || polyline.Length == 0)
216+
{
217+
throw new ArgumentException(String.Empty, nameof(polyline));
218+
}
219+
220+
// Initialize local variables
221+
int index = 0;
222+
int latitude = 0;
223+
int longitude = 0;
224+
225+
// Looping through encoded polyline char array
226+
while (index < polyline.Length)
227+
{
228+
// Attempting to calculate next latitude value. If failed exception is thrown
229+
if (!TryCalculateNext(polyline, ref index, ref latitude))
230+
{
231+
throw new InvalidOperationException(String.Empty);
232+
}
233+
234+
// Attempting to calculate next longitude value. If failed exception is thrown
235+
if (!TryCalculateNext(polyline, ref index, ref longitude))
236+
{
237+
throw new InvalidOperationException(String.Empty);
238+
}
239+
240+
var coordinate = (GetCoordinate(latitude), GetCoordinate(longitude));
241+
242+
// Validating decoded coordinate. If not valid exception is thrown
243+
if (!CoordinateValidator.IsValid(coordinate))
244+
{
245+
throw new InvalidOperationException(String.Empty);
246+
}
247+
248+
yield return coordinate;
249+
250+
#region Local functions
251+
252+
bool TryCalculateNext(string polyline, ref int index, ref int value)
253+
{
254+
// Local variable initialization
255+
int chunk;
256+
int sum = 0;
257+
int shifter = 0;
258+
259+
do
260+
{
261+
chunk = polyline[index++] - Constants.ASCII.QuestionMark;
262+
sum |= (chunk & Constants.ASCII.UnitSeparator) << shifter;
263+
shifter += Constants.ShiftLength;
264+
} while (chunk >= Constants.ASCII.Space && index < polyline.Length);
265+
266+
if (index >= polyline.Length && chunk >= Constants.ASCII.Space)
267+
return false;
268+
269+
value += (sum & 1) == 1 ? ~(sum >> 1) : sum >> 1;
270+
271+
return true;
272+
}
273+
274+
double GetCoordinate(int value)
275+
{
276+
return Convert.ToDouble(value) / Constants.Precision;
277+
}
278+
279+
#endregion
280+
}
281+
}
282+
}
283+
284+
internal class For
285+
{
286+
public static void Loop(int count, Action action)
287+
{
288+
for (int i = 0; i < count; i++)
289+
{
290+
action.Invoke();
291+
}
292+
}
293+
}
199294
}
200295
}

benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/EncodePerformanceBenchmark.cs

Lines changed: 143 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
{
33
using BenchmarkDotNet.Attributes;
44
using BenchmarkDotNet.Engines;
5+
using DropoutCoder.PolylineAlgorithm.Validation;
56
using Microsoft.Extensions.ObjectPool;
67
using System.Collections.Generic;
78
using System.Text;
@@ -20,21 +21,29 @@ public class EncodePerformanceBenchmark
2021

2122
[Benchmark(Baseline = true)]
2223
[ArgumentsSource(nameof(Coordinates))]
23-
public void Encode_V1((int, IEnumerable<(double, double)>) arg) => V1.Encode(arg.Item2).Consume(_consumer);
24+
public void Encode_V1((int, IEnumerable<(double, double)>) arg) => For.Loop(1001, () => V1.Encode(arg.Item2).Consume(_consumer));
2425

2526
[Benchmark]
2627
[ArgumentsSource(nameof(Coordinates))]
27-
public void Encode_V1_Parallel((int, IEnumerable<(double, double)>) arg) => Parallel.For(100, 200, (i) => V1.Encode(arg.Item2).Consume(_consumer));
28+
public void Encode_V2((int, IEnumerable<(double, double)>) arg) => For.Loop(1001, () => V2.Encode(arg.Item2).Consume(_consumer));
29+
30+
[Benchmark]
31+
[ArgumentsSource(nameof(Coordinates))]
32+
public void Encode_V3((int, IEnumerable<(double, double)>) arg) => For.Loop(1001, () => V3.Encode(arg.Item2).Consume(_consumer));
2833

2934

3035
[Benchmark]
3136
[ArgumentsSource(nameof(Coordinates))]
32-
public void Encode_V2((int, IEnumerable<(double, double)>) arg) => V2.Encode(arg.Item2).Consume(_consumer);
37+
public void Encode_V1_Parallel((int, IEnumerable<(double, double)>) arg) => Parallel.For(0, 1001, new ParallelOptions { MaxDegreeOfParallelism = 10 }, (i) => V1.Encode(arg.Item2).Consume(_consumer));
3338

39+
[Benchmark]
40+
[ArgumentsSource(nameof(Coordinates))]
41+
public void Encode_V2_Parallel((int, IEnumerable<(double, double)>) arg) => Parallel.For(0, 1001, new ParallelOptions { MaxDegreeOfParallelism = 10 }, (i) => V2.Encode(arg.Item2).Consume(_consumer));
3442

3543
[Benchmark]
3644
[ArgumentsSource(nameof(Coordinates))]
37-
public void Encode_V2_Parallel((int, IEnumerable<(double, double)>) arg) => Parallel.For(100, 200, (i) => V2.Encode(arg.Item2).Consume(_consumer));
45+
public void Encode_V3_Parallel((int, IEnumerable<(double, double)>) arg) => Parallel.For(0, 1001, new ParallelOptions { MaxDegreeOfParallelism = 10 }, (i) => V3.Encode(arg.Item2).Consume(_consumer));
46+
3847

3948
private class V1
4049
{
@@ -218,5 +227,135 @@ public static bool IsValidLongitude(double longitude)
218227
}
219228
}
220229
}
230+
231+
private class V3
232+
{
233+
/// <summary>
234+
/// Method encodes coordinates to polyline encoded representation
235+
/// </summary>
236+
/// <param name="coordinates">Coordinates to encode</param>
237+
/// <returns>Polyline encoded representation</returns>
238+
/// <exception cref="ArgumentException">If coordinates parameter is null or empty enumerable</exception>
239+
/// <exception cref="AggregateException">If one or more coordinate is out of range</exception>
240+
public static string Encode(IEnumerable<(double Latitude, double Longitude)> coordinates)
241+
{
242+
if (coordinates == null || !coordinates.GetEnumerator().MoveNext())
243+
{
244+
throw new ArgumentException();
245+
}
246+
247+
// Validate collection of coordinates
248+
if (!TryValidate(coordinates, out var exceptions))
249+
{
250+
throw new AggregateException(exceptions);
251+
}
252+
253+
// Initializing local variables
254+
int previousLatitude = 0;
255+
int previousLongitude = 0;
256+
var sb = new StringBuilder(coordinates.Count() * 4);
257+
258+
// Looping over coordinates and building encoded result
259+
foreach (var coordinate in coordinates)
260+
{
261+
int latitude = Round(coordinate.Latitude);
262+
int longitude = Round(coordinate.Longitude);
263+
264+
sb.Append(GetSequence(latitude - previousLatitude).ToArray());
265+
sb.Append(GetSequence(longitude - previousLongitude).ToArray());
266+
267+
previousLatitude = latitude;
268+
previousLongitude = longitude;
269+
}
270+
271+
return sb.ToString();
272+
273+
#region Local functions
274+
275+
bool TryValidate(IEnumerable<(double Latitude, double Longitude)> collection, out ICollection<CoordinateValidationException> exceptions)
276+
{
277+
exceptions = new List<CoordinateValidationException>(collection.Count());
278+
279+
foreach (var item in collection)
280+
{
281+
if (!CoordinateValidator.IsValid(item))
282+
{
283+
exceptions.Add(new CoordinateValidationException(item.Latitude, item.Longitude));
284+
}
285+
}
286+
287+
return !exceptions.GetEnumerator().MoveNext();
288+
}
289+
290+
int Round(double value)
291+
{
292+
return (int)Math.Round(value * Constants.Precision);
293+
}
294+
295+
IEnumerable<char> GetSequence(int value)
296+
{
297+
int shifted = value << 1;
298+
if (value < 0)
299+
shifted = ~shifted;
300+
301+
int rem = shifted;
302+
303+
while (rem >= Constants.ASCII.Space)
304+
{
305+
yield return (char)((Constants.ASCII.Space | rem & Constants.ASCII.UnitSeparator) + Constants.ASCII.QuestionMark);
306+
307+
rem >>= Constants.ShiftLength;
308+
}
309+
310+
yield return (char)(rem + Constants.ASCII.QuestionMark);
311+
}
312+
313+
#endregion
314+
}
315+
316+
public static class CoordinateValidator
317+
{
318+
/// <summary>
319+
/// Performs coordinate validation
320+
/// </summary>
321+
/// <param name="coordinate">Coordinate to validate</param>
322+
/// <returns>Returns validation result. If valid then true, otherwise false.</returns>
323+
public static bool IsValid((double Latitude, double Longitude) coordinate)
324+
{
325+
return IsValidLatitude(coordinate.Latitude) && IsValidLongitude(coordinate.Longitude);
326+
}
327+
328+
/// <summary>
329+
/// Performs latitude validation
330+
/// </summary>
331+
/// <param name="latitude">Latitude value to validate</param>
332+
/// <returns>Returns validation result. If valid then true, otherwise false.</returns>
333+
public static bool IsValidLatitude(double latitude)
334+
{
335+
return latitude >= Constants.Coordinate.MinLatitude && latitude <= Constants.Coordinate.MaxLatitude;
336+
}
337+
338+
/// <summary>
339+
/// Performs longitude validation
340+
/// </summary>
341+
/// <param name="longitude">Longitude value to validate</param>
342+
/// <returns>Returns validation result. If valid then true, otherwise false.</returns>
343+
public static bool IsValidLongitude(double longitude)
344+
{
345+
return longitude >= Constants.Coordinate.MinLongitude && longitude <= Constants.Coordinate.MaxLongitude;
346+
}
347+
}
348+
}
349+
350+
internal class For
351+
{
352+
public static void Loop(int count, Action action)
353+
{
354+
for (int i = 0; i < count; i++)
355+
{
356+
action.Invoke();
357+
}
358+
}
359+
}
221360
}
222361
}

0 commit comments

Comments
 (0)