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