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