blob: 81d40ac44e4c4348147312a39170436305bb7838 [file] [log] [blame]
/*
* Copyright (C) 2023 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/db/storage/numeric_storage.h"
#include <variant>
namespace perfetto {
namespace trace_processor {
namespace storage {
namespace {
// Templated part of FastPathComparison.
template <typename T>
inline void TypedFastPathComparison(std::optional<NumericValue> val,
FilterOp op,
const T* start,
uint32_t num_elements,
BitVector::Builder& builder) {
if (!val) {
builder.Skip(num_elements);
return;
}
std::visit(
[val, start, num_elements, &builder](auto comparator) {
T typed_val = std::get<T>(*val);
for (uint32_t i = 0; i < num_elements; i += BitVector::kBitsInWord) {
uint64_t word = 0;
// This part should be optimised by SIMD and is expected to be fast.
for (uint32_t k = 0; k < BitVector::kBitsInWord; ++k) {
bool comp_result = comparator(start[i + k], typed_val);
word |= static_cast<uint64_t>(comp_result) << k;
}
builder.AppendWord(word);
}
},
GetFilterOpVariant<T>(op));
}
// Templated part of SlowPathComparison.
template <typename T>
inline void TypedSlowPathComparison(std::optional<NumericValue> val,
FilterOp op,
const T* start,
uint32_t num_elements,
BitVector::Builder& builder) {
if (!val) {
builder.Skip(num_elements);
return;
}
std::visit(
[val, start, num_elements, &builder](auto comparator) {
T typed_val = std::get<T>(*val);
for (uint32_t i = 0; i < num_elements; ++i) {
builder.Append(comparator(start[i], typed_val));
}
},
GetFilterOpVariant<T>(op));
}
} // namespace
void NumericStorage::StableSort(uint32_t* rows, uint32_t rows_size) const {
NumericValue val = *GetNumericTypeVariant(type_, SqlValue::Long(0));
std::visit(
[this, &rows, rows_size](auto val_data) {
using T = decltype(val_data);
const T* typed_start = static_cast<const T*>(data_);
std::stable_sort(rows, rows + rows_size,
[typed_start](uint32_t a_idx, uint32_t b_idx) {
T first_val = typed_start[a_idx];
T second_val = typed_start[b_idx];
return first_val < second_val;
});
},
val);
}
void NumericStorage::Sort(uint32_t*, uint32_t) const {}
// Responsible for invoking templated version of FastPathComparison.
void NumericStorage::LinearSearchAligned(FilterOp op,
SqlValue sql_val,
uint32_t offset,
uint32_t num_elements,
BitVector::Builder& builder) const {
PERFETTO_DCHECK(num_elements % BitVector::kBitsInWord == 0);
std::optional<NumericValue> val = GetNumericTypeVariant(type_, sql_val);
// If the value is invalid we should just ignore those elements.
if (!val.has_value() || op == FilterOp::kIsNotNull ||
op == FilterOp::kIsNull || op == FilterOp::kGlob) {
builder.Skip(num_elements);
return;
}
std::visit(
[this, op, offset, num_elements, &builder](auto num_val) {
using T = decltype(num_val);
auto* typed_start = static_cast<const T*>(data_) + offset;
TypedFastPathComparison(num_val, op, typed_start, num_elements,
builder);
},
*val);
}
// Responsible for invoking templated version of SlowPathComparison.
void NumericStorage::LinearSearchUnaligned(FilterOp op,
SqlValue sql_val,
uint32_t offset,
uint32_t num_elements,
BitVector::Builder& builder) const {
std::optional<NumericValue> val = GetNumericTypeVariant(type_, sql_val);
// If the value is invalid we should just ignore those elements.
if (!val.has_value() || op == FilterOp::kIsNotNull ||
op == FilterOp::kIsNull || op == FilterOp::kGlob) {
builder.Skip(num_elements);
return;
}
std::visit(
[this, op, offset, num_elements, &builder](auto val) {
using T = decltype(val);
auto* typed_start = static_cast<const T*>(data_) + offset;
TypedSlowPathComparison(val, op, typed_start, num_elements, builder);
},
*val);
}
uint32_t NumericStorage::UpperBoundIndex(NumericValue val,
Range search_range) const {
return std::visit(
[this, search_range](auto val_data) {
using T = decltype(val_data);
const T* typed_start = static_cast<const T*>(data_);
auto upper = std::upper_bound(typed_start + search_range.start,
typed_start + search_range.end, val_data);
return static_cast<uint32_t>(std::distance(typed_start, upper));
},
val);
}
// As we don't template those functions, we need to use std::visitor to type
// `start`, hence this wrapping.
uint32_t NumericStorage::LowerBoundIndex(NumericValue val,
Range search_range) const {
return std::visit(
[this, search_range](auto val_data) {
using T = decltype(val_data);
const T* typed_start = static_cast<const T*>(data_);
auto lower = std::lower_bound(typed_start + search_range.start,
typed_start + search_range.end, val_data);
return static_cast<uint32_t>(std::distance(typed_start, lower));
},
val);
}
std::optional<Range> NumericStorage::BinarySearch(FilterOp op,
SqlValue sql_val,
Range search_range) const {
std::optional<NumericValue> val = GetNumericTypeVariant(type_, sql_val);
if (op == FilterOp::kIsNotNull)
return Range(0, size());
if (!val)
return std::nullopt;
switch (op) {
case FilterOp::kEq:
return Range(LowerBoundIndex(*val, search_range),
UpperBoundIndex(*val, search_range));
case FilterOp::kLe:
return Range(0, UpperBoundIndex(*val, search_range));
case FilterOp::kLt:
return Range(0, LowerBoundIndex(*val, search_range));
case FilterOp::kGe:
return Range(LowerBoundIndex(*val, search_range), size_);
case FilterOp::kGt:
return Range(UpperBoundIndex(*val, search_range), size_);
case FilterOp::kNe:
case FilterOp::kIsNull:
case FilterOp::kIsNotNull:
case FilterOp::kGlob:
return std::nullopt;
}
return std::nullopt;
}
uint32_t NumericStorage::UpperBoundIndex(NumericValue val,
uint32_t* order,
Range search_range) const {
return std::visit(
[this, order, search_range](auto val_data) {
using T = decltype(val_data);
const T* typed_start = static_cast<const T*>(data_);
auto upper = std::upper_bound(order + search_range.start,
order + search_range.end, val_data,
[typed_start](T val, uint32_t index) {
return val < *(typed_start + index);
});
return static_cast<uint32_t>(std::distance(order, upper));
},
val);
}
// As we don't template those functions, we need to use std::visitor to type
// `start`, hence this wrapping.
uint32_t NumericStorage::LowerBoundIndex(NumericValue val,
uint32_t* order,
Range search_range) const {
return std::visit(
[this, order, search_range](auto val_data) {
using T = decltype(val_data);
const T* typed_start = static_cast<const T*>(data_);
auto lower = std::lower_bound(order + search_range.start,
order + search_range.end, val_data,
[typed_start](uint32_t index, T val) {
return *(typed_start + index) < val;
});
return static_cast<uint32_t>(std::distance(order, lower));
},
val);
}
std::optional<Range> NumericStorage::BinarySearchWithIndex(
FilterOp op,
SqlValue sql_val,
uint32_t* order,
Range search_range) const {
std::optional<NumericValue> val = GetNumericTypeVariant(type_, sql_val);
if (op == FilterOp::kIsNotNull)
return Range(0, size());
if (!val.has_value())
return std::nullopt;
switch (op) {
case FilterOp::kEq:
return Range(LowerBoundIndex(*val, order, search_range),
UpperBoundIndex(*val, order, search_range));
case FilterOp::kLe:
return Range(0, UpperBoundIndex(*val, order, search_range));
case FilterOp::kLt:
return Range(0, LowerBoundIndex(*val, order, search_range));
case FilterOp::kGe:
return Range(LowerBoundIndex(*val, order, search_range), size_);
case FilterOp::kGt:
return Range(UpperBoundIndex(*val, order, search_range), size_);
case FilterOp::kNe:
case FilterOp::kIsNull:
case FilterOp::kIsNotNull:
case FilterOp::kGlob:
return std::nullopt;
}
return std::nullopt;
}
} // namespace storage
} // namespace trace_processor
} // namespace perfetto