diff --git a/velox/exec/CMakeLists.txt b/velox/exec/CMakeLists.txt index 6d12235d0311..99e58c51c50c 100644 --- a/velox/exec/CMakeLists.txt +++ b/velox/exec/CMakeLists.txt @@ -110,3 +110,5 @@ endif() if(${VELOX_ENABLE_BENCHMARKS}) add_subdirectory(benchmarks) endif() + +add_subdirectory(prefixsort) diff --git a/velox/exec/prefixsort/CMakeLists.txt b/velox/exec/prefixsort/CMakeLists.txt new file mode 100644 index 000000000000..767d312e2ac3 --- /dev/null +++ b/velox/exec/prefixsort/CMakeLists.txt @@ -0,0 +1,21 @@ +# Copyright (c) Facebook, Inc. and its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +if(${VELOX_BUILD_TESTING}) + add_subdirectory(tests) +endif() + +if(${VELOX_ENABLE_BENCHMARKS}) + add_subdirectory(benchmarks) +endif() diff --git a/velox/exec/prefixsort/PrefixSortAlgorithm.h b/velox/exec/prefixsort/PrefixSortAlgorithm.h new file mode 100644 index 000000000000..dd1ae4c5f00b --- /dev/null +++ b/velox/exec/prefixsort/PrefixSortAlgorithm.h @@ -0,0 +1,353 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include +#include + +#include "velox/common/base/Exceptions.h" +#include "velox/common/base/SimdUtil.h" + +namespace facebook::velox::exec::prefixsort { + +namespace detail { +/// Provides LegacyRandomAccessIterator but not ValueSwappable semantic +/// iterator for PrefixSort`s sort algorithm, see: +/// https://en.cppreference.com/w/cpp/algorithm/sort +/// https://en.cppreference.com/w/cpp/named_req/RandomAccessIterator +/// https://en.cppreference.com/w/cpp/named_req/ValueSwappable +/// The difference is operator*(), the LegacyInputIterator return "reference, +/// convertible to value_type", see: +/// https://en.cppreference.com/w/cpp/named_req/InputIterator +/// In contrast, PrefixSortIterator returns char* point to the prefix data +/// (not a "value_type" but a memory buffer store normalized keys) for memcmp +/// and memcpy operators, means not ValueSwappable. +/// This is also the reason we can not use std::sort directly, and implement +/// quickSort in PrefixSortRunner. +class PrefixSortIterator { + public: + PrefixSortIterator(char* prefix, const uint64_t entrySize) + : entrySize_(entrySize), prefix_(prefix) {} + + PrefixSortIterator(const PrefixSortIterator& other) + : entrySize_(other.entrySize_), prefix_(other.prefix_) {} + + // Inline functions should be placed in header file to avoid link error. + FOLLY_ALWAYS_INLINE char* operator*() const { + return prefix_; + } + + FOLLY_ALWAYS_INLINE PrefixSortIterator& operator++() { + prefix_ += entrySize_; + return *this; + } + + FOLLY_ALWAYS_INLINE PrefixSortIterator& operator--() { + prefix_ -= entrySize_; + return *this; + } + + FOLLY_ALWAYS_INLINE PrefixSortIterator operator++(int) { + const auto tmp = *this; + prefix_ += entrySize_; + return tmp; + } + + FOLLY_ALWAYS_INLINE PrefixSortIterator operator--(int) { + const auto tmp = *this; + prefix_ -= entrySize_; + return tmp; + } + + FOLLY_ALWAYS_INLINE PrefixSortIterator operator+(const uint64_t i) const { + auto result = *this; + result.prefix_ += i * entrySize_; + return result; + } + + FOLLY_ALWAYS_INLINE PrefixSortIterator operator-(const uint64_t i) const { + PrefixSortIterator result = *this; + result.prefix_ -= i * entrySize_; + return result; + } + + FOLLY_ALWAYS_INLINE PrefixSortIterator& operator=( + const PrefixSortIterator& other) { + prefix_ = other.prefix_; + return *this; + } + + FOLLY_ALWAYS_INLINE uint64_t + operator-(const PrefixSortIterator& other) const { + return (prefix_ - other.prefix_) / other.entrySize_; + } + + FOLLY_ALWAYS_INLINE bool operator<(const PrefixSortIterator& other) const { + return prefix_ < other.prefix_; + } + + FOLLY_ALWAYS_INLINE bool operator>(const PrefixSortIterator& other) const { + return prefix_ > other.prefix_; + } + + FOLLY_ALWAYS_INLINE bool operator>=(const PrefixSortIterator& other) const { + return prefix_ >= other.prefix_; + } + + FOLLY_ALWAYS_INLINE bool operator<=(const PrefixSortIterator& other) const { + return prefix_ <= other.prefix_; + } + + FOLLY_ALWAYS_INLINE bool operator==(const PrefixSortIterator& other) const { + return prefix_ == other.prefix_; + } + + FOLLY_ALWAYS_INLINE bool operator!=(const PrefixSortIterator& other) const { + return prefix_ != other.prefix_; + } + + private: + const uint64_t entrySize_; + char* prefix_; +}; +} // namespace detail + +/// Provides methods (mostly required by the sort +/// algorithm) for PrefixSort. Maintains the buffer for swap operations and +/// some necessary context information such as entrySize. +class PrefixSortRunner { + public: + /// @param swapBuffer The buffer must be at least entrySize bytes long. + PrefixSortRunner(uint64_t entrySize, char* swapBuffer) + : entrySize_(entrySize), swapBuffer_(swapBuffer) { + VELOX_CHECK_NOT_NULL(swapBuffer_); + } + + // Within quickSort, when the input data length < kSmallSort, use insert-sort + // in place of quick-sort. + // With finding a central element, there are extra comparisons. While + // this is cheap for large arrays, it is expensive for small arrays. + // Therefore, chooses the middle element of smaller arrays (=kSmallSort), + // the median of the first, middle and last elements of a mid-sized array + // (> kSmallSort and < kMediumSort), + // and the pseudo-median of nine evenly spaced elements of a large + // array(> kMediumSort). + // The two threshold values are given in the reference paper "Engineering a + // Sort Function". + static const int kSmallSort = 7; + static const int kMediumSort = 40; + + template + void quickSort(char* start, char* end, TCompare compare) const { + quickSort( + detail::PrefixSortIterator(start, entrySize_), + detail::PrefixSortIterator(end, entrySize_), + compare); + } + + /// For testing only. + template + FOLLY_ALWAYS_INLINE static char* testingMedian3( + char* a, + char* b, + char* c, + const uint64_t entrySize, + TCompare cmp) { + return *median3( + detail::PrefixSortIterator(a, entrySize), + detail::PrefixSortIterator(b, entrySize), + detail::PrefixSortIterator(c, entrySize), + cmp); + } + + private: + FOLLY_ALWAYS_INLINE void swap( + const detail::PrefixSortIterator& lhs, + const detail::PrefixSortIterator& rhs) const { + simd::memcpy(swapBuffer_, *lhs, entrySize_); + simd::memcpy(*lhs, *rhs, entrySize_); + simd::memcpy(*rhs, swapBuffer_, entrySize_); + } + + FOLLY_ALWAYS_INLINE void rangeSwap( + const detail::PrefixSortIterator& start1, + const detail::PrefixSortIterator& start2, + uint64_t length) const { + for (uint64_t i = 0; i < length; i++) { + swap(start1 + i, start2 + i); + } + } + + // Calculate which one has median of three input iterators. + // The compare logic is (the symbol '<' and '>' means value compare result): + // ---------cmp(a, b)-------- + // | | + // a=b| + // cmp(b, c) cmp(b, c) + // | | | | + // b=c| b>c| b<=c| + // a<=b<=c cmp(a, c) c<=b<=a cmp(a, c) + // | | | | + // a=c| a>c| a<=c| + // a<=c<=b c<=a<=b b<=c<=a b<=a<=c + // case a<=b= + FOLLY_ALWAYS_INLINE static detail::PrefixSortIterator median3( + const detail::PrefixSortIterator& a, + const detail::PrefixSortIterator& b, + const detail::PrefixSortIterator& c, + TCompare cmp) { + return cmp(*a, *b) < 0 ? (cmp(*b, *c) < 0 ? b + : cmp(*a, *c) < 0 ? c + : a) + : (cmp(*b, *c) > 0 ? b + : cmp(*a, *c) > 0 ? c + : a); + } + + template + FOLLY_ALWAYS_INLINE void insertSort( + const detail::PrefixSortIterator& start, + const detail::PrefixSortIterator& end, + TCompare compare) const { + for (auto i = start; i < end; ++i) { + for (auto j = i; j > start && (compare(*(j - 1), *j) > 0); --j) { + swap(j, j - 1); + } + } + } + + // Sort prefix data in range [start, end) using quick-sort. + // Algorithm implementation reference: + // 1. J.L.Bentley and M.McIlroy’s paper: + // “Engineering a Sort Function” + // 2. Presto`s quickSort implementation + // + // Note:The compare parameter should be defined as a template type parameter + // like std::sort, which is much faster than using a function type, because + // of this, quickSort`s implementations also need to be placed in the header + // file. + // TCompare is a compare function : int compare(char*, char*). + template + void quickSort( + const detail::PrefixSortIterator& start, + const detail::PrefixSortIterator& end, + TCompare compare) const { + VELOX_CHECK(end >= start, "Invalid sort range.") + const uint64_t len = end - start; + + // Insertion sort on smallest arrays + if (len < kSmallSort) { + insertSort(start, end, compare); + return; + } + + // Choose a partition element: m. + + // For small arrays, median is the middle element. + auto m = start + len / 2; + if (len > kSmallSort) { + auto l = start; + auto n = end - 1; + // For big arrays, median is the pseudo-median of nine evenly spaced + // elements. + if (len > kMediumSort) { + const uint64_t s = len / 8; + l = median3(l, l + s, l + 2 * s, compare); + m = median3(m - s, m, m + s, compare); + n = median3(n - 2 * s, n - s, n, compare); + } + // For mid-sized array, median is the median of the first, middle and last + // elements. + m = median3(l, m, n, compare); + } + auto a = start; + auto b = a; + auto c = end - 1; + auto d = c; + + // Establish invariant: v* (v)* v*, + // v* means elements that value equal to partition element v, + // (v)* means elements that value bigger than partition element v. + while (true) { + int comparison; + while (b <= c && ((comparison = compare(*b, *m)) <= 0)) { + if (comparison == 0) { + if (a == m) { + m = b; + } else if (b == m) { + m = a; + } + swap(a++, b); + } + b++; + } + while (c >= b && ((comparison = compare(*c, *m)) >= 0)) { + if (comparison == 0) { + if (c == m) { + m = d; + } else if (d == m) { + m = c; + } + swap(c, d--); + } + c--; + } + if (b > c) { + break; + } + if (b == m) { + m = d; + } + // There is a useless assignment statement in the implementation of presto + // when c == m, remove it here, + // see: https://github.com/prestodb/presto/blob + // /542beb8d89fa86feb15008611f8900f0f316ef5f/ + // presto-main/src/main/java/com/facebook/presto/operator + // /PagesIndexOrdering.java + swap(b++, c--); + } + + // Swap partition elements back end middle + uint64_t s; + auto n = end; + s = std::min(a - start, b - a); + rangeSwap(start, b - s, s); + s = std::min(d - c, n - d - 1); + rangeSwap(b, n - s, s); + + // Recursively sort non-partition-elements + s = b - a; + if (s > 1) { + quickSort(start, start + s, compare); + } + s = d - c; + if (s > 1) { + quickSort(n - s, n, compare); + } + } + + const uint64_t entrySize_; + char* const swapBuffer_; +}; +} // namespace facebook::velox::exec::prefixsort diff --git a/velox/exec/prefixsort/PrefixSortEncoder.h b/velox/exec/prefixsort/PrefixSortEncoder.h new file mode 100644 index 000000000000..151f74c3b5b9 --- /dev/null +++ b/velox/exec/prefixsort/PrefixSortEncoder.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include +#include + +#include "velox/common/base/BitUtil.h" +#include "velox/common/base/Exceptions.h" +#include "velox/common/base/SimdUtil.h" + +namespace facebook::velox::exec::prefixsort { + +/// Provides encode/decode methods for PrefixSort. +class PrefixSortEncoder { + public: + /// 1. Only int64_t is supported now. + /// 2. Encoding is compatible with sorting ascending with no nulls. + template + static FOLLY_ALWAYS_INLINE void encode(T value, char* row); + template + static FOLLY_ALWAYS_INLINE void decode(char* row); + + private: + FOLLY_ALWAYS_INLINE static uint8_t flipSignBit(uint8_t byte) { + return byte ^ 128; + } +}; + +/// Assuming that value is little-endian encoded, we encode it as follows to +/// make sure encoding is compatible with sorting ascending: +/// 1 Reverse each byte, for example, 0xaabbccdd becomes 0xddccbbaa. +/// 2 Flip the sign bit. +/// The decode logic is exactly the opposite of the above approach. +template <> +FOLLY_ALWAYS_INLINE void PrefixSortEncoder::encode(int64_t value, char* row) { + const auto v = __builtin_bswap64(static_cast(value)); + simd::memcpy(row, &v, sizeof(int64_t)); + row[0] = flipSignBit(row[0]); +} + +template <> +FOLLY_ALWAYS_INLINE void PrefixSortEncoder::decode(char* row) { + row[0] = flipSignBit(row[0]); + const auto v = __builtin_bswap64(*reinterpret_cast(row)); + simd::memcpy(row, &v, sizeof(int64_t)); +} + +/// For testing only. +namespace { +template +FOLLY_ALWAYS_INLINE void testingEncodeInPlace(const std::vector& data) { + for (auto i = 0; i < data.size(); i++) { + PrefixSortEncoder::encode(data[i], (char*)data.data() + i * sizeof(T)); + } +} + +template +FOLLY_ALWAYS_INLINE void testingDecodeInPlace(const std::vector& data) { + for (auto i = 0; i < data.size(); i++) { + PrefixSortEncoder::decode((char*)data.data() + i * sizeof(T)); + } +} +} // namespace +} // namespace facebook::velox::exec::prefixsort diff --git a/velox/exec/prefixsort/benchmarks/CMakeLists.txt b/velox/exec/prefixsort/benchmarks/CMakeLists.txt new file mode 100644 index 000000000000..2f5119b4492a --- /dev/null +++ b/velox/exec/prefixsort/benchmarks/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (c) Facebook, Inc. and its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +add_executable(velox_prefix_sort_algorithm_benchmark + PrefixSortAlgorithmBenchmark.cpp) +target_link_libraries(velox_prefix_sort_algorithm_benchmark + velox_vector_test_lib ${FOLLY_BENCHMARK}) diff --git a/velox/exec/prefixsort/benchmarks/PrefixSortAlgorithmBenchmark.cpp b/velox/exec/prefixsort/benchmarks/PrefixSortAlgorithmBenchmark.cpp new file mode 100644 index 000000000000..4b3d145e9369 --- /dev/null +++ b/velox/exec/prefixsort/benchmarks/PrefixSortAlgorithmBenchmark.cpp @@ -0,0 +1,94 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include "velox/buffer/Buffer.h" +#include "velox/exec/prefixsort/PrefixSortAlgorithm.h" +#include "velox/exec/prefixsort/PrefixSortEncoder.h" + +DEFINE_int32(sort_data_seed, 1, "random test data generate seed."); + +using namespace facebook::velox; +using namespace facebook::velox::exec; + +namespace { +class PrefixSortAlgorithmBenchmark { + public: + void seed(int32_t seed) { + rng_.seed(seed); + } + + void runQuickSort(std::vector vec) { + char* start = (char*)vec.data(); + uint32_t entrySize = sizeof(int64_t); + auto swapBuffer = AlignedBuffer::allocate(entrySize, pool_.get()); + auto sortRunner = + prefixsort::PrefixSortRunner(entrySize, swapBuffer->asMutable()); + sortRunner.quickSort( + start, start + entrySize * vec.size(), [&](char* a, char* b) { + return memcmp(a, b, 8); + }); + } + + std::vector generateTestVector(int32_t size) { + std::vector randomTestVec(size); + std::generate(randomTestVec.begin(), randomTestVec.end(), [&]() { + return folly::Random::rand64(rng_); + }); + prefixsort::testingEncodeInPlace(randomTestVec); + return randomTestVec; + } + + private: + std::shared_ptr pool_{memory::addDefaultLeafMemoryPool()}; + folly::Random::DefaultGenerator rng_; +}; + +PrefixSortAlgorithmBenchmark bm; + +std::vector data10k; +std::vector data100k; +std::vector data1000k; +std::vector data10000k; + +BENCHMARK(PrefixSort_algorithm_10k) { + bm.runQuickSort(data10k); +} + +BENCHMARK(PrefixSort_algorithm_100k) { + bm.runQuickSort(data100k); +} + +BENCHMARK(PrefixSort_algorithm_1000k) { + bm.runQuickSort(data1000k); +} + +BENCHMARK(PrefixSort_algorithm_10000k) { + bm.runQuickSort(data10000k); +} + +} // namespace + +int main(int argc, char** argv) { + folly::Init init(&argc, &argv); + bm.seed(FLAGS_sort_data_seed); + data10k = bm.generateTestVector(10'000); + data100k = bm.generateTestVector(100'000); + data1000k = bm.generateTestVector(1'000'000); + data10000k = bm.generateTestVector(10'000'000); + folly::runBenchmarks(); + return 0; +} diff --git a/velox/exec/prefixsort/tests/CMakeLists.txt b/velox/exec/prefixsort/tests/CMakeLists.txt new file mode 100644 index 000000000000..eddfa56d0502 --- /dev/null +++ b/velox/exec/prefixsort/tests/CMakeLists.txt @@ -0,0 +1,23 @@ +# Copyright (c) Facebook, Inc. and its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +add_executable(velox_exec_prefixsort_test PrefixSortAlgorithmTest.cpp) + +add_test( + NAME velox_exec_prefixsort_test + COMMAND velox_exec_prefixsort_test + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + +set_tests_properties(velox_exec_prefixsort_test PROPERTIES TIMEOUT 3000) + +target_link_libraries(velox_exec_prefixsort_test velox_vector_test_lib) diff --git a/velox/exec/prefixsort/tests/PrefixSortAlgorithmTest.cpp b/velox/exec/prefixsort/tests/PrefixSortAlgorithmTest.cpp new file mode 100644 index 000000000000..f245225b6921 --- /dev/null +++ b/velox/exec/prefixsort/tests/PrefixSortAlgorithmTest.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include "velox/exec/prefixsort/PrefixSortAlgorithm.h" +#include "velox/exec/prefixsort/PrefixSortEncoder.h" +#include "velox/vector/tests/VectorTestUtils.h" + +namespace facebook::velox::exec::prefixsort::test { + +class PrefixSortAlgorithmTest : public testing::Test, + public velox::test::VectorTestBase { + public: + void testQuickSort(size_t size) { + // Data1 will be sorted by quickSort. + std::vector data1(size); + std::generate( + data1.begin(), data1.end(), [&]() { return folly::Random::rand64(); }); + + // Data2 will be sorted by std::sort. + std::vector data2 = data1; + + // Sort data1 with quick-sort. + { + char* start = (char*)data1.data(); + char* end = start + sizeof(int64_t) * data1.size(); + uint32_t entrySize = sizeof(int64_t); + auto swapBuffer = AlignedBuffer::allocate(entrySize, pool()); + PrefixSortRunner sortRunner(entrySize, swapBuffer->asMutable()); + testingEncodeInPlace(data1); + sortRunner.quickSort( + start, end, [&](char* a, char* b) { return memcmp(a, b, 8); }); + } + + // Sort data2 with std::sort. + std::sort(data2.begin(), data2.end()); + testingDecodeInPlace(data1); + ASSERT_EQ(data1, data2); + } +}; + +TEST_F(PrefixSortAlgorithmTest, quickSort) { + testQuickSort(PrefixSortRunner::kSmallSort - 1); + testQuickSort(PrefixSortRunner::kSmallSort); + testQuickSort(PrefixSortRunner::kSmallSort + 1); + testQuickSort(PrefixSortRunner::kMediumSort); + // Any number bigger than kMediumSort is sufficient for testing. + testQuickSort(PrefixSortRunner::kMediumSort + 1000); +} + +TEST_F(PrefixSortAlgorithmTest, testingMedian3) { + // Generate 3 elements randomly as input data. + std::vector data1(3); + std::generate( + data1.begin(), data1.end(), [&]() { return folly::Random::rand64(); }); + std::vector data2(data1); + testingEncodeInPlace(data1); + + size_t entrySize = sizeof(int64_t); + auto ptr1 = (char*)data1.data(); + auto ptr2 = ptr1 + entrySize; + auto ptr3 = ptr2 + entrySize; + auto medianPtr = PrefixSortRunner::testingMedian3( + ptr1, ptr2, ptr3, entrySize, [&](char* a, char* b) { + return memcmp(a, b, entrySize); + }); + testingDecodeInPlace(data1); + auto median = *(reinterpret_cast(medianPtr)); + // Sort the input vector data, the middle element must be equal to the + // median we calculated. + std::sort(data2.begin(), data2.end()); + ASSERT_EQ(median, data2[1]); +} + +} // namespace facebook::velox::exec::prefixsort::test