1515 */
1616package com .google .common .geometry .benchmarks ;
1717
18+ import static java .lang .Math .PI ;
1819import static java .util .concurrent .TimeUnit .NANOSECONDS ;
1920import static java .util .concurrent .TimeUnit .SECONDS ;
2021
22+ import com .google .common .geometry .S1Angle ;
23+ import com .google .common .geometry .S1ChordAngle ;
2124import com .google .common .geometry .S2EdgeUtil ;
2225import com .google .common .geometry .S2Point ;
2326import com .google .errorprone .annotations .CheckReturnValue ;
2427import java .io .IOException ;
25- import java .util .ArrayList ;
26- import java .util .List ;
2728import org .openjdk .jmh .annotations .Benchmark ;
2829import org .openjdk .jmh .annotations .BenchmarkMode ;
2930import org .openjdk .jmh .annotations .Level ;
4142public class S2EdgeUtilBenchmark {
4243 private S2EdgeUtilBenchmark () {}
4344
44- /** Benchmarks the crossing functions on a list of randomly generated points. */
45+ /** Benchmarks the interpolation and crossing functions on a list of randomly generated points. */
4546 @ State (Scope .Thread )
4647 @ BenchmarkMode (Mode .AverageTime )
4748 @ OutputTimeUnit (NANOSECONDS )
48- @ Warmup (iterations = 3 , time = 15 , timeUnit = SECONDS )
49- @ Measurement (iterations = 5 , time = 15 , timeUnit = SECONDS )
49+ @ Warmup (iterations = 3 , time = 10 , timeUnit = SECONDS )
50+ @ Measurement (iterations = 10 , time = 10 , timeUnit = SECONDS )
5051 public static class BenchmarkCrossingState extends S2BenchmarkBaseState {
51- protected static final int NUM_POINTS = 400 ;
52+ // We want to avoid cache effects, so these arrays should be small enough to fit in L1 cache.
53+ // An S2Point is at most 40 bytes, so 250 will take about ~10 KiB.
54+ protected static final int NUM_POINTS = 250 ;
55+ protected static final int NUM_ANGLES = 120 ;
56+
57+ // Randomly selected values between 0 and 1.
58+ protected double [] fractions = new double [NUM_ANGLES ];
59+ // Randomly selected angles between 0 and PI/4.
60+ protected S1Angle [] angles = new S1Angle [NUM_ANGLES ];
61+ protected S1ChordAngle [] chordAngles = new S1ChordAngle [NUM_ANGLES ];
62+
63+ // Randomly selected points.
64+ protected S2Point [] points = new S2Point [NUM_POINTS ];
65+ // Precomputed S1Angle distances between successive points.
66+ protected S1Angle [] precomputedS1Angle = new S1Angle [NUM_POINTS ];
5267
53- protected List <S2Point > points ;
5468 protected int pointIndex ;
69+ protected int distanceIndex ;
5570 protected S2Point a ;
5671 protected S2Point b ;
5772
5873 @ Setup (Level .Trial )
5974 @ Override
6075 public void setup () throws IOException {
6176 super .setup ();
62- // We want to avoid cache effects, so numPoints should be small enough so that the points can
63- // be in L1 cache. The size of an S2Point is at most 40 bytes, so 400 will only take at most
64- // ~16 KiB of 64 KiB of L1 cache.
65- points = new ArrayList <>(NUM_POINTS );
6677 for (int i = 0 ; i < NUM_POINTS ; ++i ) {
67- points .add (data .getRandomPoint ());
78+ points [i ] = data .getRandomPoint ();
79+ }
80+ for (int i = 0 ; i < NUM_POINTS ; ++i ) {
81+ S2Point a = points [i ];
82+ S2Point b = points [(i + 1 ) % NUM_POINTS ];
83+ precomputedS1Angle [i ] = new S1Angle (a , b );
6884 }
6985 pointIndex = 0 ;
7086
87+ for (int i = 0 ; i < NUM_ANGLES ; ++i ) {
88+ fractions [i ] = data .uniform (0 , 1 );
89+ angles [i ] = S1Angle .radians (data .uniform (0 , PI / 4 ));
90+ chordAngles [i ] = S1ChordAngle .fromS1Angle (angles [i ]);
91+ }
92+ distanceIndex = 0 ;
93+
7194 // Approximately 1/4th of points will cross the edge 'ab'.
7295 a = data .getRandomPoint ();
7396 b = S2Point .neg (a ).add (new S2Point (0.1 , 0.1 , 0.1 )).normalize ();
@@ -80,17 +103,17 @@ public void setup() throws IOException {
80103 *
81104 * <p>For example, one test run found the following:
82105 * <ul>
83- * <li> 37.787 +/- 0.499 ns/op for benchmarkOverhead .
106+ * <li> 37.787 +/- 0.499 ns/op for benchmarkFourPointOverhead .
84107 * <li>117.601 +/- 15.786 ns/op for edgeOrVertexCrossing, so true cost is 79 +/- 16 ns.
85108 * <li>113.949 +/- 1.507 ns/op for robustCrossing, so true cost is 76 +/- 2 ns.
86109 * </ul>
87110 */
88111 @ Benchmark
89- public int benchmarkOverhead (Blackhole bh ) {
90- S2Point a = points . get (( pointIndex + 0 ) % NUM_POINTS ) ;
91- S2Point b = points . get (( pointIndex + 1 ) % NUM_POINTS ) ;
92- S2Point c = points . get (( pointIndex + 2 ) % NUM_POINTS ) ;
93- S2Point d = points . get (( pointIndex + 3 ) % NUM_POINTS ) ;
112+ public int benchmarkFourPointOverhead (Blackhole bh ) {
113+ S2Point a = points [( pointIndex + 0 ) % NUM_POINTS ] ;
114+ S2Point b = points [( pointIndex + 1 ) % NUM_POINTS ] ;
115+ S2Point c = points [( pointIndex + 2 ) % NUM_POINTS ] ;
116+ S2Point d = points [( pointIndex + 3 ) % NUM_POINTS ] ;
94117 pointIndex = (pointIndex + 1 ) % NUM_POINTS ;
95118 bh .consume (a );
96119 bh .consume (b );
@@ -102,21 +125,21 @@ public int benchmarkOverhead(Blackhole bh) {
102125 /** Benchmarks a single call to edgeOrVertexCrossing() with random points. */
103126 @ Benchmark
104127 public boolean edgeOrVertexCrossing () {
105- S2Point a = points . get (( pointIndex + 0 ) % NUM_POINTS ) ;
106- S2Point b = points . get (( pointIndex + 1 ) % NUM_POINTS ) ;
107- S2Point c = points . get (( pointIndex + 2 ) % NUM_POINTS ) ;
108- S2Point d = points . get (( pointIndex + 3 ) % NUM_POINTS ) ;
128+ S2Point a = points [( pointIndex + 0 ) % NUM_POINTS ] ;
129+ S2Point b = points [( pointIndex + 1 ) % NUM_POINTS ] ;
130+ S2Point c = points [( pointIndex + 2 ) % NUM_POINTS ] ;
131+ S2Point d = points [( pointIndex + 3 ) % NUM_POINTS ] ;
109132 pointIndex = (pointIndex + 1 ) % NUM_POINTS ;
110133 return S2EdgeUtil .edgeOrVertexCrossing (a , b , c , d );
111134 }
112135
113136 /** Benchmarks a single call to robustCrossing() with random points. */
114137 @ Benchmark
115138 public int robustCrossing () {
116- S2Point a = points . get (( pointIndex + 0 ) % NUM_POINTS ) ;
117- S2Point b = points . get (( pointIndex + 1 ) % NUM_POINTS ) ;
118- S2Point c = points . get (( pointIndex + 2 ) % NUM_POINTS ) ;
119- S2Point d = points . get (( pointIndex + 3 ) % NUM_POINTS ) ;
139+ S2Point a = points [( pointIndex + 0 ) % NUM_POINTS ] ;
140+ S2Point b = points [( pointIndex + 1 ) % NUM_POINTS ] ;
141+ S2Point c = points [( pointIndex + 2 ) % NUM_POINTS ] ;
142+ S2Point d = points [( pointIndex + 3 ) % NUM_POINTS ] ;
120143 pointIndex = (pointIndex + 1 ) % NUM_POINTS ;
121144 return S2EdgeUtil .robustCrossing (a , b , c , d );
122145 }
@@ -128,11 +151,94 @@ public int robustCrossing() {
128151 */
129152 @ Benchmark
130153 public void edgeCrosser100RobustCrossings (Blackhole bh ) {
131- S2EdgeUtil .EdgeCrosser crosser = new S2EdgeUtil .EdgeCrosser (a , b , points . get ( 0 ) );
154+ S2EdgeUtil .EdgeCrosser crosser = new S2EdgeUtil .EdgeCrosser (a , b , points [ 0 ] );
132155 for (int r = 100 ; r > 0 ; --r ) {
133- S2Point d = points . get ( r % NUM_POINTS ) ;
156+ S2Point d = points [ r % NUM_POINTS ] ;
134157 bh .consume (crosser .robustCrossing (d ));
135158 }
136159 }
160+
161+ /**
162+ * Measures overhead due to iterating through and obtaining two S2Points with this benchmark
163+ * State. Subtract the time measured by this benchmark from the interpolation benchmarks below
164+ * to get a more accurate measure of their real cost.
165+ */
166+ @ Benchmark
167+ public int benchmarkTwoPointOverhead (Blackhole bh ) {
168+ S2Point a = points [(pointIndex + 0 ) % NUM_POINTS ];
169+ S2Point b = points [(pointIndex + 1 ) % NUM_POINTS ];
170+ pointIndex = (pointIndex + 1 ) % NUM_POINTS ;
171+ bh .consume (a );
172+ bh .consume (b );
173+ return pointIndex ;
174+ }
175+
176+ /** Benchmarks a single call to interpolate() with random points and distances. */
177+ @ Benchmark
178+ public S2Point interpolate () {
179+ S2Point a = points [(pointIndex + 0 ) % NUM_POINTS ];
180+ S2Point b = points [(pointIndex + 1 ) % NUM_POINTS ];
181+ double t = fractions [distanceIndex % NUM_ANGLES ];
182+ pointIndex = (pointIndex + 1 ) % NUM_POINTS ;
183+ distanceIndex = (distanceIndex + 1 ) % NUM_ANGLES ;
184+ return S2EdgeUtil .interpolate (t , a , b );
185+ }
186+
187+ /**
188+ * Benchmarks {@link S2EdgeUtil#getPointOnLine(S2Point, S2Point, S1Angle)} with random
189+ * points and distances.
190+ */
191+ @ Benchmark
192+ public S2Point getPointOnLineS1Angle () {
193+ S2Point a = points [(pointIndex + 0 ) % NUM_POINTS ];
194+ S2Point b = points [(pointIndex + 1 ) % NUM_POINTS ];
195+ S1Angle angle = angles [distanceIndex % NUM_ANGLES ];
196+ pointIndex = (pointIndex + 1 ) % NUM_POINTS ;
197+ distanceIndex = (distanceIndex + 1 ) % NUM_ANGLES ;
198+ return S2EdgeUtil .getPointOnLine (a , b , angle );
199+ }
200+
201+ /**
202+ * Benchmarks {@link S2EdgeUtil#getPointOnLine(S2Point, S2Point, S1Angle)} with random
203+ * points and distances.
204+ */
205+ @ Benchmark
206+ public S2Point getPointOnLineS1ChordAngle () {
207+ S2Point a = points [(pointIndex + 0 ) % NUM_POINTS ];
208+ S2Point b = points [(pointIndex + 1 ) % NUM_POINTS ];
209+ S1ChordAngle chordAngle = chordAngles [distanceIndex % NUM_ANGLES ];
210+ pointIndex = (pointIndex + 1 ) % NUM_POINTS ;
211+ distanceIndex = (distanceIndex + 1 ) % NUM_ANGLES ;
212+ return S2EdgeUtil .getPointOnLine (a , b , chordAngle );
213+ }
214+
215+ /**
216+ * Benchmarks {@link S2EdgeUtil#interpolateAtDistance(S1Angle, S2Point, S2Point)} with random
217+ * points and distances.
218+ */
219+ @ Benchmark
220+ public S2Point interpolateAtDistance () {
221+ S2Point a = points [(pointIndex + 0 ) % NUM_POINTS ];
222+ S2Point b = points [(pointIndex + 1 ) % NUM_POINTS ];
223+ S1Angle angle = angles [distanceIndex % NUM_ANGLES ];
224+ pointIndex = (pointIndex + 1 ) % NUM_POINTS ;
225+ distanceIndex = (distanceIndex + 1 ) % NUM_ANGLES ;
226+ return S2EdgeUtil .interpolateAtDistance (angle , a , b );
227+ }
228+
229+ /**
230+ * Benchmarks {@link S2EdgeUtil#interpolateAtDistance(S1Angle, S2Point, S2Point, S1Angle)} with
231+ * precomputed S1Angle distance AB for random points and distances.
232+ */
233+ @ Benchmark
234+ public S2Point interpolateAtDistanceWithPrecomputedS1AngleAB () {
235+ S2Point a = points [(pointIndex + 0 ) % NUM_POINTS ];
236+ S2Point b = points [(pointIndex + 1 ) % NUM_POINTS ];
237+ S1Angle ab = precomputedS1Angle [pointIndex % NUM_POINTS ];
238+ S1Angle angle = angles [distanceIndex % NUM_ANGLES ];
239+ pointIndex = (pointIndex + 1 ) % NUM_POINTS ;
240+ distanceIndex = (distanceIndex + 1 ) % NUM_ANGLES ;
241+ return S2EdgeUtil .interpolateAtDistance (angle , a , b , ab );
242+ }
137243 }
138244}
0 commit comments