1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.geometry.examples.jmh.euclidean;
18
19 import java.util.ArrayList;
20 import java.util.List;
21 import java.util.concurrent.TimeUnit;
22 import java.util.function.DoubleSupplier;
23 import java.util.function.Function;
24 import java.util.function.ToDoubleFunction;
25 import java.util.function.UnaryOperator;
26
27 import org.apache.commons.geometry.core.Vector;
28 import org.apache.commons.geometry.euclidean.oned.Vector1D;
29 import org.apache.commons.geometry.euclidean.threed.Vector3D;
30 import org.apache.commons.geometry.euclidean.twod.Vector2D;
31 import org.apache.commons.rng.UniformRandomProvider;
32 import org.apache.commons.rng.sampling.distribution.ZigguratNormalizedGaussianSampler;
33 import org.apache.commons.rng.simple.RandomSource;
34 import org.openjdk.jmh.annotations.Benchmark;
35 import org.openjdk.jmh.annotations.BenchmarkMode;
36 import org.openjdk.jmh.annotations.Fork;
37 import org.openjdk.jmh.annotations.Level;
38 import org.openjdk.jmh.annotations.Measurement;
39 import org.openjdk.jmh.annotations.Mode;
40 import org.openjdk.jmh.annotations.OutputTimeUnit;
41 import org.openjdk.jmh.annotations.Param;
42 import org.openjdk.jmh.annotations.Scope;
43 import org.openjdk.jmh.annotations.Setup;
44 import org.openjdk.jmh.annotations.State;
45 import org.openjdk.jmh.annotations.Warmup;
46 import org.openjdk.jmh.infra.Blackhole;
47
48
49
50
51 @BenchmarkMode(Mode.AverageTime)
52 @OutputTimeUnit(TimeUnit.NANOSECONDS)
53 @Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
54 @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
55 @Fork(value = 1, jvmArgs = {"-server", "-Xms512M", "-Xmx512M"})
56 public class VectorPerformance {
57
58
59
60
61
62 private static final double[] EDGE_NUMBERS = {
63 Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.MAX_VALUE,
64 -Double.MAX_VALUE, Double.MIN_VALUE, -Double.MIN_VALUE, 0.0, -0.0, Double.NaN
65 };
66
67
68 private static final String RANDOM = "random";
69
70
71
72
73 private static final String NORMALIZABLE = "normalizable";
74
75
76
77
78 private static final String EDGE = "edge";
79
80
81
82
83 @State(Scope.Thread)
84 public abstract static class VectorInputBase<V extends Vector<V>> {
85
86
87 private final int dimension;
88
89
90 private final Function<double[], V> vectorFactory;
91
92
93 @Param({"100", "10000"})
94 private int size;
95
96
97 private List<V> vectors;
98
99
100
101
102
103 VectorInputBase(final int dimension, final Function<double[], V> vectorFactory) {
104 this.dimension = dimension;
105 this.vectorFactory = vectorFactory;
106 }
107
108
109
110 @Setup(Level.Iteration)
111 public void setup() {
112 vectors = new ArrayList<>(size);
113
114 final double[] values = new double[dimension];
115 final DoubleSupplier doubleSupplier = createDoubleSupplier(getType(),
116 RandomSource.create(RandomSource.XO_RO_SHI_RO_128_PP));
117
118 for (int i = 0; i < size; ++i) {
119 for (int j = 0; j < dimension; ++j) {
120 values[j] = doubleSupplier.getAsDouble();
121 }
122
123 vectors.add(vectorFactory.apply(values));
124 }
125 }
126
127
128
129
130 public List<V> getVectors() {
131 return vectors;
132 }
133
134
135
136
137 public abstract String getType();
138
139
140
141
142
143
144 private DoubleSupplier createDoubleSupplier(final String type, final UniformRandomProvider rng) {
145 switch (type) {
146 case RANDOM:
147 return () -> createRandomDouble(rng);
148 case NORMALIZABLE:
149 final ZigguratNormalizedGaussianSampler sampler = ZigguratNormalizedGaussianSampler.of(rng);
150 return () -> {
151 double n = sampler.sample();
152 return n == 0 ? 0.1 : n;
153 };
154 case EDGE:
155 return () -> EDGE_NUMBERS[rng.nextInt(EDGE_NUMBERS.length)];
156 default:
157 throw new IllegalStateException("Invalid input type: " + type);
158 }
159 }
160 }
161
162
163
164
165 @State(Scope.Thread)
166 public static class VectorInput1D extends VectorInputBase<Vector1D> {
167
168
169 @Param({RANDOM, EDGE})
170 private String type;
171
172
173 public VectorInput1D() {
174 super(1, arr -> Vector1D.of(arr[0]));
175 }
176
177
178 @Override
179 public String getType() {
180 return type;
181 }
182 }
183
184
185
186 @State(Scope.Thread)
187 public static class NormalizableVectorInput1D extends VectorInputBase<Vector1D> {
188
189
190 public NormalizableVectorInput1D() {
191 super(1, arr -> Vector1D.of(arr[0]));
192 }
193
194
195 @Override
196 public String getType() {
197 return NORMALIZABLE;
198 }
199 }
200
201
202
203
204 @State(Scope.Thread)
205 public static class VectorInput2D extends VectorInputBase<Vector2D> {
206
207
208 @Param({RANDOM, EDGE})
209 private String type;
210
211
212 public VectorInput2D() {
213 super(2, Vector2D::of);
214 }
215
216
217 @Override
218 public String getType() {
219 return type;
220 }
221 }
222
223
224
225 @State(Scope.Thread)
226 public static class NormalizableVectorInput2D extends VectorInputBase<Vector2D> {
227
228
229 public NormalizableVectorInput2D() {
230 super(2, Vector2D::of);
231 }
232
233
234 @Override
235 public String getType() {
236 return NORMALIZABLE;
237 }
238 }
239
240
241
242
243 @State(Scope.Thread)
244 public static class VectorInput3D extends VectorInputBase<Vector3D> {
245
246
247 @Param({RANDOM, EDGE})
248 private String type;
249
250
251 public VectorInput3D() {
252 super(3, Vector3D::of);
253 }
254
255
256 @Override
257 public String getType() {
258 return type;
259 }
260 }
261
262
263
264 @State(Scope.Thread)
265 public static class NormalizableVectorInput3D extends VectorInputBase<Vector3D> {
266
267
268 public NormalizableVectorInput3D() {
269 super(3, Vector3D::of);
270 }
271
272
273 @Override
274 public String getType() {
275 return NORMALIZABLE;
276 }
277 }
278
279
280
281
282
283
284 private static double createRandomDouble(final UniformRandomProvider rng) {
285
286
287
288 final long mask = ((1L << 52) - 1) | 1L << 63;
289 final long bits = rng.nextLong() & mask;
290
291 final long exp = rng.nextInt(129) - 64 + 1023;
292 return Double.longBitsToDouble(bits | (exp << 52));
293 }
294
295
296
297
298
299
300
301 private static <V extends Vector<V>> void testToDouble(final VectorInputBase<V> input, final Blackhole bh,
302 final ToDoubleFunction<V> fn) {
303 for (final V vec : input.getVectors()) {
304 bh.consume(fn.applyAsDouble(vec));
305 }
306 }
307
308
309
310
311
312
313
314 private static <V extends Vector<V>> void testUnary(final VectorInputBase<V> input, final Blackhole bh,
315 final UnaryOperator<V> fn) {
316 for (final V vec : input.getVectors()) {
317 bh.consume(fn.apply(vec));
318 }
319 }
320
321
322
323
324
325 @Benchmark
326 public void baseline(final VectorInput1D input, final Blackhole bh) {
327 testUnary(input, bh, UnaryOperator.identity());
328 }
329
330
331
332
333
334 @Benchmark
335 public void norm1D(final VectorInput1D input, final Blackhole bh) {
336 testToDouble(input, bh, Vector1D::norm);
337 }
338
339
340
341
342
343 @Benchmark
344 public void norm2D(final VectorInput2D input, final Blackhole bh) {
345 testToDouble(input, bh, Vector2D::norm);
346 }
347
348
349
350
351
352 @Benchmark
353 public void norm3D(final VectorInput3D input, final Blackhole bh) {
354 testToDouble(input, bh, Vector3D::norm);
355 }
356
357
358
359
360
361 @Benchmark
362 public void normalize1D(final NormalizableVectorInput1D input, final Blackhole bh) {
363 testUnary(input, bh, Vector1D::normalize);
364 }
365
366
367
368
369
370
371 @Benchmark
372 public void normalize2D(final NormalizableVectorInput2D input, final Blackhole bh) {
373 testUnary(input, bh, Vector2D::normalize);
374 }
375
376
377
378
379
380
381 @Benchmark
382 public void normalize3D(final NormalizableVectorInput3D input, final Blackhole bh) {
383 testUnary(input, bh, Vector3D::normalize);
384 }
385 }