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+ }
0 commit comments