-
Notifications
You must be signed in to change notification settings - Fork 320
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #174 from kosty/issue/157
Insights into performance gains of tuning min/max validators
- Loading branch information
Showing
1 changed file
with
332 additions
and
0 deletions.
There are no files selected for viewing
332 changes: 332 additions & 0 deletions
332
src/test/java/com/networknt/schema/ThresholdMixinPerfTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,332 @@ | ||
package com.networknt.schema; | ||
|
||
import com.fasterxml.jackson.databind.JsonNode; | ||
import com.fasterxml.jackson.databind.node.BigIntegerNode; | ||
import com.fasterxml.jackson.databind.node.DecimalNode; | ||
import com.fasterxml.jackson.databind.node.DoubleNode; | ||
import com.fasterxml.jackson.databind.node.LongNode; | ||
import com.fasterxml.jackson.databind.node.TextNode; | ||
import org.junit.Before; | ||
import org.junit.Ignore; | ||
import org.junit.Test; | ||
|
||
import java.math.BigDecimal; | ||
import java.math.BigInteger; | ||
|
||
import static java.lang.String.format; | ||
import static java.lang.System.out; | ||
|
||
@Ignore | ||
public class ThresholdMixinPerfTest { | ||
private static long thresholdIntegral = Long.MAX_VALUE - 1; | ||
|
||
|
||
private final LongNode maximumLong = new LongNode(thresholdIntegral); | ||
private final BigIntegerNode maximumBigInt = new BigIntegerNode(BigInteger.valueOf(thresholdIntegral)); | ||
|
||
private final LongNode valueLong = new LongNode(Long.MAX_VALUE); | ||
private final BigIntegerNode valueBigInt = new BigIntegerNode(BigInteger.valueOf(Long.MAX_VALUE)); | ||
|
||
// private final double threshold = Double.MAX_VALUE - 1; | ||
private final double threshold = 1797693.134E+5D; | ||
private final DoubleNode maximumDouble = new DoubleNode(threshold); | ||
private final DecimalNode maximumDecimal = new DecimalNode(BigDecimal.valueOf(threshold)); | ||
|
||
private final double value = threshold+1; | ||
private final DoubleNode valueDouble = new DoubleNode(value); | ||
private final DecimalNode valueDecimal = new DecimalNode(new BigDecimal(value)); | ||
private final TextNode valueTextual = new TextNode(String.valueOf(value)); | ||
|
||
private final String maximumText = maximumDouble.asText(); | ||
private final BigDecimal max = new BigDecimal(maximumText); | ||
|
||
private final int executeTimes = 200000; | ||
private final boolean excludeEqual = false; | ||
|
||
double baseTimeForDouble; | ||
private double baseTimeForLong; | ||
|
||
@Before | ||
public void baseTimeEstimate() { | ||
baseTimeForDouble = getAvgTimeViaMixin(asDouble, valueDouble, executeTimes); | ||
out.println(format("Base execution time (comparing two DoubleNodes) %f ns", baseTimeForDouble)); | ||
|
||
baseTimeForLong = getAvgTimeViaMixin(asLong, valueLong, executeTimes); | ||
out.println(format("Base execution time (comparing two LongeNodes) %f ns \n", baseTimeForDouble)); | ||
} | ||
|
||
@Test | ||
public void currentTimeEstimate() { | ||
out.println("Estimating time for current implementation:"); | ||
double currentAvgTimeOnDouble = getAvgTimeViaMixin(currentImplementationDouble, valueDouble, executeTimes); | ||
out.println(format("Current double on double execution time %f ns, %f times slower", currentAvgTimeOnDouble, (currentAvgTimeOnDouble / baseTimeForDouble))); | ||
|
||
double currentAvgTimeOnDecimal = getAvgTimeViaMixin(currentImplementationDouble, valueDecimal, executeTimes); | ||
out.println(format("Current double on decimal execution time %f ns, %f times slower", currentAvgTimeOnDecimal, (currentAvgTimeOnDecimal / baseTimeForDouble))); | ||
|
||
double currentAvgTimeOnText = getAvgTimeViaMixin(currentImplementationDouble, valueTextual, executeTimes); | ||
out.println(format("Current double on text execution time %f ns, %f times slower", currentAvgTimeOnText, (currentAvgTimeOnText / baseTimeForDouble))); | ||
|
||
double currentAvgTimeDecimalOnDouble = getAvgTimeViaMixin(currentImplementationDecimal, valueDouble, executeTimes); | ||
out.println(format("Current decimal on double execution time %f ns, %f times slower", currentAvgTimeDecimalOnDouble, (currentAvgTimeDecimalOnDouble / baseTimeForDouble))); | ||
|
||
double currentAvgTimeDecimalOnDecimal = getAvgTimeViaMixin(currentImplementationDecimal, valueDecimal, executeTimes); | ||
out.println(format("Current decimal on decimal execution time %f ns, %f times slower", currentAvgTimeDecimalOnDecimal, (currentAvgTimeDecimalOnDecimal / baseTimeForDouble))); | ||
|
||
double currentAvgTimeDecimalOnText = getAvgTimeViaMixin(currentImplementationDecimal, valueTextual, executeTimes); | ||
out.println(format("Current decimal on text execution time %f ns, %f times slower", currentAvgTimeDecimalOnText, (currentAvgTimeDecimalOnText / baseTimeForDouble))); | ||
|
||
out.println(format("Cumulative average: %f\n\n", (currentAvgTimeOnDouble + currentAvgTimeOnDecimal + currentAvgTimeOnText + currentAvgTimeDecimalOnDouble + currentAvgTimeDecimalOnDecimal + currentAvgTimeDecimalOnText) / 6.0d)); | ||
} | ||
|
||
@Test | ||
public void allInOneAproachTimeEstimate() { | ||
out.println("Estimating time threshold value agnostic mixin (aka allInOne):"); | ||
double allInOneDoubleOnDouble = getAvgTimeViaMixin(allInOneDouble, valueDouble, executeTimes); | ||
out.println(format("AllInOne double on double execution time %f ns, %f times slower", allInOneDoubleOnDouble, (allInOneDoubleOnDouble/ baseTimeForDouble))); | ||
|
||
double allInOneDoubleOnDecimal = getAvgTimeViaMixin(allInOneDouble, valueDecimal, executeTimes); | ||
out.println(format("AllInOne double on decimal execution time %f ns, %f times slower", allInOneDoubleOnDecimal, (allInOneDoubleOnDecimal/ baseTimeForDouble))); | ||
|
||
double allInOneDoubleOnText = getAvgTimeViaMixin(allInOneDouble, valueTextual, executeTimes); | ||
out.println(format("AllInOne double on text execution time %f ns, %f times slower", allInOneDoubleOnText, (allInOneDoubleOnText/ baseTimeForDouble))); | ||
|
||
double allInOneDecimalOnDouble = getAvgTimeViaMixin(allInOneDecimal, valueDouble, executeTimes); | ||
out.println(format("AllInOne decimal on double execution time %f ns, %f times slower", allInOneDecimalOnDouble, (allInOneDecimalOnDouble/ baseTimeForDouble))); | ||
|
||
double allInOneDecimalOnDecimal = getAvgTimeViaMixin(allInOneDecimal, valueDecimal, executeTimes); | ||
out.println(format("AllInOne decimal on decimal execution time %f ns, %f times slower", allInOneDecimalOnDecimal, (allInOneDecimalOnDecimal/ baseTimeForDouble))); | ||
|
||
double allInOneDecimalOnText = getAvgTimeViaMixin(allInOneDecimal, valueTextual, executeTimes); | ||
out.println(format("AllInOne decimal on text execution time %f ns, %f times slower", allInOneDecimalOnText, (allInOneDecimalOnText/ baseTimeForDouble))); | ||
|
||
out.println(format("Cumulative average: %f\n\n", (allInOneDoubleOnDouble+allInOneDoubleOnDecimal+allInOneDoubleOnText+allInOneDecimalOnDouble+allInOneDecimalOnDecimal+allInOneDecimalOnText)/6.0d)); | ||
} | ||
|
||
@Test | ||
public void specificCaseForEachThresholdValue() { | ||
out.println("Estimating time for specific cases:"); | ||
double doubleValueAvgTime = getAvgTimeViaMixin(typedThreshold, valueDouble, executeTimes); | ||
out.println(format("Typed threshold execution time %f ns, %f times slower", doubleValueAvgTime, (doubleValueAvgTime/ baseTimeForDouble))); | ||
|
||
double decimalValueAvgTime = getAvgTimeViaMixin(typedThreshold, valueDecimal, executeTimes); | ||
out.println(format("Typed threshold execution time %f ns, %f times slower", decimalValueAvgTime, (decimalValueAvgTime/ baseTimeForDouble))); | ||
|
||
double textValueAvgTime = getAvgTimeViaMixin(typedThreshold, valueTextual, executeTimes); | ||
out.println(format("Typed threshold execution time %f ns, %f times slower", textValueAvgTime, (textValueAvgTime/ baseTimeForDouble))); | ||
|
||
out.println(format("Cumulative average: %f\n\n", (doubleValueAvgTime+decimalValueAvgTime+textValueAvgTime)/3.0d)); | ||
} | ||
|
||
@Test | ||
public void noMixinsFloatingTimeEstimate() { | ||
out.println("Estimating time no mixins at all (floating point values):"); | ||
double doubleValueAvgTime = getAvgTimeViaMixin(oneMixinForIntegerAndNumber, valueDecimal, executeTimes); | ||
out.println(format("No mixins with double value time %f ns, %f times slower", doubleValueAvgTime, (doubleValueAvgTime/ baseTimeForDouble))); | ||
|
||
double decimalValueAvgTime = getAvgTimeViaMixin(oneMixinForIntegerAndNumber, valueDecimal, executeTimes); | ||
out.println(format("No mixins with decimal value time %f ns, %f times slower", decimalValueAvgTime, (decimalValueAvgTime/ baseTimeForDouble))); | ||
|
||
double textValueAvgTime = getAvgTimeViaMixin(oneMixinForIntegerAndNumber, valueTextual, executeTimes); | ||
out.println(format("No mixins with text value time %f ns, %f times slower", textValueAvgTime, (textValueAvgTime/ baseTimeForDouble))); | ||
out.println(format("Cumulative average: %f\n\n", | ||
(doubleValueAvgTime+decimalValueAvgTime+textValueAvgTime)/3.0d)); | ||
} | ||
|
||
@Test | ||
public void noMixinsIntegralTimeEstimate() { | ||
double longValueAvgTime = getAvgTimeViaMixin(oneMixinForIntegerAndNumber, new LongNode((long)value), executeTimes); | ||
out.println(format("No mixins with long value time %f ns, %f times slower", longValueAvgTime, (longValueAvgTime/ baseTimeForLong))); | ||
|
||
double bigIntValueAvgTime = getAvgTimeViaMixin(oneMixinForIntegerAndNumber, new BigIntegerNode(BigInteger.valueOf((long)value)), executeTimes); | ||
out.println(format("No mixins with big int value time %f ns, %f times slower", bigIntValueAvgTime, (bigIntValueAvgTime/ baseTimeForLong))); | ||
|
||
double textIntValueAvgTime = getAvgTimeViaMixin(oneMixinForIntegerAndNumber, new TextNode(String.valueOf((long)value)), executeTimes); | ||
out.println(format("No mixins with text value time %f ns, %f times slower", textIntValueAvgTime, (textIntValueAvgTime/ baseTimeForLong))); | ||
out.println(format("Cumulative average: %f\n\n", | ||
(longValueAvgTime+bigIntValueAvgTime+textIntValueAvgTime)/3.0d)); | ||
} | ||
|
||
ThresholdMixin allInOneDouble = new AllInOneThreshold(maximumDouble, false); | ||
ThresholdMixin allInOneDecimal = new AllInOneThreshold(maximumDecimal, false); | ||
|
||
public static class AllInOneThreshold implements ThresholdMixin { | ||
|
||
private final BigDecimal bigDecimalMax; | ||
JsonNode maximum; | ||
private boolean excludeEqual; | ||
|
||
AllInOneThreshold(JsonNode maximum, boolean exludeEqual){ | ||
this.maximum = maximum; | ||
this.excludeEqual = exludeEqual; | ||
this.bigDecimalMax = new BigDecimal(maximum.asText()); | ||
} | ||
|
||
@Override | ||
public boolean crossesThreshold(JsonNode node) { | ||
if (maximum.isDouble() && maximum.doubleValue() == Double.POSITIVE_INFINITY) { | ||
return false; | ||
} | ||
if (maximum.isDouble() && maximum.doubleValue() == Double.NEGATIVE_INFINITY) { | ||
return true; | ||
} | ||
if (maximum.isDouble() && node.isDouble()) { | ||
double lm = maximum.doubleValue(); | ||
double val = node.doubleValue(); | ||
return lm < val || (excludeEqual && lm == val); | ||
} | ||
|
||
if (maximum.isFloatingPointNumber() && node.isFloatingPointNumber()) { | ||
BigDecimal value = node.decimalValue(); | ||
int compare = value.compareTo(bigDecimalMax); | ||
return compare > 0 || (excludeEqual && compare == 0); | ||
} | ||
|
||
BigDecimal value = new BigDecimal(node.asText()); | ||
int compare = value.compareTo(bigDecimalMax); | ||
return compare > 0 || (excludeEqual && compare == 0); | ||
} | ||
|
||
@Override | ||
public String thresholdValue() { | ||
return maximum.asText(); | ||
} | ||
}; | ||
|
||
ThresholdMixin asDouble = new ThresholdMixin() { | ||
@Override | ||
public boolean crossesThreshold(JsonNode node) { | ||
double lm = maximumDouble.doubleValue(); | ||
double val = node.doubleValue(); | ||
return lm < val || (excludeEqual && lm == val); | ||
} | ||
|
||
@Override | ||
public String thresholdValue() { | ||
return maximumText; | ||
} | ||
}; | ||
|
||
ThresholdMixin asLong = new ThresholdMixin() { | ||
@Override | ||
public boolean crossesThreshold(JsonNode node) { | ||
long lm = maximumLong.longValue(); | ||
long val = node.longValue(); | ||
return lm < val || (excludeEqual && lm == val); | ||
} | ||
|
||
@Override | ||
public String thresholdValue() { | ||
return maximumText; | ||
} | ||
}; | ||
|
||
ThresholdMixin typedThreshold = new ThresholdMixin() { | ||
@Override | ||
public boolean crossesThreshold(JsonNode node) { | ||
if (node.isDouble()) { | ||
double lm = maximumDouble.doubleValue(); | ||
double val = node.doubleValue(); | ||
return lm < val || (excludeEqual && lm == val); | ||
} | ||
|
||
if (node.isBigDecimal()) { | ||
BigDecimal value = node.decimalValue(); | ||
int compare = value.compareTo(max); | ||
return compare > 0 || (excludeEqual && compare == 0); | ||
} | ||
|
||
BigDecimal value = new BigDecimal(node.asText()); | ||
int compare = value.compareTo(max); | ||
return compare > 0 || (excludeEqual && compare == 0); | ||
} | ||
|
||
@Override | ||
public String thresholdValue() { | ||
return maximumText; | ||
} | ||
}; | ||
|
||
ThresholdMixin currentImplementationDouble = new ThresholdMixin() { | ||
@Override | ||
public boolean crossesThreshold(JsonNode node) { | ||
if (maximumDouble.isDouble() && maximumDouble.doubleValue() == Double.POSITIVE_INFINITY) { | ||
return false; | ||
} | ||
if (maximumDouble.isDouble() && maximumDouble.doubleValue() == Double.NEGATIVE_INFINITY) { | ||
return true; | ||
} | ||
if (node.isDouble() && node.doubleValue() == Double.NEGATIVE_INFINITY) { | ||
return false; | ||
} | ||
if (node.isDouble() && node.doubleValue() == Double.POSITIVE_INFINITY) { | ||
return true; | ||
} | ||
final BigDecimal max = new BigDecimal(maximumText); | ||
BigDecimal value = new BigDecimal(node.asText()); | ||
int compare = value.compareTo(max); | ||
return compare > 0 || (excludeEqual && compare == 0); | ||
} | ||
|
||
@Override | ||
public String thresholdValue() { | ||
return maximumText; | ||
} | ||
}; | ||
|
||
|
||
ThresholdMixin currentImplementationDecimal = new ThresholdMixin() { | ||
@Override | ||
public boolean crossesThreshold(JsonNode node) { | ||
if (maximumDecimal.isDouble() && maximumDecimal.doubleValue() == Double.POSITIVE_INFINITY) { | ||
return false; | ||
} | ||
if (maximumDecimal.isDouble() && maximumDecimal.doubleValue() == Double.NEGATIVE_INFINITY) { | ||
return true; | ||
} | ||
if (node.isDouble() && node.doubleValue() == Double.NEGATIVE_INFINITY) { | ||
return false; | ||
} | ||
if (node.isDouble() && node.doubleValue() == Double.POSITIVE_INFINITY) { | ||
return true; | ||
} | ||
final BigDecimal max = new BigDecimal(maximumText); | ||
BigDecimal value = new BigDecimal(node.asText()); | ||
int compare = value.compareTo(max); | ||
return compare > 0 || (excludeEqual && compare == 0); | ||
} | ||
|
||
@Override | ||
public String thresholdValue() { | ||
return maximumText; | ||
} | ||
}; | ||
|
||
ThresholdMixin oneMixinForIntegerAndNumber = new ThresholdMixin() { | ||
@Override | ||
public boolean crossesThreshold(JsonNode node) { | ||
BigDecimal value = new BigDecimal(node.asText()); | ||
int compare = value.compareTo(max); | ||
return compare > 0 || (excludeEqual && compare == 0); | ||
} | ||
|
||
@Override | ||
public String thresholdValue() { | ||
return null; | ||
} | ||
}; | ||
|
||
private double getAvgTimeViaMixin(ThresholdMixin mixin, JsonNode value, int iterations) { | ||
boolean excludeEqual = false; | ||
long totalTime = 0; | ||
for(int i = 0; i < iterations; i++) { | ||
long start = System.nanoTime(); | ||
try { | ||
mixin.crossesThreshold(value); | ||
} finally { | ||
totalTime += System.nanoTime() - start; | ||
} | ||
} | ||
return totalTime / (iterations * 1.0D); | ||
} | ||
} |