Skip to content

Commit 6c23a14

Browse files
authored
Merge pull request #174 from kosty/issue/157
Insights into performance gains of tuning min/max validators
2 parents 413b4b7 + 10bb35a commit 6c23a14

File tree

1 file changed

+332
-0
lines changed

1 file changed

+332
-0
lines changed
Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
package com.networknt.schema;
2+
3+
import com.fasterxml.jackson.databind.JsonNode;
4+
import com.fasterxml.jackson.databind.node.BigIntegerNode;
5+
import com.fasterxml.jackson.databind.node.DecimalNode;
6+
import com.fasterxml.jackson.databind.node.DoubleNode;
7+
import com.fasterxml.jackson.databind.node.LongNode;
8+
import com.fasterxml.jackson.databind.node.TextNode;
9+
import org.junit.Before;
10+
import org.junit.Ignore;
11+
import org.junit.Test;
12+
13+
import java.math.BigDecimal;
14+
import java.math.BigInteger;
15+
16+
import static java.lang.String.format;
17+
import static java.lang.System.out;
18+
19+
@Ignore
20+
public class ThresholdMixinPerfTest {
21+
private static long thresholdIntegral = Long.MAX_VALUE - 1;
22+
23+
24+
private final LongNode maximumLong = new LongNode(thresholdIntegral);
25+
private final BigIntegerNode maximumBigInt = new BigIntegerNode(BigInteger.valueOf(thresholdIntegral));
26+
27+
private final LongNode valueLong = new LongNode(Long.MAX_VALUE);
28+
private final BigIntegerNode valueBigInt = new BigIntegerNode(BigInteger.valueOf(Long.MAX_VALUE));
29+
30+
// private final double threshold = Double.MAX_VALUE - 1;
31+
private final double threshold = 1797693.134E+5D;
32+
private final DoubleNode maximumDouble = new DoubleNode(threshold);
33+
private final DecimalNode maximumDecimal = new DecimalNode(BigDecimal.valueOf(threshold));
34+
35+
private final double value = threshold+1;
36+
private final DoubleNode valueDouble = new DoubleNode(value);
37+
private final DecimalNode valueDecimal = new DecimalNode(new BigDecimal(value));
38+
private final TextNode valueTextual = new TextNode(String.valueOf(value));
39+
40+
private final String maximumText = maximumDouble.asText();
41+
private final BigDecimal max = new BigDecimal(maximumText);
42+
43+
private final int executeTimes = 200000;
44+
private final boolean excludeEqual = false;
45+
46+
double baseTimeForDouble;
47+
private double baseTimeForLong;
48+
49+
@Before
50+
public void baseTimeEstimate() {
51+
baseTimeForDouble = getAvgTimeViaMixin(asDouble, valueDouble, executeTimes);
52+
out.println(format("Base execution time (comparing two DoubleNodes) %f ns", baseTimeForDouble));
53+
54+
baseTimeForLong = getAvgTimeViaMixin(asLong, valueLong, executeTimes);
55+
out.println(format("Base execution time (comparing two LongeNodes) %f ns \n", baseTimeForDouble));
56+
}
57+
58+
@Test
59+
public void currentTimeEstimate() {
60+
out.println("Estimating time for current implementation:");
61+
double currentAvgTimeOnDouble = getAvgTimeViaMixin(currentImplementationDouble, valueDouble, executeTimes);
62+
out.println(format("Current double on double execution time %f ns, %f times slower", currentAvgTimeOnDouble, (currentAvgTimeOnDouble / baseTimeForDouble)));
63+
64+
double currentAvgTimeOnDecimal = getAvgTimeViaMixin(currentImplementationDouble, valueDecimal, executeTimes);
65+
out.println(format("Current double on decimal execution time %f ns, %f times slower", currentAvgTimeOnDecimal, (currentAvgTimeOnDecimal / baseTimeForDouble)));
66+
67+
double currentAvgTimeOnText = getAvgTimeViaMixin(currentImplementationDouble, valueTextual, executeTimes);
68+
out.println(format("Current double on text execution time %f ns, %f times slower", currentAvgTimeOnText, (currentAvgTimeOnText / baseTimeForDouble)));
69+
70+
double currentAvgTimeDecimalOnDouble = getAvgTimeViaMixin(currentImplementationDecimal, valueDouble, executeTimes);
71+
out.println(format("Current decimal on double execution time %f ns, %f times slower", currentAvgTimeDecimalOnDouble, (currentAvgTimeDecimalOnDouble / baseTimeForDouble)));
72+
73+
double currentAvgTimeDecimalOnDecimal = getAvgTimeViaMixin(currentImplementationDecimal, valueDecimal, executeTimes);
74+
out.println(format("Current decimal on decimal execution time %f ns, %f times slower", currentAvgTimeDecimalOnDecimal, (currentAvgTimeDecimalOnDecimal / baseTimeForDouble)));
75+
76+
double currentAvgTimeDecimalOnText = getAvgTimeViaMixin(currentImplementationDecimal, valueTextual, executeTimes);
77+
out.println(format("Current decimal on text execution time %f ns, %f times slower", currentAvgTimeDecimalOnText, (currentAvgTimeDecimalOnText / baseTimeForDouble)));
78+
79+
out.println(format("Cumulative average: %f\n\n", (currentAvgTimeOnDouble + currentAvgTimeOnDecimal + currentAvgTimeOnText + currentAvgTimeDecimalOnDouble + currentAvgTimeDecimalOnDecimal + currentAvgTimeDecimalOnText) / 6.0d));
80+
}
81+
82+
@Test
83+
public void allInOneAproachTimeEstimate() {
84+
out.println("Estimating time threshold value agnostic mixin (aka allInOne):");
85+
double allInOneDoubleOnDouble = getAvgTimeViaMixin(allInOneDouble, valueDouble, executeTimes);
86+
out.println(format("AllInOne double on double execution time %f ns, %f times slower", allInOneDoubleOnDouble, (allInOneDoubleOnDouble/ baseTimeForDouble)));
87+
88+
double allInOneDoubleOnDecimal = getAvgTimeViaMixin(allInOneDouble, valueDecimal, executeTimes);
89+
out.println(format("AllInOne double on decimal execution time %f ns, %f times slower", allInOneDoubleOnDecimal, (allInOneDoubleOnDecimal/ baseTimeForDouble)));
90+
91+
double allInOneDoubleOnText = getAvgTimeViaMixin(allInOneDouble, valueTextual, executeTimes);
92+
out.println(format("AllInOne double on text execution time %f ns, %f times slower", allInOneDoubleOnText, (allInOneDoubleOnText/ baseTimeForDouble)));
93+
94+
double allInOneDecimalOnDouble = getAvgTimeViaMixin(allInOneDecimal, valueDouble, executeTimes);
95+
out.println(format("AllInOne decimal on double execution time %f ns, %f times slower", allInOneDecimalOnDouble, (allInOneDecimalOnDouble/ baseTimeForDouble)));
96+
97+
double allInOneDecimalOnDecimal = getAvgTimeViaMixin(allInOneDecimal, valueDecimal, executeTimes);
98+
out.println(format("AllInOne decimal on decimal execution time %f ns, %f times slower", allInOneDecimalOnDecimal, (allInOneDecimalOnDecimal/ baseTimeForDouble)));
99+
100+
double allInOneDecimalOnText = getAvgTimeViaMixin(allInOneDecimal, valueTextual, executeTimes);
101+
out.println(format("AllInOne decimal on text execution time %f ns, %f times slower", allInOneDecimalOnText, (allInOneDecimalOnText/ baseTimeForDouble)));
102+
103+
out.println(format("Cumulative average: %f\n\n", (allInOneDoubleOnDouble+allInOneDoubleOnDecimal+allInOneDoubleOnText+allInOneDecimalOnDouble+allInOneDecimalOnDecimal+allInOneDecimalOnText)/6.0d));
104+
}
105+
106+
@Test
107+
public void specificCaseForEachThresholdValue() {
108+
out.println("Estimating time for specific cases:");
109+
double doubleValueAvgTime = getAvgTimeViaMixin(typedThreshold, valueDouble, executeTimes);
110+
out.println(format("Typed threshold execution time %f ns, %f times slower", doubleValueAvgTime, (doubleValueAvgTime/ baseTimeForDouble)));
111+
112+
double decimalValueAvgTime = getAvgTimeViaMixin(typedThreshold, valueDecimal, executeTimes);
113+
out.println(format("Typed threshold execution time %f ns, %f times slower", decimalValueAvgTime, (decimalValueAvgTime/ baseTimeForDouble)));
114+
115+
double textValueAvgTime = getAvgTimeViaMixin(typedThreshold, valueTextual, executeTimes);
116+
out.println(format("Typed threshold execution time %f ns, %f times slower", textValueAvgTime, (textValueAvgTime/ baseTimeForDouble)));
117+
118+
out.println(format("Cumulative average: %f\n\n", (doubleValueAvgTime+decimalValueAvgTime+textValueAvgTime)/3.0d));
119+
}
120+
121+
@Test
122+
public void noMixinsFloatingTimeEstimate() {
123+
out.println("Estimating time no mixins at all (floating point values):");
124+
double doubleValueAvgTime = getAvgTimeViaMixin(oneMixinForIntegerAndNumber, valueDecimal, executeTimes);
125+
out.println(format("No mixins with double value time %f ns, %f times slower", doubleValueAvgTime, (doubleValueAvgTime/ baseTimeForDouble)));
126+
127+
double decimalValueAvgTime = getAvgTimeViaMixin(oneMixinForIntegerAndNumber, valueDecimal, executeTimes);
128+
out.println(format("No mixins with decimal value time %f ns, %f times slower", decimalValueAvgTime, (decimalValueAvgTime/ baseTimeForDouble)));
129+
130+
double textValueAvgTime = getAvgTimeViaMixin(oneMixinForIntegerAndNumber, valueTextual, executeTimes);
131+
out.println(format("No mixins with text value time %f ns, %f times slower", textValueAvgTime, (textValueAvgTime/ baseTimeForDouble)));
132+
out.println(format("Cumulative average: %f\n\n",
133+
(doubleValueAvgTime+decimalValueAvgTime+textValueAvgTime)/3.0d));
134+
}
135+
136+
@Test
137+
public void noMixinsIntegralTimeEstimate() {
138+
double longValueAvgTime = getAvgTimeViaMixin(oneMixinForIntegerAndNumber, new LongNode((long)value), executeTimes);
139+
out.println(format("No mixins with long value time %f ns, %f times slower", longValueAvgTime, (longValueAvgTime/ baseTimeForLong)));
140+
141+
double bigIntValueAvgTime = getAvgTimeViaMixin(oneMixinForIntegerAndNumber, new BigIntegerNode(BigInteger.valueOf((long)value)), executeTimes);
142+
out.println(format("No mixins with big int value time %f ns, %f times slower", bigIntValueAvgTime, (bigIntValueAvgTime/ baseTimeForLong)));
143+
144+
double textIntValueAvgTime = getAvgTimeViaMixin(oneMixinForIntegerAndNumber, new TextNode(String.valueOf((long)value)), executeTimes);
145+
out.println(format("No mixins with text value time %f ns, %f times slower", textIntValueAvgTime, (textIntValueAvgTime/ baseTimeForLong)));
146+
out.println(format("Cumulative average: %f\n\n",
147+
(longValueAvgTime+bigIntValueAvgTime+textIntValueAvgTime)/3.0d));
148+
}
149+
150+
ThresholdMixin allInOneDouble = new AllInOneThreshold(maximumDouble, false);
151+
ThresholdMixin allInOneDecimal = new AllInOneThreshold(maximumDecimal, false);
152+
153+
public static class AllInOneThreshold implements ThresholdMixin {
154+
155+
private final BigDecimal bigDecimalMax;
156+
JsonNode maximum;
157+
private boolean excludeEqual;
158+
159+
AllInOneThreshold(JsonNode maximum, boolean exludeEqual){
160+
this.maximum = maximum;
161+
this.excludeEqual = exludeEqual;
162+
this.bigDecimalMax = new BigDecimal(maximum.asText());
163+
}
164+
165+
@Override
166+
public boolean crossesThreshold(JsonNode node) {
167+
if (maximum.isDouble() && maximum.doubleValue() == Double.POSITIVE_INFINITY) {
168+
return false;
169+
}
170+
if (maximum.isDouble() && maximum.doubleValue() == Double.NEGATIVE_INFINITY) {
171+
return true;
172+
}
173+
if (maximum.isDouble() && node.isDouble()) {
174+
double lm = maximum.doubleValue();
175+
double val = node.doubleValue();
176+
return lm < val || (excludeEqual && lm == val);
177+
}
178+
179+
if (maximum.isFloatingPointNumber() && node.isFloatingPointNumber()) {
180+
BigDecimal value = node.decimalValue();
181+
int compare = value.compareTo(bigDecimalMax);
182+
return compare > 0 || (excludeEqual && compare == 0);
183+
}
184+
185+
BigDecimal value = new BigDecimal(node.asText());
186+
int compare = value.compareTo(bigDecimalMax);
187+
return compare > 0 || (excludeEqual && compare == 0);
188+
}
189+
190+
@Override
191+
public String thresholdValue() {
192+
return maximum.asText();
193+
}
194+
};
195+
196+
ThresholdMixin asDouble = new ThresholdMixin() {
197+
@Override
198+
public boolean crossesThreshold(JsonNode node) {
199+
double lm = maximumDouble.doubleValue();
200+
double val = node.doubleValue();
201+
return lm < val || (excludeEqual && lm == val);
202+
}
203+
204+
@Override
205+
public String thresholdValue() {
206+
return maximumText;
207+
}
208+
};
209+
210+
ThresholdMixin asLong = new ThresholdMixin() {
211+
@Override
212+
public boolean crossesThreshold(JsonNode node) {
213+
long lm = maximumLong.longValue();
214+
long val = node.longValue();
215+
return lm < val || (excludeEqual && lm == val);
216+
}
217+
218+
@Override
219+
public String thresholdValue() {
220+
return maximumText;
221+
}
222+
};
223+
224+
ThresholdMixin typedThreshold = new ThresholdMixin() {
225+
@Override
226+
public boolean crossesThreshold(JsonNode node) {
227+
if (node.isDouble()) {
228+
double lm = maximumDouble.doubleValue();
229+
double val = node.doubleValue();
230+
return lm < val || (excludeEqual && lm == val);
231+
}
232+
233+
if (node.isBigDecimal()) {
234+
BigDecimal value = node.decimalValue();
235+
int compare = value.compareTo(max);
236+
return compare > 0 || (excludeEqual && compare == 0);
237+
}
238+
239+
BigDecimal value = new BigDecimal(node.asText());
240+
int compare = value.compareTo(max);
241+
return compare > 0 || (excludeEqual && compare == 0);
242+
}
243+
244+
@Override
245+
public String thresholdValue() {
246+
return maximumText;
247+
}
248+
};
249+
250+
ThresholdMixin currentImplementationDouble = new ThresholdMixin() {
251+
@Override
252+
public boolean crossesThreshold(JsonNode node) {
253+
if (maximumDouble.isDouble() && maximumDouble.doubleValue() == Double.POSITIVE_INFINITY) {
254+
return false;
255+
}
256+
if (maximumDouble.isDouble() && maximumDouble.doubleValue() == Double.NEGATIVE_INFINITY) {
257+
return true;
258+
}
259+
if (node.isDouble() && node.doubleValue() == Double.NEGATIVE_INFINITY) {
260+
return false;
261+
}
262+
if (node.isDouble() && node.doubleValue() == Double.POSITIVE_INFINITY) {
263+
return true;
264+
}
265+
final BigDecimal max = new BigDecimal(maximumText);
266+
BigDecimal value = new BigDecimal(node.asText());
267+
int compare = value.compareTo(max);
268+
return compare > 0 || (excludeEqual && compare == 0);
269+
}
270+
271+
@Override
272+
public String thresholdValue() {
273+
return maximumText;
274+
}
275+
};
276+
277+
278+
ThresholdMixin currentImplementationDecimal = new ThresholdMixin() {
279+
@Override
280+
public boolean crossesThreshold(JsonNode node) {
281+
if (maximumDecimal.isDouble() && maximumDecimal.doubleValue() == Double.POSITIVE_INFINITY) {
282+
return false;
283+
}
284+
if (maximumDecimal.isDouble() && maximumDecimal.doubleValue() == Double.NEGATIVE_INFINITY) {
285+
return true;
286+
}
287+
if (node.isDouble() && node.doubleValue() == Double.NEGATIVE_INFINITY) {
288+
return false;
289+
}
290+
if (node.isDouble() && node.doubleValue() == Double.POSITIVE_INFINITY) {
291+
return true;
292+
}
293+
final BigDecimal max = new BigDecimal(maximumText);
294+
BigDecimal value = new BigDecimal(node.asText());
295+
int compare = value.compareTo(max);
296+
return compare > 0 || (excludeEqual && compare == 0);
297+
}
298+
299+
@Override
300+
public String thresholdValue() {
301+
return maximumText;
302+
}
303+
};
304+
305+
ThresholdMixin oneMixinForIntegerAndNumber = new ThresholdMixin() {
306+
@Override
307+
public boolean crossesThreshold(JsonNode node) {
308+
BigDecimal value = new BigDecimal(node.asText());
309+
int compare = value.compareTo(max);
310+
return compare > 0 || (excludeEqual && compare == 0);
311+
}
312+
313+
@Override
314+
public String thresholdValue() {
315+
return null;
316+
}
317+
};
318+
319+
private double getAvgTimeViaMixin(ThresholdMixin mixin, JsonNode value, int iterations) {
320+
boolean excludeEqual = false;
321+
long totalTime = 0;
322+
for(int i = 0; i < iterations; i++) {
323+
long start = System.nanoTime();
324+
try {
325+
mixin.crossesThreshold(value);
326+
} finally {
327+
totalTime += System.nanoTime() - start;
328+
}
329+
}
330+
return totalTime / (iterations * 1.0D);
331+
}
332+
}

0 commit comments

Comments
 (0)