Skip to content

Commit 97f383b

Browse files
author
Petr Sramek
committed
optimization, updates, polishing
1 parent d11a88d commit 97f383b

File tree

9 files changed

+150
-36
lines changed

9 files changed

+150
-36
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
namespace PolylineAlgorithm.Benchmarks.Internal
2+
{
3+
using System.Collections.Concurrent;
4+
5+
public static class ValueProvider
6+
{
7+
private static ConcurrentDictionary<int, CoordinatePair> _cache = new();
8+
private static PolylineEncoder _encoder = new();
9+
10+
public static IEnumerable<Coordinate> GetCoordinates(int count) {
11+
var entry = GetCaheEntry(count);
12+
13+
return entry.Coordinates;
14+
}
15+
16+
public static Polyline GetPolyline(int count) {
17+
var entry = GetCaheEntry(count);
18+
19+
return entry.Polyline;
20+
}
21+
22+
private static CoordinatePair GetCaheEntry(int count) {
23+
if (_cache.TryGetValue(count, out var entry)) {
24+
return entry;
25+
}
26+
27+
var enumeration = Enumerable
28+
.Range(0, count)
29+
.Select(i => new Coordinate(RandomLatitude(), RandomLongitude()));
30+
31+
entry = _cache.GetOrAdd(count, _ => new CoordinatePair(enumeration, _encoder.Encode(enumeration)));
32+
33+
return entry;
34+
}
35+
36+
private static double RandomLongitude() {
37+
return Random.Shared.Next(-180, 180) + Random.Shared.NextDouble();
38+
}
39+
40+
private static double RandomLatitude() {
41+
return Random.Shared.Next(-90, 90) + Random.Shared.NextDouble();
42+
}
43+
44+
private readonly struct CoordinatePair {
45+
public CoordinatePair(IEnumerable<Coordinate> coordinates, Polyline polyline) {
46+
Coordinates = coordinates;
47+
Polyline = polyline;
48+
}
49+
50+
public IEnumerable<Coordinate> Coordinates { get; }
51+
public Polyline Polyline { get; }
52+
}
53+
}
54+
}

benchmarks/PolylineAlgorithm.Benchmarks/PolylineDecoderBenchmark.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ namespace PolylineAlgorithm.Benchmarks;
88
using BenchmarkDotNet.Attributes;
99
using BenchmarkDotNet.Engines;
1010
using PolylineAlgorithm;
11+
using PolylineAlgorithm.Benchmarks.Internal;
1112

1213
/// <summary>
1314
/// Benchmarks for the <see cref="PolylineDecoder"/> class.
@@ -16,6 +17,9 @@ namespace PolylineAlgorithm.Benchmarks;
1617
public class PolylineDecoderBenchmark {
1718
private readonly Consumer _consumer = new();
1819

20+
[Params(10, 100, 1_000, 10_000, 100_000)]
21+
public int N;
22+
1923
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
2024
/// <summary>
2125
/// Gets the string value representing the encoded polyline.
@@ -43,7 +47,7 @@ public class PolylineDecoderBenchmark {
4347
/// </summary>
4448
[GlobalSetup]
4549
public void SetupData() {
46-
StringValue = @"sq_|Fptm{UrbxoHinoiEuhmbHctjfc@`rocCt|nfJgiauLvc_uIh`pg_@h_fkVgctZoyqfW{rxgQhhymFpuvvDwqcfTlqbcBiegdTwf{uNngc|z@{vhlDnsi_B~nz`O}d_hNp{n`E}kcoKm`bCiul~\jhg|Hv{qoWshzh\{lf_G`pzMqm|bLzswoQbhcm`@b`}cIgignPgxntR|}vo^f~|}L}|_jBa|ujWuxkjQfj|w[wsz`Pmdb{XohnwA`srxWjitms@c_~`Tava_l@jxxcF`d|zHcpnaMnd{kYzccwPxzpiHfsxlL}jjjQqvdbIikyvj@cjdqTh`zoDzqleHnfmik@tbnoB_t}gFkzx_Ct_eiP`wxrBcd_|w@zlhfPtlxgBkwyyZn|~tFlpj|HxqiwUnddkFoo|nTee}dSfkcg`@py`uQiguom@zkkpEfcgkAntuuDzl~il@ir_gCrd~nI_ryeC_qmmMl_kgCz`qgFzkejBmlchYyp`hZ`_cuYzuc}Onqz}Ew~Gcsmj@lp{Hqj_gz@ne{pJnny~]g{tuNxbno[lfq_Lqhwjt@qn|cCuxnMyivuIh{|tR`ylsQlqbfO}rf`LxghfBg~{nAv`gdNbjh_Fglt|NfxwyBowwhW{bdtNdbkqe@rxtwSy{_fX{btm@va`_LkhwuUyqgzK`xdnKgbwsFigt_Mofdn\h|x[ccoPtbpvNz}skb@pl~xEqascV_wsx[`f_z]zewFs`zjAhturWxayhJqmfaAjmhhHxwuwF_aru@ojemVq|beu@kkucBdmryTevflDcbmdAnp|dHfpbd^io}z@e~}dFzcybQ}`hxOyt|bNl}blLnuspKk|t|k@itjfHt}}aVyzmcF_rgmLct}x@bazdq@loajBxygb@f}krVgnuqOcrx_Daqvp_@ew}yUn}kpU|uwnItashEpe_aHusi{Fsu}_Ewfhv[dzhzKxh_qXucxfXmynkGxuqbW|ppgi@vrsq@clryZk`bt^spkyP";
50+
StringValue = ValueProvider.GetPolyline(N).ToString();
4751
CharArray = StringValue.ToCharArray();
4852
Memory = StringValue.AsMemory();
4953
}

benchmarks/PolylineAlgorithm.Benchmarks/PolylineEncoderBenchmark.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@ namespace PolylineAlgorithm.Benchmarks;
77

88
using BenchmarkDotNet.Attributes;
99
using PolylineAlgorithm;
10+
using PolylineAlgorithm.Benchmarks.Internal;
1011
using System.Collections.Generic;
1112

1213
/// <summary>
1314
/// Benchmarks for the <see cref="PolylineEncoder"/> class.
1415
/// </summary>
1516
[RankColumn]
1617
public class PolylineEncoderBenchmark {
17-
private static readonly Random R = new();
18+
[Params(10, 100, 1_000, 10_000, 100_000)]
19+
public int N;
1820

1921
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
2022
/// <summary>
@@ -38,8 +40,8 @@ public class PolylineEncoderBenchmark {
3840
/// </summary>
3941
[GlobalSetup]
4042
public void SetupData() {
41-
Enumeration = Enumerable.Range(0, 100).Select(i => new Coordinate(R.Next(-90, 90) + R.NextDouble(), R.Next(-180, 180) + R.NextDouble()));
42-
List = new List<Coordinate>(Enumeration);
43+
Enumeration = ValueProvider.GetCoordinates(N);
44+
List = [..Enumeration];
4345
}
4446

4547
/// <summary>

src/PolylineAlgorithm/Internal/Defaults.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,20 @@ public static class Algorithm {
4343
public const byte UnitSeparator = 31;
4444
}
4545

46+
/// <summary>
47+
/// Contains default values related to polyline.
48+
/// </summary>
49+
public static class Polyline {
50+
/// <summary>
51+
/// The minimum length of an encoded coordinate.
52+
/// </summary>
53+
public const int MinEncodedCoordinateLength = 2;
54+
/// <summary>
55+
/// The maximum length of an encoded coordinate.
56+
/// </summary>
57+
public const int MaxEncodedCoordinateLength = 12;
58+
}
59+
4660
/// <summary>
4761
/// Contains default values related to coordinates.
4862
/// </summary>

src/PolylineAlgorithm/Internal/PolylineWriter.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ namespace PolylineAlgorithm.Internal;
1515
[StructLayout(LayoutKind.Auto)]
1616
internal ref struct PolylineWriter {
1717
private WriterState _state = new();
18-
private Memory<char> _buffer;
18+
private Span<char> _buffer;
1919

2020
/// <summary>
2121
/// Initializes a new instance of the <see cref="PolylineWriter"/> struct with the specified buffer.
2222
/// </summary>
2323
/// <param name="buffer">The buffer to write to.</param>
24-
public PolylineWriter(Memory<char> buffer) {
24+
public PolylineWriter(Span<char> buffer) {
2525
_buffer = buffer;
2626
}
2727

@@ -90,7 +90,7 @@ void WriteNext(ref readonly int value) {
9090
void WriteChar(char value) {
9191
InvalidWriterStateException.ThrowIfCannotWrite(CanWrite, Position, _buffer.Length);
9292

93-
_buffer.Span[Position] = value;
93+
_buffer[Position] = value;
9494
_state.Position += 1;
9595
}
9696

@@ -115,8 +115,8 @@ void UpdateCurrent(ref readonly int latitude, ref readonly int longitude, out in
115115
/// </summary>
116116
/// <returns>The <see cref="Polyline"/> instance.</returns>
117117
public readonly Polyline ToPolyline() {
118-
ReadOnlyMemory<char> buffer = _buffer[.._state.Position];
119-
var polyline = Polyline.FromMemory(buffer);
118+
Span<char> buffer = _buffer[.._state.Position];
119+
var polyline = Polyline.FromString(buffer.ToString());
120120
return polyline;
121121
}
122122

src/PolylineAlgorithm/PolylineDecoder.cs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,18 @@ namespace PolylineAlgorithm;
77

88
using PolylineAlgorithm.Internal;
99
using PolylineAlgorithm.Properties;
10+
using System.Buffers;
11+
using System.Runtime.InteropServices;
1012

1113

1214
/// <summary>
1315
/// Performs polyline algorithm decoding
1416
/// </summary>
1517
public class PolylineDecoder : IPolylineDecoder {
1618

19+
private static readonly int _size = Marshal.SizeOf<Coordinate>();
20+
private IMemoryOwner<Coordinate>? _pool;
21+
1722
/// <inheritdoc />
1823
/// <exception cref="ArgumentException">Thrown when <paramref name="polyline"/> argument is null -or- empty.</exception>
1924
/// <exception cref="InvalidOperationException">Thrown when <paramref name="polyline"/> is not in correct format.</exception>
@@ -24,20 +29,36 @@ public IEnumerable<Coordinate> Decode(ref readonly Polyline polyline) {
2429
}
2530

2631
// Initialize local variables
27-
int capacity = polyline.Length / 9;
28-
var result = new List<Coordinate>(capacity);
32+
int capacity = polyline.Length / Defaults.Polyline.MinEncodedCoordinateLength;
33+
Span<Coordinate> buffer = _size * capacity <= 512_000 ? stackalloc Coordinate[capacity] : RentMemory(capacity);
2934

3035
PolylineReader reader = new(in polyline);
36+
int index = 0;
3137

3238
// Looping through encoded polyline char array
3339
while (reader.CanRead) {
3440
var coordinate = reader.Read();
35-
41+
3642
InvalidCoordinateException.ThrowIfNotValid(coordinate);
3743

38-
result.Add(coordinate);
44+
buffer[index] = coordinate;
45+
index++;
3946
}
4047

48+
var result = buffer[..index].ToArray();
49+
50+
ReturnMemory();
51+
4152
return result;
4253
}
54+
55+
private Span<Coordinate> RentMemory(int capacity) {
56+
_pool = MemoryPool<Coordinate>.Shared.Rent(capacity);
57+
58+
return _pool.Memory.Span;
59+
}
60+
61+
private void ReturnMemory() {
62+
_pool?.Dispose();
63+
}
4364
}

src/PolylineAlgorithm/PolylineEncoder.cs

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,18 @@ namespace PolylineAlgorithm;
88
using PolylineAlgorithm.Internal;
99
using PolylineAlgorithm.Properties;
1010
using System;
11+
using System.Buffers;
1112
using System.Collections.Generic;
1213
using System.Diagnostics.CodeAnalysis;
1314
using System.Runtime.CompilerServices;
1415

1516
/// <summary>
1617
/// Provides methods to encode a set of coordinates into a polyline string.
17-
/// </summary>
18+
/// </summary>\
1819
public class PolylineEncoder : IPolylineEncoder {
20+
private static readonly int _size = sizeof(char);
21+
private IMemoryOwner<char>? _pool;
22+
1923
/// <summary>
2024
/// Encodes a set of coordinates into a polyline string.
2125
/// </summary>
@@ -28,33 +32,48 @@ public Polyline Encode(IEnumerable<Coordinate> coordinates) {
2832
throw new ArgumentNullException(nameof(coordinates));
2933
}
3034

31-
int count = GetCount(in coordinates);
35+
int count = GetCount(coordinates);
3236

3337
if (count == 0) {
3438
throw new ArgumentException(ExceptionMessageResource.ArgumentCannotBeEmptyEnumerationMessage, nameof(coordinates));
3539
}
3640

37-
int capacity = count * 12;
38-
Memory<char> buffer = new char[capacity];
41+
int capacity = count * Defaults.Polyline.MaxEncodedCoordinateLength;
42+
Span<char> buffer = _size * capacity <= 512_000 ? stackalloc char[capacity] : RentMemory(capacity);
3943
PolylineWriter writer = new(buffer);
4044

4145
foreach (var coordinate in coordinates) {
4246
InvalidCoordinateException.ThrowIfNotValid(coordinate);
4347
writer.Write(coordinate);
4448
}
4549

46-
return writer.ToPolyline();
47-
48-
/// <summary>
49-
/// Gets the count of coordinates in the enumerable.
50-
/// </summary>
51-
/// <param name="coordinates">The enumerable of coordinates.</param>
52-
/// <returns>The count of coordinates.</returns>
53-
[ExcludeFromCodeCoverage]
54-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
55-
static int GetCount(ref readonly IEnumerable<Coordinate> coordinates) => coordinates switch {
56-
ICollection<Coordinate> collection => collection.Count,
57-
_ => coordinates.Count(),
58-
};
50+
var result = writer.ToPolyline();
51+
52+
53+
54+
return result;
55+
}
56+
57+
/// <summary>
58+
/// Gets the count of coordinates in the enumerable.
59+
/// </summary>
60+
/// <param name="coordinates">The enumerable of coordinates.</param>
61+
/// <returns>The count of coordinates.</returns>
62+
[ExcludeFromCodeCoverage]
63+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
64+
static int GetCount(IEnumerable<Coordinate> coordinates) => coordinates switch {
65+
ICollection<Coordinate> collection => collection.Count,
66+
_ => coordinates.Count(),
67+
};
68+
69+
70+
private Span<char> RentMemory(int capacity) {
71+
_pool = MemoryPool<char>.Shared.Rent(capacity);
72+
73+
return _pool.Memory.Span;
74+
}
75+
76+
private void ReturnMemory() {
77+
_pool?.Dispose();
5978
}
6079
}

tests/PolylineAlgorithm.Tests/Internal/PolylineReaderTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public void Read_Valid_Parameter_Ok() {
7878
int position = value.Length;
7979
Polyline polyline = Polyline.FromString(value);
8080
PolylineReader reader = new(in polyline);
81-
List<Coordinate> expected = new(Values.Coordinates.Valid);
81+
List<Coordinate> expected = [..Values.Coordinates.Valid];
8282
List<Coordinate> result = new(expected.Count);
8383

8484
// Act

tests/PolylineAlgorithm.Tests/Internal/PolylineWriterTest.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public void Constructor_Parameterless_Ok() {
6161
[DynamicData(nameof(Valid_Constructor_Parameter))]
6262
public void Constructor_Valid_Parameter_Ok(int length) {
6363
// Arrange
64-
Memory<char> buffer = new char[length];
64+
Span<char> buffer = new char[length];
6565
bool canWrite = !buffer.IsEmpty;
6666
int position = 0;
6767

@@ -81,7 +81,7 @@ public void Write_Valid_Parameter_Ok() {
8181
// Arrange
8282
IEnumerable<Coordinate> coordinates = Values.Coordinates.Valid;
8383
Polyline expected = Polyline.FromString(Values.Polyline.Valid);
84-
Memory<char> buffer = new char[coordinates.Count() * 12];
84+
Span<char> buffer = new char[coordinates.Count() * Defaults.Polyline.MaxEncodedCoordinateLength];
8585
PolylineWriter writer = new(buffer);
8686
bool canWrite = buffer.Length > expected.Length;
8787
int position = expected.Length;
@@ -106,11 +106,11 @@ public void Write_Valid_Parameter_Ok() {
106106
public void Write_Invalid_Coordinate_Parameter_Ok((double Latitude, double Longitude) value) {
107107
// Arrange
108108
Coordinate coordinate = new(value.Latitude, value.Longitude);
109-
int bufferSize = 12;
109+
int bufferSize = Defaults.Polyline.MaxEncodedCoordinateLength;
110110

111111
// Act
112112
static void Write(Coordinate coordinate, int bufferSize) {
113-
Memory<char> buffer = new char[bufferSize];
113+
Span<char> buffer = new char[bufferSize];
114114
PolylineWriter writer = new(buffer);
115115
writer.Write(coordinate);
116116
}
@@ -131,7 +131,7 @@ public void Write_Buffer_Overflow_InvalidWriterStateException_Thrown(int bufferS
131131

132132
// Act
133133
static void Write(Coordinate coordinate, int bufferSize) {
134-
Memory<char> buffer = new char[bufferSize];
134+
Span<char> buffer = new char[bufferSize];
135135
PolylineWriter writer = new(buffer);
136136

137137
writer.Write(coordinate);

0 commit comments

Comments
 (0)