Skip to content

Commit 0b92dcd

Browse files
committed
performance
1 parent c131987 commit 0b92dcd

20 files changed

Lines changed: 106 additions & 134 deletions

samples/PolylineAlgorithm.NetTopologySuite.Sample/NetTopologyPolylineDecoder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ protected override Point CreateCoordinate(double latitude, double longitude) {
4141
/// <exception cref="ArgumentException">
4242
/// Thrown when the provided polyline string is null, empty, or consists only of whitespace characters.
4343
/// </exception>
44-
protected override ReadOnlyMemory<char> GetReadOnlyMemory(string polyline) {
44+
protected override ReadOnlyMemory<char> GetReadOnlyMemory(ref string polyline) {
4545
return polyline.AsMemory();
4646
}
4747
}

samples/PolylineAlgorithm.NetTopologySuite.Sample/NetTopologyPolylineEncoder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public sealed class NetTopologyPolylineEncoder : AbstractPolylineEncoder<Point,
2121
/// <returns>
2222
/// An encoded polyline string representation of the provided polyline.
2323
/// </returns>
24-
protected override string CreatePolyline(ref ReadOnlyMemory<char> polyline) {
24+
protected override string CreatePolyline(ReadOnlyMemory<char> polyline) {
2525
if (polyline.IsEmpty) {
2626
return string.Empty;
2727
}

src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -73,20 +73,20 @@ public IEnumerable<TCoordinate> Decode(TPolyline polyline) {
7373
_logger.
7474
LogOperationStartedDebug(OperationName);
7575

76-
ValidateNullPolyline(ref polyline, _logger);
76+
ValidateNullPolyline(polyline, _logger);
7777

78-
ReadOnlyMemory<char> sequence = GetReadOnlyMemory(polyline);
78+
ReadOnlyMemory<char> sequence = GetReadOnlyMemory(ref polyline);
7979

80-
ValidateEmptySequence(ref sequence, _logger);
80+
ValidateEmptySequence(sequence, _logger);
8181

8282
int position = 0;
8383
int latitude = 0;
8484
int longitude = 0;
8585

8686
while (position < sequence.Length) {
8787
// Read the next value from the polyline encoding
88-
if (!PolylineEncoding.TryReadValue(ref latitude, ref sequence, ref position)
89-
|| !PolylineEncoding.TryReadValue(ref longitude, ref sequence, ref position)
88+
if (!PolylineEncoding.TryReadValue(ref latitude, sequence, ref position)
89+
|| !PolylineEncoding.TryReadValue(ref longitude, sequence, ref position)
9090
) {
9191
_logger.
9292
LogOperationFailedDebug(OperationName);
@@ -103,7 +103,7 @@ public IEnumerable<TCoordinate> Decode(TPolyline polyline) {
103103
.LogOperationFinishedDebug(OperationName);
104104

105105
[MethodImpl(MethodImplOptions.AggressiveInlining)]
106-
static void ValidateNullPolyline(ref TPolyline polyline, ILogger logger) {
106+
static void ValidateNullPolyline(TPolyline polyline, ILogger logger) {
107107
if (polyline is null) {
108108
logger
109109
.LogNullArgumentWarning(nameof(polyline));
@@ -113,7 +113,7 @@ static void ValidateNullPolyline(ref TPolyline polyline, ILogger logger) {
113113
}
114114

115115
[MethodImpl(MethodImplOptions.AggressiveInlining)]
116-
static void ValidateEmptySequence(ref ReadOnlyMemory<char> polyline, ILogger logger) {
116+
static void ValidateEmptySequence(ReadOnlyMemory<char> polyline, ILogger logger) {
117117
if (polyline.Length < Defaults.Polyline.Block.Length.Min) {
118118
logger.
119119
LogOperationFailedDebug(OperationName);
@@ -135,7 +135,7 @@ static void ValidateEmptySequence(ref ReadOnlyMemory<char> polyline, ILogger log
135135
/// A <see cref="ReadOnlyMemory{T}"/> representing the encoded polyline data.
136136
/// </returns>
137137
[MethodImpl(MethodImplOptions.AggressiveInlining)]
138-
protected abstract ReadOnlyMemory<char> GetReadOnlyMemory(TPolyline polyline);
138+
protected abstract ReadOnlyMemory<char> GetReadOnlyMemory(ref TPolyline polyline);
139139

140140
/// <summary>
141141
/// Creates a coordinate instance from the given latitude and longitude values.

src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs

Lines changed: 37 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,12 @@ namespace PolylineAlgorithm.Abstraction;
1111
using PolylineAlgorithm.Internal.Logging;
1212
using PolylineAlgorithm.Properties;
1313
using System;
14+
using System.Buffers;
1415
using System.Collections.Generic;
1516
using System.Diagnostics;
17+
using System.Diagnostics.CodeAnalysis;
1618
using System.Runtime.CompilerServices;
19+
using static PolylineAlgorithm.Internal.Defaults.Polyline.Block;
1720

1821
/// <summary>
1922
/// Provides functionality to encode a collection of geographic coordinates into an encoded polyline string.
@@ -57,7 +60,6 @@ protected AbstractPolylineEncoder(PolylineEncodingOptions options) {
5760
/// </param>
5861
/// <returns>
5962
/// An instance of <typeparamref name="TPolyline"/> representing the encoded coordinates.
60-
/// Returns <see langword="default"/> if the input collection is empty or null.
6163
/// </returns>
6264
/// <exception cref="ArgumentNullException">
6365
/// Thrown when <paramref name="coordinates"/> is <see langword="null"/>.
@@ -81,38 +83,48 @@ public TPolyline Encode(ReadOnlySpan<TCoordinate> coordinates) {
8183
int consumed = 0;
8284
int length = GetMaxBufferLength(coordinates.Length, _logger);
8385

84-
Span<char> buffer = stackalloc char[length];
86+
char[]? temp = length <= Options.StackAllocLimit
87+
? null
88+
: ArrayPool<char>.Shared.Rent(length);
8589

86-
for (var i = 0; i < coordinates.Length; i++) {
87-
variance
88-
.Next(
89-
PolylineEncoding.Normalize(GetLatitude(coordinates[i]), CoordinateValueType.Latitude),
90-
PolylineEncoding.Normalize(GetLongitude(coordinates[i]), CoordinateValueType.Longitude)
91-
);
90+
Span<char> buffer = temp is null ? stackalloc char[length] : temp.AsSpan(0, length);
9291

93-
ValidateBuffer(ref variance, ref position, ref buffer, _logger);
92+
try {
93+
for (var i = 0; i < coordinates.Length; i++) {
94+
variance
95+
.Next(
96+
PolylineEncoding.Normalize(GetLatitude(coordinates[i]), CoordinateValueType.Latitude),
97+
PolylineEncoding.Normalize(GetLongitude(coordinates[i]), CoordinateValueType.Longitude)
98+
);
9499

95-
if (!PolylineEncoding.TryWriteValue(variance.Latitude, ref buffer, ref position)
96-
|| !PolylineEncoding.TryWriteValue(variance.Longitude, ref buffer, ref position)
97-
) {
98-
// This shouldn't happen, but if it does, log the error and throw an exception.
99-
_logger
100-
.LogOperationFailedDebug(OperationName);
101-
_logger
102-
.LogCannotWriteValueToBufferWarning(position, consumed);
100+
ValidateBuffer(variance, position, buffer, _logger);
103101

104-
throw new InvalidOperationException(ExceptionMessageResource.CouldNotWriteEncodedValueToTheBuffer);
105-
}
102+
if (!PolylineEncoding.TryWriteValue(variance.Latitude, buffer, ref position)
103+
|| !PolylineEncoding.TryWriteValue(variance.Longitude, buffer, ref position)
104+
) {
105+
// This shouldn't happen, but if it does, log the error and throw an exception.
106+
_logger
107+
.LogOperationFailedDebug(OperationName);
108+
_logger
109+
.LogCannotWriteValueToBufferWarning(position, consumed);
110+
111+
throw new InvalidOperationException(ExceptionMessageResource.CouldNotWriteEncodedValueToTheBuffer);
112+
}
106113

107-
consumed++;
114+
consumed++;
115+
}
116+
} finally {
117+
if(temp is not null) {
118+
ArrayPool<char>.Shared.Return(temp);
119+
}
108120
}
109121

122+
123+
110124
_logger
111125
.LogOperationFinishedDebug(OperationName);
112126

113-
var result = buffer[..position].ToString().AsMemory();
114-
115-
return CreatePolyline(ref result);
127+
return CreatePolyline(buffer[..position].ToString().AsMemory());
116128

117129
[MethodImpl(MethodImplOptions.AggressiveInlining)]
118130
static int GetRequiredLength(CoordinateVariance variance) =>
@@ -134,16 +146,8 @@ int GetMaxBufferLength(int count, ILogger logger) {
134146

135147
int requestedBufferLength = count * Defaults.Polyline.Block.Length.Max;
136148

137-
Debug.Assert(Options.MaxBufferLength > 0, "Max buffer length must be greater than zero.");
138149
Debug.Assert(requestedBufferLength > 0, "Requested buffer length must be greater than zero.");
139150

140-
if (requestedBufferLength > Options.MaxBufferLength) {
141-
logger
142-
.LogRequestedBufferSizeExceedsMaxBufferLengthWarning(requestedBufferLength, Options.MaxBufferLength);
143-
144-
return Options.MaxBufferLength;
145-
}
146-
147151
return requestedBufferLength;
148152
}
149153

@@ -160,7 +164,7 @@ static void ValidateEmptyCoordinates(ref ReadOnlySpan<TCoordinate> coordinates,
160164
}
161165

162166
[MethodImpl(MethodImplOptions.AggressiveInlining)]
163-
static void ValidateBuffer(ref CoordinateVariance variance, ref int position, ref Span<char> buffer, ILogger logger) {
167+
static void ValidateBuffer(CoordinateVariance variance, int position, Span<char> buffer, ILogger logger) {
164168
if (GetRemainingBufferSize(position, buffer.Length) < GetRequiredLength(variance)) {
165169
logger
166170
.LogOperationFailedDebug(OperationName);
@@ -180,7 +184,7 @@ static void ValidateBuffer(ref CoordinateVariance variance, ref int position, re
180184
/// An instance of <typeparamref name="TPolyline"/> representing the encoded polyline.
181185
/// </returns>
182186
[MethodImpl(MethodImplOptions.AggressiveInlining)]
183-
protected abstract TPolyline CreatePolyline(ref ReadOnlyMemory<char> polyline);
187+
protected abstract TPolyline CreatePolyline(ReadOnlyMemory<char> polyline);
184188

185189
/// <summary>
186190
/// Extracts the longitude value from the specified coordinate.

src/PolylineAlgorithm/Internal/CoordinateVariance.cs

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ namespace PolylineAlgorithm.Internal;
1313
/// Represents the difference (variance) in latitude and longitude between consecutive geographic coordinates.
1414
/// This struct is used to compute and store the change in coordinate values as integer deltas.
1515
/// </summary>
16-
[DebuggerDisplay($"{{{nameof(ToString)}(),nq}}")]
17-
[StructLayout(LayoutKind.Sequential, Pack = 4, Size = 16)]
16+
[DebuggerDisplay("{ToString(),nq}")]
1817
internal struct CoordinateVariance {
1918
private (int Latitude, int Longitude) _current;
2019

@@ -59,22 +58,16 @@ public void Next(int latitude, int longitude) {
5958
/// <param name="next">The next coordinate value.</param>
6059
/// <returns>The computed variance between <paramref name="initial"/> and <paramref name="next"/>.</returns>
6160

62-
private static int Variance(int initial, int next) => (initial, next) switch {
63-
(0, 0) => 0,
64-
(0, _) => next,
65-
(_, 0) => -initial,
66-
( < 0, < 0) => -(Math.Abs(next) - Math.Abs(initial)),
67-
( < 0, > 0) => next + Math.Abs(initial),
68-
( > 0, < 0) => -(Math.Abs(next) + initial),
69-
( > 0, > 0) => next - initial,
70-
};
61+
private static int Variance(int initial, int next) => next - initial;
7162

7263
/// <summary>
7364
/// Returns a string representation of the current coordinate variance.
7465
/// </summary>
7566
/// <returns>
7667
/// A string in the format <c>{ Coordinate: { Latitude: [int], Longitude: [int] }, Variance: { Latitude: [int], Longitude: [int] } }</c> representing the current coordinate and deltas to previous coordinate.
7768
/// </returns>
78-
public override readonly string ToString()
79-
=> $"{{ Coordinate: {{ Latitude: {Latitude}, Longitude: {Longitude} }}, Variance: {{ Latitude: {Latitude}, Longitude: {Longitude} }} }}";
69+
public override readonly string ToString() =>
70+
$"{{ Coordinate: {{ Latitude: {_current.Latitude}, Longitude: {_current.Longitude} }}, " +
71+
$"Variance: {{ Latitude: {Latitude}, Longitude: {Longitude} }} }}";
72+
8073
}

src/PolylineAlgorithm/PolylineDecoder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ protected override Coordinate CreateCoordinate(double latitude, double longitude
2323
}
2424

2525
/// <inheritdoc />
26-
protected override ReadOnlyMemory<char> GetReadOnlyMemory(Polyline polyline) {
26+
protected override ReadOnlyMemory<char> GetReadOnlyMemory(ref Polyline polyline) {
2727
return polyline.Value;
2828
}
2929
}

src/PolylineAlgorithm/PolylineEncoder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ protected override double GetLongitude(Coordinate coordinate) {
2828
}
2929

3030
/// <inheritdoc />
31-
protected override Polyline CreatePolyline(ref ReadOnlyMemory<char> polyline) {
31+
protected override Polyline CreatePolyline(ReadOnlyMemory<char> polyline) {
3232
return Polyline.FromMemory(polyline);
3333
}
3434
}

src/PolylineAlgorithm/PolylineEncoding.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public static class PolylineEncoding {
3838
/// langword="false"/>.
3939
/// </returns>
4040

41-
public static bool TryReadValue(ref int variance, ref ReadOnlyMemory<char> buffer, ref int position) {
41+
public static bool TryReadValue(ref int variance, ReadOnlyMemory<char> buffer, ref int position) {
4242
// Validate that the position is within the bounds of the buffer.
4343
if (position == buffer.Length) {
4444
return false;
@@ -105,7 +105,7 @@ public static double Denormalize(int value, CoordinateValueType type) {
105105
return 0.0;
106106
}
107107

108-
return Math.Truncate((double)value) / Defaults.Algorithm.Precision;
108+
return value / (double)Defaults.Algorithm.Precision;
109109
}
110110

111111
/// <summary>
@@ -131,7 +131,7 @@ public static double Denormalize(int value, CoordinateValueType type) {
131131
/// <see langword="true"/> if the value was successfully written to the buffer; otherwise, <see langword="false"/>.
132132
/// </returns>
133133

134-
public static bool TryWriteValue(int variance, ref Span<char> buffer, ref int position) {
134+
public static bool TryWriteValue(int variance, Span<char> buffer, ref int position) {
135135
// Validate that the position and required space for write is within the bounds of the buffer.
136136
if (buffer.Length < position + GetCharCount(variance)) {
137137
return false;

src/PolylineAlgorithm/PolylineEncodingOptions.cs

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,27 +18,18 @@ namespace PolylineAlgorithm;
1818
[DebuggerDisplay("MaxBufferSize: {MaxBufferSize}, MaxBufferLength: {MaxBufferLength}, LoggerFactoryType: {LoggerFactory.GetType().Name}")]
1919
public sealed class PolylineEncodingOptions {
2020
/// <summary>
21-
/// Gets the maximum buffer size for encoding operations.
22-
/// </summary>
23-
/// <remarks>
24-
/// The default maximum buffer size is 64,000 bytes (64 KB). This can be adjusted based on the expected size of the polyline data, but should be enough for common cases.
25-
/// </remarks>
26-
public int MaxBufferSize { get; internal set; } = 64_000;
27-
28-
/// <summary>
29-
/// Gets or sets the precision for encoding coordinates.
21+
/// Gets logger factory.
3022
/// </summary>
3123
/// <remarks>
3224
/// The default logger factory is <see cref="NullLoggerFactory"/>, which does not log any messages.
3325
/// </remarks>
3426
public ILoggerFactory LoggerFactory { get; internal set; } = NullLoggerFactory.Instance;
3527

36-
3728
/// <summary>
38-
/// Gets the maximum length of the encoded polyline string.
29+
/// Gets stackalloc limit.
3930
/// </summary>
4031
/// <remarks>
41-
/// The maximum length is calculated based on the buffer size divided by the size of a character.
32+
/// The default stack alloc limit is 512.
4233
/// </remarks>
43-
internal int MaxBufferLength => MaxBufferSize / sizeof(char);
34+
public int StackAllocLimit { get; internal set; } = 512;
4435
}

src/PolylineAlgorithm/PolylineEncodingOptionsBuilder.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ namespace PolylineAlgorithm;
1313
/// Provides a builder for configuring options for polyline encoding operations.
1414
/// </summary>
1515
public class PolylineEncodingOptionsBuilder {
16-
private int _bufferSize = 64_000;
16+
private int _stackAllocLimit = 512;
1717
private ILoggerFactory _loggerFactory = NullLoggerFactory.Instance;
1818

1919
private PolylineEncodingOptionsBuilder() { }
@@ -36,7 +36,7 @@ public static PolylineEncodingOptionsBuilder Create() {
3636
/// </returns>
3737
public PolylineEncodingOptions Build() {
3838
return new PolylineEncodingOptions {
39-
MaxBufferSize = _bufferSize,
39+
StackAllocLimit = _stackAllocLimit,
4040
LoggerFactory = _loggerFactory
4141
};
4242
}
@@ -51,8 +51,9 @@ public PolylineEncodingOptions Build() {
5151
/// The current builder instance.
5252
/// </returns>
5353
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="bufferSize"/> is less than or equal to 11.</exception>
54-
public PolylineEncodingOptionsBuilder WithMaxBufferSize(int bufferSize) {
55-
_bufferSize = bufferSize > 11 ? bufferSize : throw new ArgumentOutOfRangeException(nameof(bufferSize), string.Format(ExceptionMessageResource.BufferSizeMustBeGreaterThanMessageFormat, 11));
54+
public PolylineEncodingOptionsBuilder WithStackAllocLimit(int stackAllocLimit) {
55+
const int minStackAllocLimit = 1;
56+
_stackAllocLimit = stackAllocLimit >= minStackAllocLimit ? stackAllocLimit : throw new ArgumentOutOfRangeException(nameof(stackAllocLimit), string.Format(ExceptionMessageResource.StackAllocLimitMustBeEqualOrGreaterThanMessageFormat, minStackAllocLimit));
5657

5758
return this;
5859
}

0 commit comments

Comments
 (0)