From 70e92996d11abf8d325b2fe25120720e11619c34 Mon Sep 17 00:00:00 2001 From: Petr Sramek Date: Mon, 1 Jan 2024 13:26:41 +0100 Subject: [PATCH 1/2] added benchmarks --- DropoutCoder.PolylineAlgorithm.sln | 18 +- ...tCoder.PolylineAlgorithm.Benchmarks.csproj | 18 ++ .../PolylineEncodingBenchmark.cs | 40 +++ .../Program.cs | 13 + .../Constants.cs | 82 +++++++ .../DecodePerformanceBenchmark.cs | 200 +++++++++++++++ ...Algorithm.Implementation.Benchmarks.csproj | 19 ++ .../EncodePerformanceBenchmark.cs | 231 ++++++++++++++++++ .../Program.cs | 15 ++ 9 files changed, 635 insertions(+), 1 deletion(-) create mode 100644 benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks.csproj create mode 100644 benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks/PolylineEncodingBenchmark.cs create mode 100644 benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks/Program.cs create mode 100644 benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/Constants.cs create mode 100644 benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/DecodePerformanceBenchmark.cs create mode 100644 benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks.csproj create mode 100644 benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/EncodePerformanceBenchmark.cs create mode 100644 benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/Program.cs diff --git a/DropoutCoder.PolylineAlgorithm.sln b/DropoutCoder.PolylineAlgorithm.sln index d4eca94..a767308 100644 --- a/DropoutCoder.PolylineAlgorithm.sln +++ b/DropoutCoder.PolylineAlgorithm.sln @@ -10,13 +10,19 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{576FEFFC EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "nuget", "nuget", "{7F9807A1-01FF-40C3-9342-3F3F35D632DA}" ProjectSection(SolutionItems) = preProject - .\nuget\DropoutCoder.PolylineAlgorithm.nuspec = .\nuget\DropoutCoder.PolylineAlgorithm.nuspec + nuget\DropoutCoder.PolylineAlgorithm.nuspec = nuget\DropoutCoder.PolylineAlgorithm.nuspec EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DropoutCoder.PolylineAlgorithm.Tests", "tests\DropoutCoder.PolylineAlgorithm.Tests.csproj", "{30324A08-AA42-425D-87DA-8F9C6AF60454}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{C13E31F9-B8EF-4915-A1C8-BC33F6431EB9}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{33C03F16-4313-4579-87E6-65892AF21D7D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DropoutCoder.PolylineAlgorithm.Benchmarks", "benchmarks\DropoutCoder.PolylineAlgorithm.Benchmarks\DropoutCoder.PolylineAlgorithm.Benchmarks.csproj", "{9C7CBAD5-415B-4589-86E1-01C849F9C56C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks", "benchmarks\DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks\DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks.csproj", "{D9F175EA-6F4C-4BFF-AB1D-5F45324B6C1B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -31,6 +37,14 @@ Global {30324A08-AA42-425D-87DA-8F9C6AF60454}.Debug|Any CPU.Build.0 = Debug|Any CPU {30324A08-AA42-425D-87DA-8F9C6AF60454}.Release|Any CPU.ActiveCfg = Release|Any CPU {30324A08-AA42-425D-87DA-8F9C6AF60454}.Release|Any CPU.Build.0 = Release|Any CPU + {9C7CBAD5-415B-4589-86E1-01C849F9C56C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9C7CBAD5-415B-4589-86E1-01C849F9C56C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9C7CBAD5-415B-4589-86E1-01C849F9C56C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9C7CBAD5-415B-4589-86E1-01C849F9C56C}.Release|Any CPU.Build.0 = Release|Any CPU + {D9F175EA-6F4C-4BFF-AB1D-5F45324B6C1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D9F175EA-6F4C-4BFF-AB1D-5F45324B6C1B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D9F175EA-6F4C-4BFF-AB1D-5F45324B6C1B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D9F175EA-6F4C-4BFF-AB1D-5F45324B6C1B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -38,6 +52,8 @@ Global GlobalSection(NestedProjects) = preSolution {882322A6-E758-4662-8D1C-7C555C8FC3F2} = {51C886AF-D610-48A4-9D73-2DEB38742801} {30324A08-AA42-425D-87DA-8F9C6AF60454} = {576FEFFC-B624-40C3-A8AF-4E5233802EA0} + {9C7CBAD5-415B-4589-86E1-01C849F9C56C} = {33C03F16-4313-4579-87E6-65892AF21D7D} + {D9F175EA-6F4C-4BFF-AB1D-5F45324B6C1B} = {33C03F16-4313-4579-87E6-65892AF21D7D} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {93A268DC-0947-4FBB-B495-DDAD4B013D82} diff --git a/benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks.csproj b/benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks.csproj new file mode 100644 index 0000000..2d5b8cc --- /dev/null +++ b/benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks.csproj @@ -0,0 +1,18 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + diff --git a/benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks/PolylineEncodingBenchmark.cs b/benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks/PolylineEncodingBenchmark.cs new file mode 100644 index 0000000..798fd3a --- /dev/null +++ b/benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks/PolylineEncodingBenchmark.cs @@ -0,0 +1,40 @@ +namespace DropoutCoder.PolylineAlgorithm.Benchmarks +{ + using BenchmarkDotNet.Attributes; + using BenchmarkDotNet.Engines; + using DropoutCoder.PolylineAlgorithm.Encoding; + + [MemoryDiagnoser] + [MarkdownExporter] + public class PolylineEncodingBenchmark + { + private Consumer _consumer = new Consumer(); + + [Params(10_000, 100_000, 1_000_000, Priority = 2)] + public int N; + + public IEnumerable<(double, double)> Coordinates; + + public PolylineEncoding Encoding { get; private set; } + + public string Polyline; + + [GlobalSetup] + public void Setup() + { + Encoding = new PolylineEncoding(); + Coordinates = new[] { (42.88895, -100.30630), (44.91513, 19.22495), (20.40244, 7.97495), (-15.52130, -63.74380), (-78.95116, -72.18130), (38.63072, 88.13120), (60.81071, 151.41245), (-58.20769, -173.43130), (59.40939, 83.91245), (-58.20769, 61.41245), (-20.86278, -119.99380), (34.10374, -150.93130), (-71.15367, 31.88120), (-72.04138, -153.74380), (-49.99635, -107.33755), (76.12614, 135.94370), (70.05664, 41.72495), (63.43879, -77.80630), (13.68456, -90.46255), (-75.90519, -7.49380), (74.71112, -127.02505), (-66.61109, 17.81870), (-49.08384, 37.50620) }; + Polyline = "}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"; + } + + [Benchmark] + public void Decode() => Encoding + .Decode(Polyline) + .Consume(_consumer); + + [Benchmark] + public void Encode() => Encoding + .Encode(Coordinates) + .Consume(_consumer); + } +} diff --git a/benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks/Program.cs b/benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks/Program.cs new file mode 100644 index 0000000..d6e3655 --- /dev/null +++ b/benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks/Program.cs @@ -0,0 +1,13 @@ +namespace DropoutCoder.PolylineAlgorithm.Benchmarks +{ + using BenchmarkDotNet.Running; + + internal class Program + { + static void Main(string[] args) + { + BenchmarkRunner + .Run(); + } + } +} diff --git a/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/Constants.cs b/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/Constants.cs new file mode 100644 index 0000000..9854d04 --- /dev/null +++ b/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/Constants.cs @@ -0,0 +1,82 @@ +// +// Copyright (c) Petr Šrámek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks +{ + /// + /// Defines global constant values + /// + internal static class Constants + { + #region Constants + + /// + /// Defines the coordinate precision + /// + public const double Precision = 1E5; + + /// + /// Defines the shift length + /// + public const int ShiftLength = 5; + + #endregion + + /// + /// Defines ASCII characters constant values + /// + internal static class ASCII + { + #region Constants + + /// + /// Defines the ASCII Question Mark + /// + public const int QuestionMark = 63; + + /// + /// Defines the ASCII Space + /// + public const int Space = 32; + + /// + /// Defines the ASCII Unit Separator + /// + public const int UnitSeparator = 31; + + #endregion + } + + /// + /// Defines coordinates constant values + /// + internal static class Coordinate + { + #region Constants + + /// + /// Defines the maximum value for latitude + /// + public const int MaxLatitude = 90; + + /// + /// Defines the maximum value for longitude + /// + public const int MaxLongitude = 180; + + /// + /// Defines the minimum value for latitude + /// + public const int MinLatitude = -MaxLatitude; + + /// + /// Defines the minimum value for longitude + /// + public const int MinLongitude = -MaxLongitude; + + #endregion + } + } +} diff --git a/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/DecodePerformanceBenchmark.cs b/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/DecodePerformanceBenchmark.cs new file mode 100644 index 0000000..fd4ca47 --- /dev/null +++ b/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/DecodePerformanceBenchmark.cs @@ -0,0 +1,200 @@ +namespace DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks +{ + using BenchmarkDotNet.Attributes; + using BenchmarkDotNet.Engines; + + [MemoryDiagnoser] + public class DecodePerformanceBenchmark + { + private Consumer _consumer = new Consumer(); + + public static IEnumerable<(int, char[])> Polylines() + { + yield return (1, "mz}lHssngJj`gqSnx~lEcovfTnms{Zdy~qQj_deI".ToCharArray()); + 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()); + 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()); + } + + [Benchmark(Baseline = true)] + [ArgumentsSource(nameof(Polylines))] + public void Decode_V1((int, char[]) arg) => V1.Decode(arg.Item2).Consume(_consumer); + + [Benchmark] + [ArgumentsSource(nameof(Polylines))] + public void Decode_V2((int, char[]) arg) => V2.Decode(arg.Item2).Consume(_consumer); + + [Benchmark] + [ArgumentsSource(nameof(Polylines))] + public void Decode_V1_Parallel((int, char[]) arg) => Parallel.For(100, 200, (i) => V1.Decode(arg.Item2).Consume(_consumer)); + + [Benchmark] + [ArgumentsSource(nameof(Polylines))] + public void Decode_V2_Parallel((int, char[]) arg) => Parallel.For(100, 200, (i) => V2.Decode(arg.Item2).Consume(_consumer)); + + private class V1 + { + public static IEnumerable<(double Latitude, double Longitude)> Decode(char[] polyline) + { + if (polyline is null || polyline.Length == 0) + { + throw new ArgumentException(nameof(polyline)); + } + + int index = 0; + int latitude = 0; + int longitude = 0; + + var result = new List<(double Latitude, double Longitude)>(); + + while (index < polyline.Length) + { + if (!TryCalculateNext(ref polyline, ref index, ref latitude)) + { + throw new InvalidOperationException(); + } + + if (!TryCalculateNext(ref polyline, ref index, ref longitude)) + { + throw new InvalidOperationException(); + } + + var coordinate = (GetDoubleRepresentation(latitude), GetDoubleRepresentation(longitude)); + + if (!CoordinateValidator.IsValid(coordinate)) + { + throw new InvalidOperationException(); + } + + result.Add(coordinate); + } + + return result; + } + + private static bool TryCalculateNext(ref char[] polyline, ref int index, ref int value) + { + int chunk; + int sum = 0; + int shifter = 0; + + do + { + chunk = polyline[index++] - Constants.ASCII.QuestionMark; + sum |= (chunk & Constants.ASCII.UnitSeparator) << shifter; + shifter += Constants.ShiftLength; + } while (chunk >= Constants.ASCII.Space && index < polyline.Length); + + if (index >= polyline.Length && chunk >= Constants.ASCII.Space) + return false; + + value += (sum & 1) == 1 ? ~(sum >> 1) : sum >> 1; + + return true; + } + + private static double GetDoubleRepresentation(int value) + { + return Convert.ToDouble(value) / Constants.Precision; + } + + public static class CoordinateValidator + { + public static bool IsValid((double Latitude, double Longitude) coordinate) + { + return IsValidLatitude(coordinate.Latitude) && IsValidLongitude(coordinate.Longitude); + } + + public static bool IsValidLatitude(double latitude) + { + return latitude >= Constants.Coordinate.MinLatitude && latitude <= Constants.Coordinate.MaxLatitude; + } + + public static bool IsValidLongitude(double longitude) + { + return longitude >= Constants.Coordinate.MinLongitude && longitude <= Constants.Coordinate.MaxLongitude; + } + } + } + + private class V2 + { + public static IEnumerable<(double Latitude, double Longitude)> Decode(char[] polyline) + { + if (polyline is null || polyline.Length == 0) + { + throw new ArgumentException(nameof(polyline)); + } + + int index = 0; + int latitude = 0; + int longitude = 0; + + while (index < polyline.Length) + { + if (!TryCalculateNext(ref polyline, ref index, ref latitude)) + { + throw new InvalidOperationException(); + } + + if (!TryCalculateNext(ref polyline, ref index, ref longitude)) + { + throw new InvalidOperationException(); + } + + var coordinate = (GetDoubleRepresentation(latitude), GetDoubleRepresentation(longitude)); + + if (!CoordinateValidator.IsValid(coordinate)) + { + throw new InvalidOperationException(); + } + + yield return (latitude, longitude); + } + } + + private static bool TryCalculateNext(ref char[] polyline, ref int index, ref int value) + { + int chunk; + int sum = 0; + int shifter = 0; + + do + { + chunk = polyline[index++] - Constants.ASCII.QuestionMark; + sum |= (chunk & Constants.ASCII.UnitSeparator) << shifter; + shifter += Constants.ShiftLength; + } while (chunk >= Constants.ASCII.Space && index < polyline.Length); + + if (index >= polyline.Length && chunk >= Constants.ASCII.Space) + return false; + + value += (sum & 1) == 1 ? ~(sum >> 1) : sum >> 1; + + return true; + } + + private static double GetDoubleRepresentation(int value) + { + return Convert.ToDouble(value) / Constants.Precision; + } + + public static class CoordinateValidator + { + public static bool IsValid((double Latitude, double Longitude) coordinate) + { + return IsValidLatitude(coordinate.Latitude) && IsValidLongitude(coordinate.Longitude); + } + + public static bool IsValidLatitude(double latitude) + { + return latitude >= Constants.Coordinate.MinLatitude && latitude <= Constants.Coordinate.MaxLatitude; + } + + public static bool IsValidLongitude(double longitude) + { + return longitude >= Constants.Coordinate.MinLongitude && longitude <= Constants.Coordinate.MaxLongitude; + } + } + } + } +} diff --git a/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks.csproj b/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks.csproj new file mode 100644 index 0000000..d799ba3 --- /dev/null +++ b/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks.csproj @@ -0,0 +1,19 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + + diff --git a/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/EncodePerformanceBenchmark.cs b/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/EncodePerformanceBenchmark.cs new file mode 100644 index 0000000..2777ea3 --- /dev/null +++ b/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/EncodePerformanceBenchmark.cs @@ -0,0 +1,231 @@ +namespace DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks +{ + using BenchmarkDotNet.Attributes; + using BenchmarkDotNet.Engines; + using Microsoft.Extensions.ObjectPool; + using System.Collections.Generic; + using System.Text; + + [MemoryDiagnoser] + public class EncodePerformanceBenchmark + { + private Consumer _consumer = new Consumer(); + + public IEnumerable<(int, IEnumerable<(double, double)>)> Coordinates() + { + yield return (1, new[] { (49.47383, 59.06250), (-58.37407, 25.31250), (52.99363, -120.93750), (-44.49024, -174.37500) }); + yield return (2, new[] { (42.88895, -100.30630), (44.91513, 19.22495), (20.40244, 7.97495), (-15.52130, -63.74380), (-78.95116, -72.18130), (38.63072, 88.13120), (60.81071, 151.41245), (-58.20769, -173.43130), (59.40939, 83.91245), (-58.20769, 61.41245), (-20.86278, -119.99380), (34.10374, -150.93130), (-71.15367, 31.88120), (-72.04138, -153.74380), (-49.99635, -107.33755), (76.12614, 135.94370), (70.05664, 41.72495), (63.43879, -77.80630), (13.68456, -90.46255), (-75.90519, -7.49380), (74.71112, -127.02505), (-66.61109, 17.81870), (-49.08384, 37.50620) }); + yield return (3, new[] { (60.81071, -121.40005), (70.05664, -38.43130), (37.52379, -84.83755), (41.85003, 26.25620), (68.04709, 110.63120), (61.48922, 50.16245), (-4.46018, -58.11880), (-32.16061, -3.27505), (-50.89185, -55.30630), (-28.52070, 90.94370), (35.26009, 93.75620), (54.83622, 128.91245), (1.16022, 37.50620), (-44.26398, -131.24380), (-33.34325, 154.22495), (-59.65879, 90.94370), (-62.38215, 0.94370), (72.32117, 40.31870), (64.66910, 2.34995), (-61.04971, -84.83755), (77.10238, -91.86880), (-72.88859, -129.83755), (-69.24987, -24.36880), (77.41254, 119.06870), (-70.69409, 83.91245), (78.85650, 75.47495), (26.83989, 140.16245), (-24.75069, -108.74380), (30.53968, -145.30630), (79.12503, 145.78745), (-34.51006, 133.13120), (-73.29753, -60.93130), (-74.08712, 23.44370), (-76.57404, 100.78745), (-76.57404, 100.78745), (39.72082, 103.59995), (70.99412, 148.59995), (82.27591, 138.75620), (78.29964, -3.27505), (78.29964, -3.27505), (-8.65039, 47.34995) }); + } + + [Benchmark(Baseline = true)] + [ArgumentsSource(nameof(Coordinates))] + public void Encode_V1((int, IEnumerable<(double, double)>) arg) => V1.Encode(arg.Item2).Consume(_consumer); + + [Benchmark] + [ArgumentsSource(nameof(Coordinates))] + public void Encode_V1_Parallel((int, IEnumerable<(double, double)>) arg) => Parallel.For(100, 200, (i) => V1.Encode(arg.Item2).Consume(_consumer)); + + + [Benchmark] + [ArgumentsSource(nameof(Coordinates))] + public void Encode_V2((int, IEnumerable<(double, double)>) arg) => V2.Encode(arg.Item2).Consume(_consumer); + + + [Benchmark] + [ArgumentsSource(nameof(Coordinates))] + public void Encode_V2_Parallel((int, IEnumerable<(double, double)>) arg) => Parallel.For(100, 200, (i) => V2.Encode(arg.Item2).Consume(_consumer)); + + [Benchmark] + [ArgumentsSource(nameof(Coordinates))] + public void Encode_V3((int, IEnumerable<(double, double)>) arg) => V3.Encode(arg.Item2).Consume(_consumer); + + + [Benchmark] + [ArgumentsSource(nameof(Coordinates))] + public void Encode_V3_Parallel((int, IEnumerable<(double, double)>) arg) => Parallel.For(100, 200, (i) => V3.Encode(arg.Item2).Consume(_consumer)); + + private class V1 + { + public static string Encode(IEnumerable<(double Latitude, double Longitude)> coordinates) + { + if (coordinates is null || !coordinates.Any()) + { + throw new ArgumentException(nameof(coordinates)); + } + + EnsureCoordinates(coordinates); + + int lastLatitude = 0; + int lastLongitude = 0; + var sb = new StringBuilder(); + + foreach (var coordinate in coordinates) + { + int latitude = GetIntegerRepresentation(coordinate.Latitude); + int longitude = GetIntegerRepresentation(coordinate.Longitude); + + sb.Append(GetEncodedCharacters(latitude - lastLatitude).ToArray()); + sb.Append(GetEncodedCharacters(longitude - lastLongitude).ToArray()); + + lastLatitude = latitude; + lastLongitude = longitude; + } + + return sb.ToString(); + } + + private static void EnsureCoordinates(IEnumerable<(double Latitude, double Longitude)> coordinates) + { + var invalidCoordinates = coordinates + .Where(c => !CoordinateValidator.IsValid(c)); + + if (invalidCoordinates.Any()) + { + throw new AggregateException( + invalidCoordinates + .Select(c => + new ArgumentOutOfRangeException() + ) + ); + } + } + + private static IEnumerable GetEncodedCharacters(int value) + { + int shifted = value << 1; + if (value < 0) + shifted = ~shifted; + + int rem = shifted; + + while (rem >= Constants.ASCII.Space) + { + yield return (char)((Constants.ASCII.Space | rem & Constants.ASCII.UnitSeparator) + Constants.ASCII.QuestionMark); + + rem >>= Constants.ShiftLength; + } + + yield return (char)(rem + Constants.ASCII.QuestionMark); + } + + private static int GetIntegerRepresentation(double value) + { + return (int)Math.Round(value * Constants.Precision); + } + + public static class CoordinateValidator + { + public static bool IsValid((double Latitude, double Longitude) coordinate) + { + return IsValidLatitude(coordinate.Latitude) && IsValidLongitude(coordinate.Longitude); + } + + public static bool IsValidLatitude(double latitude) + { + return latitude >= Constants.Coordinate.MinLatitude && latitude <= Constants.Coordinate.MaxLatitude; + } + + public static bool IsValidLongitude(double longitude) + { + return longitude >= Constants.Coordinate.MinLongitude && longitude <= Constants.Coordinate.MaxLongitude; + } + } + } + + private class V2 + { + private static readonly ObjectPool _pool = new DefaultObjectPoolProvider().CreateStringBuilderPool(5, int.MaxValue); + + public static string Encode(IEnumerable<(double Latitude, double Longitude)> coordinates) + { + if (coordinates is null || !coordinates.Any()) + { + throw new ArgumentException(nameof(coordinates)); + } + + EnsureCoordinates(coordinates); + + int lastLatitude = 0; + int lastLongitude = 0; + + var sb = _pool.Get(); + + foreach (var coordinate in coordinates) + { + int latitude = GetIntegerRepresentation(coordinate.Latitude); + int longitude = GetIntegerRepresentation(coordinate.Longitude); + + sb.Append(GetEncodedCharacters(latitude - lastLatitude).ToArray()); + sb.Append(GetEncodedCharacters(longitude - lastLongitude).ToArray()); + + lastLatitude = latitude; + lastLongitude = longitude; + } + + var result = sb.ToString(); + + _pool.Return(sb); + + return result; + } + + private static void EnsureCoordinates(IEnumerable<(double Latitude, double Longitude)> coordinates) + { + var invalidCoordinates = coordinates + .Where(c => !CoordinateValidator.IsValid(c)); + + if (invalidCoordinates.Any()) + { + throw new AggregateException( + invalidCoordinates + .Select(c => + new ArgumentOutOfRangeException() + ) + ); + } + } + + private static IEnumerable GetEncodedCharacters(int value) + { + int shifted = value << 1; + if (value < 0) + shifted = ~shifted; + + int rem = shifted; + + while (rem >= Constants.ASCII.Space) + { + yield return (char)((Constants.ASCII.Space | rem & Constants.ASCII.UnitSeparator) + Constants.ASCII.QuestionMark); + + rem >>= Constants.ShiftLength; + } + + yield return (char)(rem + Constants.ASCII.QuestionMark); + } + + private static int GetIntegerRepresentation(double value) + { + return (int)Math.Round(value * Constants.Precision); + } + + public static class CoordinateValidator + { + public static bool IsValid((double Latitude, double Longitude) coordinate) + { + return IsValidLatitude(coordinate.Latitude) && IsValidLongitude(coordinate.Longitude); + } + + public static bool IsValidLatitude(double latitude) + { + return latitude >= Constants.Coordinate.MinLatitude && latitude <= Constants.Coordinate.MaxLatitude; + } + + public static bool IsValidLongitude(double longitude) + { + return longitude >= Constants.Coordinate.MinLongitude && longitude <= Constants.Coordinate.MaxLongitude; + } + } + } + } +} diff --git a/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/Program.cs b/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/Program.cs new file mode 100644 index 0000000..1a591c1 --- /dev/null +++ b/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/Program.cs @@ -0,0 +1,15 @@ +namespace DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks +{ + using BenchmarkDotNet.Running; + + internal class Program + { + static void Main(string[] args) + { + BenchmarkRunner + .Run(); + BenchmarkRunner + .Run(); + } + } +} From 78fec5a332f7e9bb31fedb02f2d77af2f03d2e0b Mon Sep 17 00:00:00 2001 From: Petr Sramek Date: Mon, 1 Jan 2024 13:37:23 +0100 Subject: [PATCH 2/2] removed not implemented V3 encode implementation benchmark --- .../EncodePerformanceBenchmark.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/EncodePerformanceBenchmark.cs b/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/EncodePerformanceBenchmark.cs index 2777ea3..dff6e19 100644 --- a/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/EncodePerformanceBenchmark.cs +++ b/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/EncodePerformanceBenchmark.cs @@ -36,15 +36,6 @@ public class EncodePerformanceBenchmark [ArgumentsSource(nameof(Coordinates))] public void Encode_V2_Parallel((int, IEnumerable<(double, double)>) arg) => Parallel.For(100, 200, (i) => V2.Encode(arg.Item2).Consume(_consumer)); - [Benchmark] - [ArgumentsSource(nameof(Coordinates))] - public void Encode_V3((int, IEnumerable<(double, double)>) arg) => V3.Encode(arg.Item2).Consume(_consumer); - - - [Benchmark] - [ArgumentsSource(nameof(Coordinates))] - public void Encode_V3_Parallel((int, IEnumerable<(double, double)>) arg) => Parallel.For(100, 200, (i) => V3.Encode(arg.Item2).Consume(_consumer)); - private class V1 { public static string Encode(IEnumerable<(double Latitude, double Longitude)> coordinates)