-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor common parts from the Rounding class into a separate 'round'…
… package (#11023) * Refactor common parts from the Rounding class into a separate 'round' package Signed-off-by: Ketan Verma <[email protected]> * Move RoundableTests from :server to :libs:opensearch-common module Signed-off-by: Ketan Verma <[email protected]> * Address PR comments Signed-off-by: Ketan Verma <[email protected]> * Replace assert with IllegalArgumentException for size checks Signed-off-by: Ketan Verma <[email protected]> --------- Signed-off-by: Ketan Verma <[email protected]>
- Loading branch information
Showing
10 changed files
with
279 additions
and
146 deletions.
There are no files selected for viewing
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
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
59 changes: 59 additions & 0 deletions
59
libs/common/src/main/java/org/opensearch/common/round/BidirectionalLinearSearcher.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,59 @@ | ||
/* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
*/ | ||
|
||
package org.opensearch.common.round; | ||
|
||
import org.opensearch.common.annotation.InternalApi; | ||
|
||
/** | ||
* It uses linear search on a sorted array of pre-computed round-down points. | ||
* For small inputs (≤ 64 elements), this can be much faster than binary search as it avoids the penalty of | ||
* branch mispredictions and pipeline stalls, and accesses memory sequentially. | ||
* | ||
* <p> | ||
* It uses "meet in the middle" linear search to avoid the worst case scenario when the desired element is present | ||
* at either side of the array. This is helpful for time-series data where velocity increases over time, so more | ||
* documents are likely to find a greater timestamp which is likely to be present on the right end of the array. | ||
* | ||
* @opensearch.internal | ||
*/ | ||
@InternalApi | ||
class BidirectionalLinearSearcher implements Roundable { | ||
private final long[] ascending; | ||
private final long[] descending; | ||
|
||
BidirectionalLinearSearcher(long[] values, int size) { | ||
if (size <= 0) { | ||
throw new IllegalArgumentException("at least one value must be present"); | ||
} | ||
|
||
int len = (size + 1) >>> 1; // rounded-up to handle odd number of values | ||
ascending = new long[len]; | ||
descending = new long[len]; | ||
|
||
for (int i = 0; i < len; i++) { | ||
ascending[i] = values[i]; | ||
descending[i] = values[size - i - 1]; | ||
} | ||
} | ||
|
||
@Override | ||
public long floor(long key) { | ||
int i = 0; | ||
for (; i < ascending.length; i++) { | ||
if (descending[i] <= key) { | ||
return descending[i]; | ||
} | ||
if (ascending[i] > key) { | ||
assert i > 0 : "key must be greater than or equal to " + ascending[0]; | ||
return ascending[i - 1]; | ||
} | ||
} | ||
return ascending[i - 1]; | ||
} | ||
} |
43 changes: 43 additions & 0 deletions
43
libs/common/src/main/java/org/opensearch/common/round/BinarySearcher.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,43 @@ | ||
/* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
*/ | ||
|
||
package org.opensearch.common.round; | ||
|
||
import org.opensearch.common.annotation.InternalApi; | ||
|
||
import java.util.Arrays; | ||
|
||
/** | ||
* It uses binary search on a sorted array of pre-computed round-down points. | ||
* | ||
* @opensearch.internal | ||
*/ | ||
@InternalApi | ||
class BinarySearcher implements Roundable { | ||
private final long[] values; | ||
private final int size; | ||
|
||
BinarySearcher(long[] values, int size) { | ||
if (size <= 0) { | ||
throw new IllegalArgumentException("at least one value must be present"); | ||
} | ||
|
||
this.values = values; | ||
this.size = size; | ||
} | ||
|
||
@Override | ||
public long floor(long key) { | ||
int idx = Arrays.binarySearch(values, 0, size, key); | ||
assert idx != -1 : "key must be greater than or equal to " + values[0]; | ||
if (idx < 0) { | ||
idx = -2 - idx; | ||
} | ||
return values[idx]; | ||
} | ||
} |
28 changes: 28 additions & 0 deletions
28
libs/common/src/main/java/org/opensearch/common/round/Roundable.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,28 @@ | ||
/* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
*/ | ||
|
||
package org.opensearch.common.round; | ||
|
||
import org.opensearch.common.annotation.InternalApi; | ||
|
||
/** | ||
* Interface to round-off values. | ||
* | ||
* @opensearch.internal | ||
*/ | ||
@InternalApi | ||
@FunctionalInterface | ||
public interface Roundable { | ||
/** | ||
* Returns the greatest lower bound of the given key. | ||
* In other words, it returns the largest value such that {@code value <= key}. | ||
* @param key to floor | ||
* @return the floored value | ||
*/ | ||
long floor(long key); | ||
} |
39 changes: 39 additions & 0 deletions
39
libs/common/src/main/java/org/opensearch/common/round/RoundableFactory.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,39 @@ | ||
/* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
*/ | ||
|
||
package org.opensearch.common.round; | ||
|
||
import org.opensearch.common.annotation.InternalApi; | ||
|
||
/** | ||
* Factory class to create and return the fastest implementation of {@link Roundable}. | ||
* | ||
* @opensearch.internal | ||
*/ | ||
@InternalApi | ||
public final class RoundableFactory { | ||
/** | ||
* The maximum limit up to which linear search is used, otherwise binary search is used. | ||
* This is because linear search is much faster on small arrays. | ||
* Benchmark results: <a href="https://github.com/opensearch-project/OpenSearch/pull/9727">PR #9727</a> | ||
*/ | ||
private static final int LINEAR_SEARCH_MAX_SIZE = 64; | ||
|
||
private RoundableFactory() {} | ||
|
||
/** | ||
* Creates and returns the fastest implementation of {@link Roundable}. | ||
*/ | ||
public static Roundable create(long[] values, int size) { | ||
if (size <= LINEAR_SEARCH_MAX_SIZE) { | ||
return new BidirectionalLinearSearcher(values, size); | ||
} else { | ||
return new BinarySearcher(values, size); | ||
} | ||
} | ||
} |
12 changes: 12 additions & 0 deletions
12
libs/common/src/main/java/org/opensearch/common/round/package-info.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,12 @@ | ||
/* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
*/ | ||
|
||
/** | ||
* Contains classes to round-off values. | ||
*/ | ||
package org.opensearch.common.round; |
57 changes: 57 additions & 0 deletions
57
libs/common/src/test/java/org/opensearch/common/round/RoundableTests.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,57 @@ | ||
/* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
*/ | ||
|
||
package org.opensearch.common.round; | ||
|
||
import org.opensearch.test.OpenSearchTestCase; | ||
|
||
public class RoundableTests extends OpenSearchTestCase { | ||
|
||
public void testFloor() { | ||
int size = randomIntBetween(1, 256); | ||
long[] values = new long[size]; | ||
for (int i = 1; i < values.length; i++) { | ||
values[i] = values[i - 1] + (randomNonNegativeLong() % 200) + 1; | ||
} | ||
|
||
Roundable[] impls = { new BinarySearcher(values, size), new BidirectionalLinearSearcher(values, size) }; | ||
|
||
for (int i = 0; i < 100000; i++) { | ||
// Index of the expected round-down point. | ||
int idx = randomIntBetween(0, size - 1); | ||
|
||
// Value of the expected round-down point. | ||
long expected = values[idx]; | ||
|
||
// Delta between the expected and the next round-down point. | ||
long delta = (idx < size - 1) ? (values[idx + 1] - values[idx]) : 200; | ||
|
||
// Adding a random delta between 0 (inclusive) and delta (exclusive) to the expected | ||
// round-down point, which will still floor to the same value. | ||
long key = expected + (randomNonNegativeLong() % delta); | ||
|
||
for (Roundable roundable : impls) { | ||
assertEquals(expected, roundable.floor(key)); | ||
} | ||
} | ||
} | ||
|
||
public void testFailureCases() { | ||
Throwable throwable; | ||
|
||
throwable = assertThrows(IllegalArgumentException.class, () -> new BinarySearcher(new long[0], 0)); | ||
assertEquals("at least one value must be present", throwable.getMessage()); | ||
throwable = assertThrows(IllegalArgumentException.class, () -> new BidirectionalLinearSearcher(new long[0], 0)); | ||
assertEquals("at least one value must be present", throwable.getMessage()); | ||
|
||
throwable = assertThrows(AssertionError.class, () -> new BinarySearcher(new long[] { 100 }, 1).floor(50)); | ||
assertEquals("key must be greater than or equal to 100", throwable.getMessage()); | ||
throwable = assertThrows(AssertionError.class, () -> new BidirectionalLinearSearcher(new long[] { 100 }, 1).floor(50)); | ||
assertEquals("key must be greater than or equal to 100", throwable.getMessage()); | ||
} | ||
} |
Oops, something went wrong.