Insights into performance gains of tuning min/max validators #174
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Context
While there is a wide range of tradeoffs between code readability/maintainability and performance it would be interesting to look at how much speed is given up with different levels of readability.
This PR offers a simple test-case-like setup that allows do have some ball-park numbers to make discussion more concrete. Also by no means is this test suite complete or precise.
Methodology
Base case
Since there was no isolated environment to do the benchmarking on, I opted for benchmarking with respect to some "base" cases. Base cases were chosen as comparing two
DoubleNode
and comparing twoLongNode
.Average time
Time is measured as difference between start and end of
ThresholdMixin
execution, and averaged over number of executions. Then average time for a particular case is compared to average time of a corresponding base caseCases
Each combination of threshold and value is measured separately. Measurement for each combination is treated equally, which might not be true for a particular production setup, i.e. if numbers in schema/payload were always generated without quotes and for values within 64-bit range cases with
double
/double
andinteger
/integer
comparison would account for majority of use-cases.Test values
Same numbers were used across multiple tests. Numbers were set to be within 64bit range. See Insights section for more.
Considered cases
Which cases should we consider when talking about performance/readability spectrum? Before going into case let us describe some questions each of the cases would have to answer to be functionally correct:
What json schma
"type"
was provided ?Generally two are assumed:
"integer"
and"number"
What threshold value was provided?
Here we can talk of CPU-word long case (aka
long
/double
) and a number that are outside CPU-word length (akaBigInteger
/BigDecimal
).Case of quoted numeric value can easily be assumed as one of the above two cases, as threshold assignment happens at schema creation and does not generally affect the validation runtime.
What value node was provided for comparison?
There seems to be 5 major categories, 4 similar to ones in threshold aspect plus a quoted value. Since string parsing now contributes to validation runtime we need to include this separately.
case 1: blind type coercing
Essentially it would functionally correct to coerce any threshold to a
BigDecimal
do the very same thing to comparison value and then compare twoBigDecimal
values. Very readable, simple and clean.ThresholdMixin
is not really a necessity here. even less code! Performance comparable (might even slightly improve) compared to current setup.case 2: special treatment for
"integer"
type, current setupAssume that
"number"
type comparison is performed similarly to blind coercing and give special treatment to"integer"
. This already brings inThresholdMixin
into picture and given that threshold value can still be expressed as a floating point calls for a several sub-cases. Readability degraded. Same performance. Mixin could be refactored into it's own class, making code slightly less cluttered.case 3: special treatment for value types
Here each
"integer"
and"number"
each get their own mixin. At runtime mixin differentiates between values under/above 64-bit. Major speedup ~2x performance gain. Readability degraded similar to current case.case 4: Special treatment to
"types"
and threshold valuesHere a dedicated mixin is created based on combination of expected type and actual value provided for
minimum
/maximum
. Number of mixins grows up to 4-6 (depending on implementation), runtime code for each mixin still needs to differentiate between different value types. Readability is greatly reduced. Performance gain ~30% over the special treatment for each of value types caseInsights
After some examination it looks like most of performance penalty is payed when a
double
/long
value is serialized to string and then de-serialized back into some sort of numeric representation. Similar to this snippetBoth "big" number and textual values seem to perform better for such code. Also performance gains become more apparent as size of the value grows. Likely because bigger values usually take more symbols in a serialized form which then means a longer input for de-serializing the value.
Personal note
Not sure what is the major use case for light-4j frameworks. From where I am coming it looks like overwhelming majority of inputs is quoted numerics (
long
/double
values serialized with quotes around them), with a minor share of values under 32-bits that come in unquoted form.Reference
Sample result from my laptop (3.1 GHz Intel Core i7)
Base execution time (comparing two DoubleNodes) 36-59 ns
Base execution time (comparing two LongeNodes) 36-59 ns
or in terms of base performance