blob: 96c27c86298f0c57775991b317ccf7c28d934cfc [file]
/*
* Copyright (C) 2025 The Android Open Source Project
*
* 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 "src/trace_processor/util/galloping_search.h"
#include <algorithm>
#include <cstdint>
#include <random>
#include <vector>
#include "test/gtest_and_gmock.h"
namespace perfetto::trace_processor {
namespace {
std::vector<uint32_t> ExpectedLowerBounds(const std::vector<int64_t>& data,
const std::vector<int64_t>& keys) {
std::vector<uint32_t> expected;
expected.reserve(keys.size());
for (int64_t key : keys) {
auto it = std::lower_bound(data.begin(), data.end(), key);
expected.push_back(static_cast<uint32_t>(it - data.begin()));
}
return expected;
}
std::vector<int64_t> GenerateUniformKeys(int64_t start,
int64_t step,
size_t count) {
std::vector<int64_t> keys;
keys.reserve(count);
for (size_t i = 0; i < count; ++i) {
keys.push_back(start + static_cast<int64_t>(i) * step);
}
return keys;
}
std::vector<int64_t> GenerateSortedRandomKeys(int64_t min_val,
int64_t max_val,
size_t count,
uint32_t seed = 42) {
std::vector<int64_t> keys;
keys.reserve(count);
std::mt19937 rng(seed);
std::uniform_int_distribution<int64_t> dist(min_val, max_val);
for (size_t i = 0; i < count; ++i) {
keys.push_back(dist(rng));
}
std::sort(keys.begin(), keys.end());
return keys;
}
TEST(GallopingSearchTest, Empty) {
std::vector<int64_t> data;
GallopingSearch searcher(data.data(), static_cast<uint32_t>(data.size()));
std::vector<int64_t> keys = {0, 100};
std::vector<uint32_t> results(keys.size());
searcher.BatchedLowerBound(keys.data(), static_cast<uint32_t>(keys.size()),
results.data());
EXPECT_EQ(results[0], 0u);
EXPECT_EQ(results[1], 0u);
}
TEST(GallopingSearchTest, SingleElement) {
std::vector<int64_t> data = {50};
GallopingSearch searcher(data.data(), static_cast<uint32_t>(data.size()));
std::vector<int64_t> keys = {0, 50, 100};
std::vector<uint32_t> results(keys.size());
searcher.BatchedLowerBound(keys.data(), static_cast<uint32_t>(keys.size()),
results.data());
auto expected = ExpectedLowerBounds(data, keys);
EXPECT_EQ(results, expected);
}
TEST(GallopingSearchTest, DenseQueries) {
std::vector<int64_t> data;
for (int64_t i = 0; i < 1000; ++i) {
data.push_back(i * 10);
}
GallopingSearch searcher(data.data(), static_cast<uint32_t>(data.size()));
auto keys = GenerateUniformKeys(0, 5, 200);
std::vector<uint32_t> results(keys.size());
searcher.BatchedLowerBound(keys.data(), static_cast<uint32_t>(keys.size()),
results.data());
auto expected = ExpectedLowerBounds(data, keys);
EXPECT_EQ(results, expected);
}
TEST(GallopingSearchTest, SparseQueries) {
std::vector<int64_t> data;
for (int64_t i = 0; i < 10000; ++i) {
data.push_back(i * 10);
}
GallopingSearch searcher(data.data(), static_cast<uint32_t>(data.size()));
auto keys = GenerateUniformKeys(0, 1000, 100);
std::vector<uint32_t> results(keys.size());
searcher.BatchedLowerBound(keys.data(), static_cast<uint32_t>(keys.size()),
results.data());
auto expected = ExpectedLowerBounds(data, keys);
EXPECT_EQ(results, expected);
}
TEST(GallopingSearchTest, RandomSortedKeys) {
std::vector<int64_t> data;
for (int64_t i = 0; i < 5000; ++i) {
data.push_back(i * 10);
}
GallopingSearch searcher(data.data(), static_cast<uint32_t>(data.size()));
auto keys = GenerateSortedRandomKeys(data.front(), data.back(), 500);
std::vector<uint32_t> results(keys.size());
searcher.BatchedLowerBound(keys.data(), static_cast<uint32_t>(keys.size()),
results.data());
auto expected = ExpectedLowerBounds(data, keys);
EXPECT_EQ(results, expected);
}
TEST(GallopingSearchTest, KeysBeyondData) {
std::vector<int64_t> data = {10, 20, 30, 40, 50};
GallopingSearch searcher(data.data(), static_cast<uint32_t>(data.size()));
std::vector<int64_t> keys = {0, 5, 25, 35, 60, 100};
std::vector<uint32_t> results(keys.size());
searcher.BatchedLowerBound(keys.data(), static_cast<uint32_t>(keys.size()),
results.data());
auto expected = ExpectedLowerBounds(data, keys);
EXPECT_EQ(results, expected);
}
TEST(GallopingSearchTest, DuplicateKeys) {
std::vector<int64_t> data = {10, 20, 20, 20, 30, 40};
GallopingSearch searcher(data.data(), static_cast<uint32_t>(data.size()));
std::vector<int64_t> keys = {15, 20, 25};
std::vector<uint32_t> results(keys.size());
searcher.BatchedLowerBound(keys.data(), static_cast<uint32_t>(keys.size()),
results.data());
auto expected = ExpectedLowerBounds(data, keys);
EXPECT_EQ(results, expected);
}
} // namespace
} // namespace perfetto::trace_processor